首页 > 技术文章 > 算法刷了也有小400了,该静下心来好好总结一下了

ningxinjie 2020-09-17 18:35 原文

前言:准备将一些个人理解的思想(抽象、通用)和一些较别扭的题(具体、个例)列举出来,方便自己回忆,也方便大家学习,如果你正好浏览到此网页,那么想跟你说,这篇网页我会不断更新下去,以后可能会一直会在增长(也有可能太多,我另起一个网页,如果是的话,我会放链接在下方,好了,大家一起学习,共勉吧~~~)

建议:题不在多,在精,即使我刷了几百题,有些题我仍然会忘记,算法是这样,语言也是这样,杰杰给的建议:刷题要理解它的精髓,学语言一定要具备手撕代码的能力(以下绝大多数是杰杰自己在typora中,文本编辑的),如果你恰巧看到了这篇文章,这就是杰杰给你最真诚的建议,加油!

承诺:如果你有任何不懂得地方,可以留言在评论区,我都会认真解答

                          --我是程序杰杰,一个努力的奋进者

 Date:2020.10.17 

 Remark:博主需要学习框架了,本篇帖子暂时更新到这里,以后如有增加,我会在这里再次声明,希望本篇文章能让你对算法的理解更加深刻一些!

1.快慢指针

这个针对于求链表的中点等一些很有用

//伪代码
//求链表的中间节点[1,2,3]就是2;[1,2,3,4]就是3
public ListNode getMidListNode(ListNode root){
   if(root==null||root.next==null)return root;
   ListNode slowPoint=root;
   ListNode fastPoint=root;
   while(fastPoint.next!=null&&fastPoint.next.next!=null){
       slowPoint=slowPoint.next;
       fastPoint=fastPoint.next.next;
  }
   if(fastPoint.next!=null){
       slowPoint=slowPoint.next;
  }
   return slowPoint;
}

2.求和为k的连续子序列

技巧就是用HashMap接收前面已经存在的值了

例1(LC1):求两个和为k的值的下标

我们可以在遍历的时候,使用HashMap来接收当前值为key,和他当前的索引i为value,然后每次走的时候只需要判断当前hashmap中存不存在k-nums[i],如果存在答案就出来了

//伪代码如下
public int get2Add(int nums[],int k){
   HashMap<Integer,Integer> hashMap=new HashMap<>();
   int res[]=new int[2];
   for(int i=0;i<nums.length;i++){
       if(hashMap.contains(k-nums[i])){
           res[0]=hashMap.get(k-nums[i]);
           res[1]=i;
      }
       else{
           hashMap.put(nums[i],i);
      }
  }
   return res;
}

例2(也是LC):求和为k的连续子序列的个数

当我们走到当前位置,将当前位置的和(从头开始的)记录下来,如果j~i之间的和为k,也就是说pre[i]-pre[j-1]=k,转化一下就是pre[i]-k=pre[j-1];也就是说如果hashMap中有pre[j-1],就说明存在从j到i的子串,他们前面的和为pre[j-1],也就是j~i和为k

//伪代码
public int getCount(int nums[],int k){
   HashMap<Integer,Integer> hashMap=new HashMap<>();
   hashMap.put(0,1);//针对于某个值正好等于k
   int sum=0;
   int res=0;
   for(int i=0;i<nums.length;i++){
       sum+=nums[i];
       if(hashMap.contains(sum-k)){
           res+=hashMap.get(sum-k);
      }
       hashMap.put(hashMap.getOrDefault(sum,0)+1) ;
  }
   return res;
}

3.滑动窗口

这个我博客有很好的总结,大体的流程一定是这样:

int l=0,r=0;
while(r<len){
   //do something...
   if(判断条件){
       //do something...
  }
   else{
       //需要移动左指针
  }
   r++;
}

按照这个思路来写,滑动窗口就不会有问题。

4.最长回文串

就是从中心出发,向两边扩散,当然还有奇偶,因此一次循环的时候,两个都考虑,把最大的留下来即可。

//伪代码
//截取字符串太浪费性能了,我们期间不截取,我们找到最大的之后,把左右指针位置保存下来,最后返回的时候截取一次就好了
public String getMaxLenPastr(String s){
   if(s==null|s.length()<=1)return s;
   int len=s.length();
   int oddl,oddr,evenl,evenr;//奇数和偶数的左右指针
   int maxl=0,maxr=0,max=0;
   int templ,tempr;
   for(int i=0;i<len-1;i++){//最后一位最多就是1,因此不需要考虑它
       //先考虑单个中心的扩散(下方代码我们停留的位置内部是回文串,也就是到了我们停留位置就不满足了,这个要知道)
       oddl=i;
       oddr=i;
       while(oddl>=0&&oddr<len){
           if(s.charAt(oddl)!=s.charAt(oddr))break;
           oddl--;
           oddr++;
      }
       //再考虑两个为中心的中心扩散
       evenl=i;
       evenr=i+1;
       while(evenl>=0&&evenr<len){
           if(s.charAt(evenl)!=s.charAt(evenr))break;
           evenl--;
           evenr++;
      }
       if(oddr-oddl>evenr-evenl){
           templ=oddl;
           tempr=oddr;
      }
       else{
           templ=evenl;
           tempr=evenr;
      }
       //tempr-templ+1-2
      if(tempr-templ-1>max){
           max=tempr-templ-1;
           maxl=templ+1;
           maxr=tempr;
      }
  }
   return s.substring(maxl,maxr);
}

5.二叉树的遍历(非递归)

我的博客也有

(有一个很有意思的点,当我们使用栈来做的时候,我们会发现无论哪种遍历,我们都会首先将根节点加进去,这个很有趣) 有点像BFS在使用队列的时候一样,我们总是首先将根节点加入进去

前序遍历

public List<Integer> preOder(TreeNode root){
   List<Integer> res=new ArrayList<>();
   Stack<TreeNode> stack=new Stack<>();
   if(root!=null)
       stack.push(root);
   TreeNode curNode;
   while(!stack.isEmpty()){
       curNode=stack.pop();
       res.add(curNode.val);
       if(curNode.right!=null)stack.push(curNode.right);
       if(curNode.left!=null)stack.push(curNode.left);
  }
   return res;
}

中序遍历(有左边压左边,如果没有弹出,从它做文章)

public List<Integer> infixOrder(TreeNode root){
   List<Integer> res=new ArrayList<>();
   Stack<TreeNode> stack=new Stack<>();
   if(root!=null)
       stack.push(root);
   TreeNode curNode=root;
   while(!stack.isEmpty()){
       if(curNode!=null&&curNode.left!=null){
           stack.push(curNode.left);
           curNode=curNode.left;
      }
       else{
           curNode=stack.pop();
           res.add(curNode.val);
           if(curNode!=null&&curNode.right!=null){
               stack.push(curNode.right);
               curNode=curNode.right;
          }
           else{
               curNode=null;
          }
      }
  }
   return res;
}

后序遍历

public List<Integer> sufixOrder(TreeNode root){
   LinkedList<Integer> res=new LinkedList<>();
   Stack<TreeNode> stack=new Stack<>();
   if(root!=null)
       stack.push(root);
   TreeNode curNode=root;
   while(!stack.isEmpty()){
       curNode=stack.pop();
       res.addFirst(curNode.val);
       if(curNode.left!=null)stack.push(curNode.left);
       if(curNode.right!=null)stack.push(curNode.right);
  }
   return res;
}

6.双指针

左右两个指针,一个往左,一个往右,这个在有序里面用到还是很多的,比如说LC中找【三数之和】

LC三数之和为k(注意去重):

//伪代码
//思路也就是:固定一个,然后其余两个双指针即可
public List<List<Integer>> getRes(int nums[],int k){
   List<List<Integer>> res=new ArrayList<>();
   Arrays.sort(nums);
   int l,r,sum;
   int len=nums.length;
   for(int i=0;i<len;i++){
       if(nums[i]>k)break;//第一个都比k大,那和不可能比k小了
       if(i>0&&nums[i]==nums[i-1])continue;
       l=i+1;
       r=len-1;
       while(l<r){
           sum=nums[i]+nums[l]+nums[r];
           if(sum<k){
               l++;
          }
           else if(sum>k){
               r--;
          }
           else{
               res.add(Arrays.asList(nums[i],nums[l],nums[r]));
               while(l<r&&nums[l]==nums[l+1])l++;
               while(l<r&&nums[r]==nums[r-1])r--;
               l++;
               r--;
          }
      }
  }
   return res;
}

 

7.反转链表

//伪代码
ListNode res=null;
ListNode next;
while(head!=null){
   next=head.next;
   head.next=res;
   res=head;
   head=next;
}

8.leetcode 原题链接跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4] 输出: true 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

public boolean canJump(int[] nums) {
   int max=0;
   for(int i=0;i<nums.length;i++){
       if(i>max)return false;
       max=Math.max(max,i+num[i]);
  }
   return true;
}

9.约瑟夫环问题

这题我们可以使用循环链表来做,这种方式我自己从0写,至少写了5遍了,每次都写好长好长的代码,直到我看到了这个思路:

(思路就是倒叙推,最后一个剩余的元素只有一个那么他的索引就是0,上一层就有2个元素,这个元素的位置在(0+m)%2,这个值作为新的index,继续找(index+m)%3...一直找到第n层即可,显然找到列表有n的元素的位置的时候就是这个剩余的元素所处的位置)

https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/

数学解法,O(n)O(n) 这么著名的约瑟夫环问题,是有数学解法的! 因为数据是放在数组里,所以我在数组后面加上了数组的复制,以体现是环状的。我们先忽略图片里的箭头: 【第一轮后面的数字应该是[0, 1, 2 ,3 ,4],手误打错了。。抱歉】

很明显我们每次删除的是第 mm 个数字,我都标红了。

第一轮是 [0, 1, 2, 3, 4] ,所以是 [0, 1, 2, 3, 4] 这个数组的多个复制。这一轮 2 删除了。

第二轮开始时,从 3 开始,所以是 [3, 4, 0, 1] 这个数组的多个复制。这一轮 0 删除了。

第三轮开始时,从 1 开始,所以是 [1, 3, 4] 这个数组的多个复制。这一轮 4 删除了。

第四轮开始时,还是从 1 开始,所以是 [1, 3] 这个数组的多个复制。这一轮 1 删除了。

最后剩下的数字是 3。

图中的绿色的线指的是新的一轮的开头是怎么指定的,每次都是固定地向前移位 mm 个位置。

然后我们从最后剩下的 3 倒着看,我们可以反向推出这个数字在之前每个轮次的位置。

最后剩下的 3 的下标是 0。

第四轮反推,补上 mm 个位置,然后模上当时的数组大小 22,位置是(0 + 3) % 2 = 1。

第三轮反推,补上 mm 个位置,然后模上当时的数组大小 33,位置是(1 + 3) % 3 = 1。

第二轮反推,补上 mm 个位置,然后模上当时的数组大小 44,位置是(1 + 3) % 4 = 0。

第一轮反推,补上 mm 个位置,然后模上当时的数组大小 55,位置是(0 + 3) % 5 = 3。

所以最终剩下的数字的下标就是3。因为数组是从0开始的,所以最终的答案就是3。

总结一下反推的过程,就是 (当前index + m) % 上一轮剩余数字的个数。

代码就很简单了。

class Solution {
   public int lastRemaining(int n, int m) {
       int ans = 0;
       // 最后一轮剩下2个人,所以从2开始反推
       for (int i = 2; i <= n; i++) {
           ans = (ans + m) % i;
      }
       return ans;
  }
}

10.LC312(戳气球获得最大金币)

dp[i][j]:i~j之间能产生的最大值 【1~n就是我们需要寻找的】

我们假设在i~j之间最后取出的是k,则我们有如下算式

dp[i][j]=dp[i][k-1]+dp[k+1][j]+pointer[i-1]*pointer[k]*pointer[j+1]

上式的解释:因为k是最后一个扎破的气球,因此计算i~k-1k+1~j,它的得分就是pointer[i-1]*pointer[k]*pointer[j+1]

还有一点遍历之所以自下向上,自左向右,这个是根据我们的状态转移方程的哦,这个要牢记

因为i<=k<=j,所以dp[i][k-1]在二维表dp[i][j]的左边,因此我们的列需要从左向右

dp[k+1][j]在二维表dp[i][j]的下边,因此我们的行需要从下向上

 public int maxCoins(int[] nums) {
       int n=nums.length;
       int[] pointer=new int[n+2];
       pointer[0]=1;
       pointer[n+1]=1;
       for (int i = 1; i <= n; i++) {
           pointer[i]=nums[i-1];
      }
    //dp[i][j]代表i~j可以得到的最大金币
       int[][] dp=new int[n+2][n+2];
       for(int i=n;i>0;i--){
           for(int j=i;j<n+1;j++){
               for(int k=i;k<=j;k++){//k是最后取出的一个(这个点是这道题的关键)
                   dp[i][j]=Math.max(dp[i][j],
                           dp[i][k-1]+dp[k+1][j]+pointer[i-1]*pointer[k]*pointer[j+1]);
              }
          }
      }
       return dp[1][n];
  }

我的博客

11.LC452(戳气球)

这道题不难,我们只需要按照结尾排序,这样我们确保了下一个的气球结尾一定比我大,我只需要判断它初始位置是不是<=我当前的位置,如果是则他也就能被扎破了,否则必须从它开始(因为这是下一个结尾最小的了)

 public int findMinArrowShots(int[][] points) {
       if(points==null||points.length==0)return 0;
       Arrays.sort(points,(o1,o2)->o1[1]-o2[1]);
       int res=1;
       int curMax=points[0][1];
       for(int[] item:points){
           if(item[0]>curMax){
               res++;
               curMax=item[1];
          }
      }
       return res;
  }

12.最少插眼个数

这道题显然和上方还是有区别的,为我们需要插入一个范围,因此我们不能按照结尾排序了,需要按照开头排。

思路就是:1.从起点开始找满足的重点最大;2.若出现>起点了,判断是不是比起点的终点还大,如果是则不连续,如果不是则以此时最大的终点为起点继续寻找。

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class Main {
   public static void main(String[] args) {
       Scanner s=new Scanner(System.in);
       while (s.hasNext()){
           int n=s.nextInt();
           int L=s.nextInt();
           int[][] arr=new int[n][2];
           for (int i = 0; i < n; i++) {
               arr[i][0]=s.nextInt();
               arr[i][1]=s.nextInt();
          }

           Arrays.sort(arr, Comparator.comparingInt(o -> o[0]));

           int l=0,r=0,res=0;
           for(int[] item:arr){
               if(item[0]<=l){
                   r=Math.max(r,item[1]);//找到可延伸最大的
                   if(r>=L){
                       res++;
                       System.out.println(res);
                       return;
                  }
              }
               else{
                   if(item[0]>r){//最左区间都不在r内,那么不连续
                       System.out.println(-1);
                       return;
                  }
                   res++;
                   l=r;
                   r=item[1];
                   if(r>=L){
                       res++;
                       System.out.println(res);
                       return;
                  }
              }
          }
           if(r<L)  System.out.println(-1);//找到最后最长的区间,长度也不足L
      }
  }
}

   //这个写法

public int getMinCount(int arrs[][],int L){
   Arrays.sort(arr,Comparator.comparingInt(o -> o[0]));
   int l=0,r=0,res=1;
   for(int[] item:arrs){
       if(item[0]<=l){
           r=Math.max(r,item[1]);
           if(r>=L)return res;
      }
       else{
           if(item[0]>r){
               return -1;
          }
           res++;
           l=r;
           r=item[1];
           if(r>=L)return res;
      }
  }
   if(r<L) return -1
   return res;
}

 

13.双指针问题(很巧)

题目:给定一个有序数组,请算出平方后的结果可能的个数。

思路一:计算平方然后排序,然后找(o(nlogn))

思路二:双指针(o(n))【取完绝对值后,我们只需要每次找到相同的最末端就好(左在右端,右在左端);如:3 【3】0 1【2】2 】

//统计数组的数字平方和不同的个数
//利用双指针
public int findElements(int[] arr){
int len=arr.length;
for(int i=0;i<len;i++){
    arr[i]=Math.abs(arr[i]);
}
int l=0,r=len-1;
int count=0;
while(l<=r){
    //找到第一个相等的数字的端(左在右端,右在左端)
    while(l<r&&arr[l]==arr[l+1]){
        l++;
    }
    while(l<r&&arr[r]==arr[r-1]){
      r--;
    }
    if(arr[l]<arr[r]){
        r--;
    }
    else if(arr[l]>arr[r]){
        l++;
    }
    else{
        r--;
        l++;
    }
    count++;
}
return count;
}

14.双指针问题(2)

题目

一个数据先递增再递减(可能包含重复的数字【也就是说不是严格的递增】),找出数组不重复的个数。不能使用额外空间,复杂度o(n)

public int findElements(int[] arr){
   int count=0;
   int l=0,r=arr.length-1;
   while(l<=r){
       //找到端
       while(l<r&&arr[l]==arr[l+1]){
           l++;
      }
       while(l<r&&arr[r]==arr[r-1]){
           r--;
      }
       
       if(arr[l]<arr[r]){
           l++;
      }
       else if(arr[l]>arr[r]){
           r--;
      }
       else{
           l++;
           r--;
      }
       count++;
  }
   return count;
}

15.每k个反转一次链表

//每k个反转一次链表
//https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
//以下为自己写的代码,效率极高 超过lc 100%java用户
public ListNode reverseKGroup(ListNode head, int k) {
       //首先需要判断够不够k个
       boolean isOk=true;
       ListNode test=head;
       for(int i=0;i<k;i++){
           if(test==null){
               isOk=false;
               break;
          }
           test=test.next;
      }
       if(!isOk){
           return head;
      }
       ListNode res=null;
       ListNode next=null;
       ListNode tail=head; //保存每个的尾部,因为在翻转的时候,最开始的会变成尾部,我使用尾部连接就好啦
       //先翻转前k个
       for(int i=0;i<k;i++){
           next=head.next;
           head.next=res;
           res=head;
           head=next;
      }
       tail.next=reverseKGroup(head,k);
       return res;
  }

16.顺时针打印数组(面试笔试最常考之一)

public List<Integer> printClockWise(int[] arr){
List<Integer> res=new ArrayList<>();
if(arr==null||arr.length==0||arr[0].length==0)return res;
int l=0,r=arr[0].length-1,t=0,b=arr.length-1;
while(true){
    for(int i=l;i<=r;i++){
        res.add(arr[t][i]);
    }
    if(++t>b)break;
   
    for(int i=t;i<=b;i++){
        res.add(arr[i][r]);
    }
    if(--r<l)break;
   
    for(int i=r;i>=l;i--){
        res.add(arr[b][i]);
    }
    if(--b<t)break;
   
    for(int i=b;i>=t;i--){
        res.add(arr[i][l]);
    }
    if(++l>r)break;
}
return res;
}

17.数组排序

写一下堆排序,归并排序,快速排序,插入排序,冒泡排序

堆排序:重点就是adjustHeap这个调整堆的函数

我们首先需要调整堆,最初的时候是完全无序的,因此第一次我们需要从后往前调整堆((len-1-1)/2)

堆形成后,我们将堆首与最后一个叶子节点交换,然后我们移除这个叶子节点,这样一直下去我们就可以通过堆完成排序了

//heap sort
public void heapSort(int[] arr){
   int len=arr.length;
for(int i=len/2-1;i>=0;i--){
       adjustHeap(arr,i,len);
  }
   for(int i=len-1;i>0;i--){
       swap(arr,0,i);
       adjustHeap(arr,0,i);
  }
}
//重点就是调整堆的这个函数
//arr是数组,i是从哪个元素开始调整堆,lenth是当前数组从0开始考虑的长度
private void adjustHeap(int arr[],int i,int length){
int temp=arr[i];
   for(int k=2*i+1;k<length;k=2*k+1){
       //首先判断右节点是不是比左节点还要大,因为我们要找最大的
       if(k+1<length&&arr[k+1]>arr[k]){
           k++;
      }
       if(arr[k]>temp){
           arr[i]=arr[k];
           i=k;
      }
       else{
           break;
      }
  }
   arr[i]=temp;
}
private void swap(int[] arr,int a,int b){
   int temp=arr[a];
   arr[a]=arr[b];
   arr[b]=temp;
}

归并排序:先拆数组,然后合并

public void mergeSort(int[] arr){
   int[] temp=new int[arr.length];
   mergeSort(arr,0,arr.length-1,temp);
}
private void mergeSort(int[] arr,int leftIndex,int rightIndex,int[] temp){
   if(leftIndex>=rightIndex)return;
   int mid=(leftIndex+rightIndex)/2;
   mergeSort(arr,leftIndex,mid,temp);
   mergeSort(arr,mid+1,rightIndex,temp);
   int l=mid,r=rightIndex,t=rightIndex;
   while(l>=leftIndex&&r>mid){
       if(arr[l]>=arr[r]){
           temp[t--]=arr[l--];
      }
       else{
           temp[t--]=arr[r--];
      }
  }
   while(l>=leftIndex){
       temp[t--]=arr[l--];
  }
   while(r>mid){
       temp[t--]=arr[r--];
  }
   for(int i=leftIndex;i<=rightIndex;i++){
       arr[i]=temp[i];
  }
}

快速排序:固定最左边,然后指针先从右找,找到停下,然后左指针再找,找到交换一直到l==r停止,此时左边都是<=base右边都是>base所以我们针对于左右再排序就好了

public void quickSort(int[] arr){
   quickSort(arr,0,arr.length-1);
}
private void quickSort(int[] arr,int leftIndex,int rightIndex){
   if(leftIndex>=rightIndex)return;
   int base=arr[leftIndex];
   int l=leftIndex,r=rightIndex;
   while(l<r){
       while(l<r&&arr[r]>base){
           r--;
      }
       while(l<r&&arr[l]<=base){
           l++;
      }
       //交换
       swap(arr,l,r);
  }
   arr[leftIndex]=arr[l];
   arr[l]=base;
   quickSort(arr,leftIndex,l-1);
   quickSort(arr,l+1,rightIndex);
}
private void swap(int[] arr,int a,int b){
   int temp=arr[a];
   arr[a]=arr[b];
   arr[b]=temp;
}

插入排序

public void insertSort(int[] arr){
   int insertVal,insertIndex;
   for(int i=0;i<arr.length;i++){
       insertVal=arr[i];
       insertIndex=i-1;
       while(insertIndex>=0&&arr[insertIndex]>insertVal){
           arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
      }
       arr[insertIndex+1]=insertVal;
  }
}

public void insertSort(int[] arr){
    for(int i=1;i<arr.length;i++){
        for(int j=i;j>0;j--){
            if(arr[j-1]>arr[j]){
                swap(arr,j-1,j);
            }
        }
    }
}

冒泡排序:每次将最大的移到最后

public void bubbleSort(int[] arr){
   for(int i=arr.length-1;i>0;i--){
       for(int j=0;j<i;j++){
           if(arr[j]>arr[j+1]){
               swap(arr,j,j+1);
          }
      }
  }
}
private void swap(int[] arr,int a,int b){
   int temp=arr[a];
   arr[a]=arr[b];
   arr[b]=temp;
}
public void bubbleSort(int[] arr){
   for(int i=0;i<arr.length;i++){
       for(int j=0;j<arr.length-1-i;j++){
           if(arr[j]>arr[j+1]){
               swap(arr,j,j+1);
          }
      }
  }
}

18.LC845数组中的最长山脉

我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”:

B.length >= 3 存在 0 < i < B.length - 1 使得 B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1] (注意:B 可以是 A 的任意子数组,包括整个数组 A。)

给出一个整数数组 A,返回最长 “山脉” 的长度。

如果不含有 “山脉” 则返回 0。

输入:[2,1,4,7,3,2,5]
输出:5
解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。
class Solution {
   
   public int longestMountain(int[] arr) {
       if(arr==null||arr.length==0) return 0;
       int len=arr.length;
       int res=0,index=0;
       while(index<len){
           int end=index;
           //先升序
           if(end+1<len&&arr[end]<arr[end+1]){
               while(end+1<len&&arr[end]<arr[end+1]){
                   end++;
              }
               //再降序,才是山脉
               if(end+1<len&&arr[end]>arr[end+1]){
                   while(end+1<len&&arr[end]>arr[end+1]){
                       end++;
                  }
                   res=Math.max(res,end-index+1);
              }
          }

           index=Math.max(index+1,end);
      }
       return res;
  }
   
    /*
       //动态规划运行到72/72超时。。。。
       if(arr==null||arr.length==0)return 0;
       int n=arr.length;
       int res=0;
       boolean[][] dp=new boolean[n][n];//代表i~j是否是山脉
       //dp[i][j]=(j-i+1>=3)&&dp[i+1][j-1]&&arr[i]<arr[i+1]&&arr[j]<arr[j-1]  
       //上方的表达式其实还是有问题的,就是我有4个的时候判别直接缩成2个肯定是false,因此我们不能一次缩两个,我们可以左边或者右边缩,如下表达式
       //dp[i][j]=(j-i+1>=3)&&(dp[i+1][j]&&arr[i]<arr[i+1])||(dp[i][j-1]&&arr[j]<arr[j-1]);
       //可以看出,我们需要使用左下角的值,因此我们必须确保我们需要的左下角的值能求出来
       //那么我们就可以从下往上,从左往右
       //当然我们必须确保可以为true的条件哦,也就是在临街的长度为3的时候
       for(int i=n-1;i>=0;i--){
           for(int j=i+1;j<n;j++){
               if(j-i+1==3){
                   if(arr[i]<arr[i+1]&&arr[i+1]>arr[i+2])dp[i][j]=true;
               }
               else{
                   dp[i][j]=(j-i+1>=3)&&(dp[i+1][j]&&arr[i]<arr[i+1])||(dp[i][j-1]&&arr[j]<arr[j-1]);
               }
               if(dp[i][j]){
                   res=Math.max(res,j-i+1);
               }
           }
       }
       return res;*/
}

 

19.重建二叉树【前序和中序】

https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/ 【剑指offer的题】

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

 

例如,给出

前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树:

3

/ \ 9 20 / \ 15 7

public TreeNode buildTree(int[] preorder, int[] inorder) {
    TreeNode res=dfs(preorder,0,inorder,0,inorder.length-1);
    return res;
}
private TreeNode dfs(int[] preorder,int psIndex, int[] inorder,int isIndex,int ieIndex){
    if(isIndex>ieIndex){
        return null;
    }
    TreeNode res=new TreeNode(preorder[psIndex]);
    int iMid=isIndex;
    for(int i=isIndex;i<=ieIndex;i++){
        if(inorder[i]==preorder[psIndex]){
            iMid=i;
            break;
        }
    }
    res.left=dfs(preorder,psIndex+1,inorder,isIndex,iMid-1);
    res.right=dfs(preorder,psIndex+iMid-isIndex+1,inorder,iMid+1,ieIndex);
    return res;
}

中序和后序

public TreeNode buildTree(int[] inorder, int[] sufixorder) {
   int len=inorder.length;
   return buildTree(inorder,0,len-1,sufixorder,0,len-1);
}
private TreeNode buildTree(int[] inorder,int is,int ie, int[] sufixorder,int ss,int se){
   if(ss>se)return null;
   int val=sufixorder[se];
   TreeNode treeNode=new TreeNode(val);
   int mid=0;
   for(int i=is;i<=ie;i++){
       if(inorder[i]==val){
           mid=i;
           break;
      }
  }
   treeNode.left=buildTree(inorder,is,mid-1,sufixorder,ss,ss+mid-is-1);
   treeNode.right=buildTree(inorder,mid+1,ie,sufixorder,ss+mid-is,se-1);
   return treeNode;
}

20.LC671. 二叉树中第二小的节点

难度简单104收藏分享切换为英文关注反馈

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 20。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。

给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1

示例 1:

输入: 
2
/ \
2   5
/ \
5   7

输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。

示例 2:

输入: 
2
/ \
2   2

输出: -1
说明: 最小的值是 2, 但是不存在第二小的值。
public int findSecondMinimumValue(TreeNode root) {
//根节点一定是最小值
if(root==null||root.left==null)return -1;//因为题目要求出现只能2个,因此右边可以不判别
return dfs(root,root.val);
}
private int dfs(TreeNode root,int min){
if(root==null) return -1;
if(root.val>min)return root.val;//由题意知,任何一个树的根节点的值一定是最小的
//找不到就是-1,也就是必须要比min大的
int left=dfs(root.left,min);
int right=dfs(root.right,min);
if(left==-1)return right;
if(right==-1)return left;
return Math.min(left,right);
}

21.LC873. 最长的斐波那契子序列的长度

如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的:

  • n >= 3

  • 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}

给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

(回想一下,子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8][3, 4, 5, 6, 7, 8] 的一个子序列)

示例 1:

输入: [1,2,3,4,5,6,7,8]
输出: 5
解释:
最长的斐波那契式子序列为:[1,2,3,5,8] 。

示例 2:

输入: [1,3,7,11,12,14,18]
输出: 3
解释:
最长的斐波那契式子序列有:
[1,11,12],[3,11,14] 以及 [7,11,18] 。
public int lenLongestFibSubseq(int[] A){
HashMap<Integer,Integer> hashIndex=new HashMap<>();
int len=A.length;
for (int i = 0; i < len; i++) {
    hashIndex.put(A[i],i);
}
int res=0;
//dp[i][j]代表结尾到i j一共有几个满足条件的(也就是说考虑结尾是i j的斐波那契数列有几个)
int[][] dp=new int[len][len];
for(int k=0;k<len;k++){
    for(int j=0;j<k;j++){
        int index=hashIndex.getOrDefault(A[k]-A[j],-1);
        if(index>=0&&index<j){
            dp[j][k]=dp[index][j]==0?3:dp[index][j]+1;
            res=Math.max(res,dp[j][k]);
        }
    }
}
return res;
}

22.删除重复连续的数字 如1221 输出为空

个人思路:我先放进去,然后判别是否和我最后插入进来的重复,如果是的话,则这些后面重复的我直接跳过,跳过完后,我需要将这个也移除。

public String getNewStr(String str){
Deque<Character> queue=new LinkedList<>();
int len=str.length();
char c;
for(int i=0;i<len;i++){
    c=str.charAt(i);
    if(queue.isEmpty()){
        queue.add(c);
    }
    else{
        if(queue.getLast()==c){
            //这些重复的都不添加进去
            while (i<len&&queue.getLast()==str.charAt(i)){
                i++;
            }
            queue.removeLast();
            i--;//因为i下次循环还要++,因此我这里应该定位到前一个,这样循环到这一位才是对的
        }
        else{
            queue.add(c);
        }
    }
}
return queue.toString();
}

23.LC538把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 原始二叉搜索树: 5 / \ 2 13

输出: 转换为累加树: 18 / \ 20 13

class Solution {
   //1ms
   int sum=0;
   public TreeNode convertBST(TreeNode root) {
       if (root != null) {
           convertBST(root.right);
           sum += root.val;
           root.val = sum;
           convertBST(root.left);
      }
       return root;
       
  }
   //3ms
   //就是二叉树中序遍历反过来,中序是左 上 右 反过来呗 我就 右 上 左 (因为大的都在右边)
    /*public TreeNode convertBST(TreeNode root) {
       //这是AVL,也就是右节点的左<上<右
       //利用这个规律开始计算(最右节点肯定最大,因此它需要+0,其余的都不行,因此我们直接中序遍历给反过来,就好了)
       //变成->右,上,左
       //先加完,再给他赋值
       Stack<TreeNode> stack=new Stack<>();
       if(root!=null){
           stack.push(root);
       }
       int sum=0;
       int tempSum;
       TreeNode curNode=root;
       while(!stack.isEmpty()){
           if(curNode!=null&&curNode.right!=null){
               stack.push(curNode.right);
               curNode=curNode.right;
           }
           else{
               curNode=stack.pop();
               tempSum=sum;
               sum+=curNode.val;
               curNode.val+=tempSum;
               if(curNode!=null&&curNode.left!=null){
                   stack.push(curNode.left);
                   curNode=curNode.left;
               }
               else{
                   curNode=null;
               }
           }
       }
       return root;
       }*/

    //11ms
   //先取出所有节点的值,排序(这个解法针对于任意树(当前写法是节点值没有重复的,重复就不对了,hash哪里需要改的),本题是AVL)
    /*if(root==null||(root.left==null&&root.right==null))return root;
       List<Integer> list=new ArrayList<>();
       Queue<TreeNode> queue=new LinkedList<>();
       if(root!=null)
           queue.add(root);
       while(!queue.isEmpty()){
           int size=queue.size();
           while(size-->0){
               TreeNode temp=queue.poll();
               list.add(temp.val);
               if(temp.left!=null){
                   queue.add(temp.left);
               }
               if(temp.right!=null){
                   queue.add(temp.right);
               }
           }
       }
       Collections.sort(list,(o1,o2)->(o2-o1));
       HashMap<Integer,Integer> hashMap=new HashMap<>();
       int sum=0;
       hashMap.put(list.get(0),0);
       //这里是严格的AVL不含重复的节点值
       for(int i=1;i<list.size();i++){
           hashMap.put(list.get(i),hashMap.get(list.get(i-1))+list.get(i-1));
       }

       if(root!=null)
           queue.add(root);
       while(!queue.isEmpty()){
           int size=queue.size();
           while(size-->0){
               TreeNode temp=queue.poll();
               temp.val+=hashMap.get(temp.val);
               if(temp.left!=null){
                   queue.add(temp.left);
               }
               if(temp.right!=null){
                   queue.add(temp.right);
               }
           }
       }
       return root;*/
}

24.LC739每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

思路:我们当然可以从当前点出发,暴力解决,但是显然时间复杂度过高

我们可以使用栈来解决,也就是将当前节点索引和值压入栈,下一个来的时候,我判断当前顶端是不是比我这个值小,如果是的话,则将顶端弹出,并且更新它的天数,继续做知道找不到或者栈为空,则将这个元素压入,个人实现代码如下:

public int[] dailyTemperatures(int[] arr) {
int len=arr.length;
int[] res=new int[len];
Stack<MyPoint> stack=new Stack<>();
MyPoint temp;
for(int i=0;i<len;i++){
    if(stack.isEmpty()){
        stack.push(new MyPoint(i,arr[i]));
    }
    else{
        //temp=stack.peek();
        while(!stack.isEmpty()&&stack.peek().val<arr[i]){
            res[stack.peek().index]=i-stack.peek().index;
            stack.pop();
        }
        stack.push(new MyPoint(i,arr[i]));
    }
}
return res;
}
class MyPoint{
int index;
int val;
public MyPoint(int x,int y){
    this.index=x;
    this.val=y;
}
}

25.LC315. 计算右侧小于当前元素的个数

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

思路:same as https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 我们使用归并排序来使用,本题我们需要比这个链接题做的要多一些,因为他需要确定每个索引位置对应后面比他小的个数,这样我们就没法用一个sum来全部统计,因此我们使用index数组,记录他其实index,随着他的位置变化,他的index也跟着他走,这样我就能一直知道它对应起始的index在哪,我创建一个ans数组,每次我按index位置找ans去+1,这样答案也就出来了

//可以再归并的时候,我们统计
int[] ans;

public List<Integer> countSmaller(int[] nums) {
int len=nums.length;
this.ans=new int[len];
int[] indexs=new int[len];
for(int i=0;i<len;i++){
    indexs[i]=i;
}
mergeSort(nums,indexs,0,len-1,new int[len],new int[len]);
List<Integer> arrList=new ArrayList<>();
for(int i=0;i<len;i++){
    arrList.add(ans[i]);
}
return arrList;
}
//indexs记录我们移动式nums各个数字变动所在的位置
private void mergeSort(int[] arr,int[] indexs,int leftIndex,int rightIndex,int[] temp,int[] tempIndex){
if(leftIndex>=rightIndex)return;
int mid= (leftIndex+rightIndex)/2;
mergeSort(arr,indexs,leftIndex,mid,temp,tempIndex);
mergeSort(arr,indexs,mid+1,rightIndex,temp,tempIndex);
int l=mid,r=rightIndex,t=rightIndex,ti=rightIndex;
while(l>=leftIndex&&r>mid){
    if(arr[l]>arr[r]){
        temp[t--]=arr[l];
        tempIndex[ti--]=indexs[l];
        ans[indexs[l]]+=r-mid;
        l--;
    }
    else{
        temp[t--]=arr[r];
        tempIndex[ti--]=indexs[r];
        r--;
    }
}
while(l>=leftIndex){
    temp[t--]=arr[l];
    tempIndex[ti--]=indexs[l];  
    l--;
}
while(r>mid){
    temp[t--]=arr[r];
    tempIndex[ti--]=indexs[r];
    r--;
}
for(int i=leftIndex;i<=rightIndex;i++){
    arr[i]=temp[i];
    indexs[i]=tempIndex[i];
}
}

26.LC968监控二叉树

lc968(ctrl+k)

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

示例 1:

输入:[0,0,null,0,0] 输出:1 解释:如图所示,一台摄像头足以监控所有节点。

示例 2:

输入:[0,0,null,0,null,0,null,null,0] 输出:2 解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

/*
   参考题解
   有三个状态:
   0=>这个结点待覆盖
   1=>这个结点已经覆盖
   2=>这个结点上安装了相机
*/
class Solution {
   int res=0;
   public int minCameraCover(TreeNode root) {
       if(dfs(root)==0){
           //我没被覆盖,则我需要一个摄像头
           res++;
      }
       return res;
  }
   //返回当前节点的状态
   private int dfs(TreeNode root){
       //没有节点,那说明我也不需要管他,它肯定算被覆盖
       if(root==null)return 1;
       int left=dfs(root.left);
       int right=dfs(root.right);

       //左右子节点都没有被监控到,那么我需要安装摄像头了
       if(left==0||right==0){
           res++;
           return 2;
      }
       //左右子节点都被覆盖了(没人管我)
       else if(left==1&&right==1){
           return 0;
      }
       //左右有一个安装了摄像头(我就被覆盖啦)
       else if(left+right>=3){
           return 1;
      }
       return -1;
  }
}

总结:二叉树的题目百分之95都是需要递归写的,无论多复杂,只要你想明白如何构建递归函数即可,我们可以试想一下,如上方的题,试想它只有3个节点,然后我们构建递归,写完之后再考虑有没有其他边界点没有考虑到的,这样问题就迎刃而解了。

 

27.LC33. 搜索旋转排序数组(要求算法时间复杂度为O(log n) 级别)

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0 输出: 4 示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3 输出: -1

思路:我们二分查找中间的,如果中间的正好是,那么找到了,直接返回

否则判断左边数组,最左边是不是比右边大,如果是则说明左边是有序的,然后判断target是不是在这个范围内,如果是则在这个范围找,否则在另一边找


public int search(int[] nums, int target) {
int l=0,r=nums.length-1;
while(l<=r){
    int mid=l+(r-l)/2;
    if(nums[mid]==target)return mid;
    //左半边是排好序的(是小于等于,因为需要把单个的考虑上)
    //不加的话[3,2] 1就出问题喽,但是可下方的写法
    if(nums[l]<=nums[mid]){
        if(nums[l]<=target&&nums[mid]>target){
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }
    else{
        if(nums[r]>=target&&nums[mid]<target){
            l=mid+1;
        }
        else{
            r=mid-1;
        }
    }
}
return -1;
}

或者这个写法

public int search(int[] nums, int target) {
   int l=0,r=nums.length-1;
   while(l<=r){
       int mid=l+(r-l)/2;
       if(nums[mid]==target)return mid;

       //左边是有序的
       if(nums[l]<nums[mid]){
           if(nums[mid]>target&&nums[l]<=target){
               r=mid-1;
          }
           else{
               l=mid+1;
          }
      }
       else{//右边有序
           if(mid+1<nums.length&&nums[mid+1]<=target&&nums[r]>=target){
               l=mid+1;
          }
           else{
               r=mid-1;
          }
      }
  }
   return -1;
}

28.剑指 Offer 42. 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

 

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i = 1; i < nums.length; i++) {
    nums[i] += Math.max(nums[i - 1], 0);
    res = Math.max(res, nums[i]);
}
return res;
}
public int maxSubArray(int[] nums) {
if(nums.length==0)return 0;
int res=nums[0],len=nums.length;
for(int i=1;i<len;i++){
    nums[i]=Math.max(nums[i-1]+nums[i],nums[i]);//
    res=Math.max(nums[i],res);
}
return res;
}

29.LC6. Z 字形变换

难度中等842收藏分享切换为英文关注反馈

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例 1:

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"

示例 2:

输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:

L     D     R
E   O E   I I
E C   I H   N
T     S     G
//整个过程也就是加到第0个,然后加到第1,2,3,2,1,0,1,2这样无限加,直到加完,然后我们合并每一行即可
//https://leetcode-cn.com/problems/zigzag-conversion/solution/zzi-xing-bian-huan-by-jyd/
//这个链接的图解异常清晰
public String convert(String s, int numRows) {
   if(numRows==1)return s;
   List<StringBuilder> list=new ArrayList<>();
   for(int i=0;i<numRows;i++){
       list.add(new StringBuilder());
  }

   int i=0;
   int index=0;
   char c;
   boolean isIncrease=true;
   while(i<s.length()){
       c=s.charAt(i);
       list.get(index).append(c);
       if(isIncrease){
           index++;
           if(index==numRows){
               index-=2;
               isIncrease=false;
          }
      }
       else{
           index--;
           if(index<0){
               isIncrease=true;
               index+=2;
          }
      }
       i++;
  }
   StringBuilder sb=new StringBuilder();
   for(int k=0;k<numRows;k++){
       sb.append(list.get(k));
  }
   return sb.toString();
}

30.链表相交(写的太好了)

leetcode 原题链接面试题 02.07. 链表相交

思路

参考一个非常精简的的写法的的:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/solution/chao-jian-dan-zheng-ming-shuang-zhi-zhen-de-zheng-/

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode t1 = headA;
    ListNode t2 = headB;
    while(t1 != t2){
        t1 = t1 != null ? t1.next : headB;
        t2 = t2 != null ? t2.next : headA;
    }
    return t2;
}
}

作者:antione
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/solution/chao-jian-dan-zheng-ming-shuang-zhi-zhen-de-zheng-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

31.LC76. 最小覆盖子串

难度困难772收藏分享切换为英文关注反馈

给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。

 

示例:

输入:S = "ADOBECODEBANC", T = "ABC"
输出:"BANC"

 

提示:

  • 如果 S 中不存这样的子串,则返回空字符串 ""

  • 如果 S 中存在这样的子串,我们保证它是唯一的答案。

下面代码是我自己写的一个,用的滑动窗口,效率有点低,不过看起来还是很好理解的

public String minWindow(String s, String t) {
   int sLen=s.length(),tLen=t.length();
   if(sLen<tLen)return "";
   char c;
   HashMap<Character,Integer> hashT=new HashMap<>();//统计t一共多少个字符以及各个的数量
   for (int i = 0; i < tLen; i++) {
       c=t.charAt(i);
       hashT.put(c,hashT.getOrDefault(c,0)+1);
  }

   HashMap<Character,Integer> hashS=new HashMap<>();
   String res="";
   int l=0,r=0;
   while (r<sLen){
       c=s.charAt(r);
       hashS.put(c,hashS.getOrDefault(c,0)+1);
       while (l<=r&&check(hashS,hashT)){
           if(res.equals("")||r-l+1<res.length()){
               res=s.substring(l,r+1);
          }
           c=s.charAt(l++);
           hashS.put(c,hashS.get(c)-1);
      }
       r++;
  }
   return res;
}
//检查hashS是否包含了hashT全部
private boolean check(HashMap<Character,Integer> hashS,HashMap<Character,Integer> hashT){
   for (Map.Entry<Character, Integer> me : hashT.entrySet()) {
       if(hashS.get(me.getKey())==null||hashS.get(me.getKey())<me.getValue())return false;
  }
   return true;
}
//看另外一个人写的,效率很高
public String minWindow(String s, String t) {
   int[] cnts=new int[128];
   for(char c:t.toCharArray()){
       cnts[c]++;
  }

   int sLen=s.length(),tLen=t.length();
   int begin=0,end=0;
   int minbegin=0,minLen=sLen+1;

   while(end<sLen){
       char c1=s.charAt(end);
       if(cnts[c1]>0){
           tLen--;//当tLen减小至0时,s[begin,end]包含了子串t
      }
       cnts[c1]--;//这里即使不是t中的我也-1 这样等下tLen==0的时候,我们移动左指针的时候能知道我们里面还有没有包含t中所有的串了
       while(tLen==0){
           if(end-begin+1 < minLen){
               minLen=end-begin+1;
               minbegin=begin;
          }
           char c2=s.charAt(begin);
           cnts[c2]++;
           if(cnts[c2]>0){
               tLen++;
          }
           begin++;
      }
       end++;
  }
   return minLen <= sLen ? s.substring(minbegin,minbegin+minLen) : "";
}

32.LC18. 四数之和](https://leetcode-cn.com/problems/4sum/)

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

思路:这题的思路和三数之和一样,只不过三数之和我们使用3个指针,那么四数之和我们使用4个指针就好

public List<List<Integer>> fourSum(int[] nums, int target){
   Arrays.sort(nums);
   List<List<Integer>> res=new ArrayList<>();
   int l,r,curSum,len=nums.length;
   for(int i=0;i<=len-4;i++){
       //如果当前这个数字和前方一样,说明这个数字作为第一个值的全部组合已经都存在了,不需要再找以这个节点开头的了,否则就是重复呗
       if(i>0&&nums[i]==nums[i-1])continue;
       for(int j=i+1;j<=len-3;j++){
           //同样,(这里就是求三数之和,这个就相当于三数的第一个节点)
           if(j>i+1&&nums[j]==nums[j-1])continue;
           l=j+1;
           r=len-1;
           while(l<r){
               curSum=nums[i]+nums[j]+nums[l]+nums[r];
               if(curSum<target){
                   l++;
              }
               else if(curSum>target){
                   r--;
              }
               else{
                   res.add(Arrays.asList(nums[i],nums[j],nums[l],nums[r]));
                   //如果发现下个数字和本数字一样的话,我不能要,因为如果要的话,那么移动后还是原来的数字,这样结果就出现重复了;
                   while(l<r&&nums[l]==nums[l+1])l++;
                   while(l<r&&nums[r]==nums[r-1])r--;
                   //找到不重复最后一个的数的下一个
                   l++;
                   r--;
              }
          }
      }
  }
   return res;
}

33.LCP 18. 早餐组合

这是一道力扣的简单题,但是可别用暴力算法,无法通过的哦

小扣在秋日市集选择了一家早餐摊位,一维整型数组 staple 中记录了每种主食的价格,一维整型数组 drinks 中记录了每种饮料的价格。小扣的计划选择一份主食和一款饮料,且花费不超过 x 元。请返回小扣共有多少种购买方案。

注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1

示例 1:

输入:staple = [10,20,5], drinks = [5,5,2], x = 15

输出:6

解释:小扣有 6 种购买方案,所选主食与所选饮料在数组中对应的下标分别是: 第 1 种方案:staple[0] + drinks[0] = 10 + 5 = 15; 第 2 种方案:staple[0] + drinks[1] = 10 + 5 = 15; 第 3 种方案:staple[0] + drinks[2] = 10 + 2 = 12; 第 4 种方案:staple[2] + drinks[0] = 5 + 5 = 10; 第 5 种方案:staple[2] + drinks[1] = 5 + 5 = 10; 第 6 种方案:staple[2] + drinks[2] = 5 + 2 = 7。

示例 2:

输入:staple = [2,1,1], drinks = [8,9,5,1], x = 9

输出:8

解释:小扣有 8 种购买方案,所选主食与所选饮料在数组中对应的下标分别是: 第 1 种方案:staple[0] + drinks[2] = 2 + 5 = 7; 第 2 种方案:staple[0] + drinks[3] = 2 + 1 = 3; 第 3 种方案:staple[1] + drinks[0] = 1 + 8 = 9; 第 4 种方案:staple[1] + drinks[2] = 1 + 5 = 6; 第 5 种方案:staple[1] + drinks[3] = 1 + 1 = 2; 第 6 种方案:staple[2] + drinks[0] = 1 + 8 = 9; 第 7 种方案:staple[2] + drinks[2] = 1 + 5 = 6; 第 8 种方案:staple[2] + drinks[3] = 1 + 1 = 2;

提示:

1 <= staple.length <= 10^5 1 <= drinks.length <= 10^5 1 <= staple[i],drinks[i] <= 10^5 1 <= x <= 2*10^5

思路:我想着是,取出一个主食,看看剩下的钱可以买多少杯饮料,这样遍历一波主食列表,答案也就出来了,可是问题在于我如何知道<=这个金额的饮料有多少呢?看到一种巧妙地思路,也就是说我先记录下来每个价格主食的个数,然后我从头往后加,这样这个位置代表的就是<=这个价格的饮料的个数啦,很清晰哈,如果没看懂的话,看代码,我注释再分析!

执行用时:5 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:57.8 MB, 在所有 Java 提交中击败了87.56%的用户


public int breakfastNumber(int[] staple, int[] drinks, int x){
   int[] dp=new int[x+1];
   //此时的dp[i]代表价格为i的饮料的个数
   for(int i=0;i<drinks.length;i++){
       if(drinks[i]<x)dp[drinks[i]]++;
  }
   //下面操作后dp[i]代表<=i的饮料的个数(这个是重点!!!!)
   for(int i=1;i<x+1;i++){
       dp[i]+=dp[i-1];
  }
   int res=0;
   for(int i=0;i<staple.length;i++){
       if(staple[i]<x){
           res+=dp[x-staple[i]];
      }
       if(res > 1000000007) res %= 1000000007;
  }
   return res;
}

34.面试题 17.18. 最短超串

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入: big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7] small = [1,5,9] 输出: [7,10] 示例 2:

输入: big = [1,2,3] small = [4] 输出: [] 提示:

big.length <= 100000 1 <= small.length <= 100000

以下为个人思路,超过96.77%的java提交

整个思路就是hashMap来记录我需要的元素,找到了则剩余寻找个数-1,因为这个区间可能包含多个同一个元素,因此我如果第一次找到则-1,否则剩余寻找个数不-1,接下来右指针移动,如果剩余为0,则表示全部找到了,那么我左指针需要一直往右移(只要里面包含了全部元素,也就是剩余寻找个数仍然为0),如果当前元素是small中的元素,我判别是不是只有一个,如果不是那我可以继续移动,否则不可以,剩余寻找个数+1,继续下次循环寻找!

 public int[] shortestSeq(int[] big, int[] small) {
       Map<Integer,Integer> hashMap=new HashMap<>();
       for(int i=0;i<small.length;i++){
           hashMap.put(small[i],1);
      }
       int sLen=small.length;
       int l=0,r=0;
       int minLen=Integer.MAX_VALUE;
       int val;
       int[] res=new int[2];
       while(r<big.length){
           val=big[r];
           if(hashMap.containsKey(val)){
               if(hashMap.get(val)==1){
                   hashMap.put(val,0);
                   sLen--;
              }
               else {
                   hashMap.put(val,hashMap.get(val)-1);
              }
          }
           if(sLen==0){
               while (sLen==0){
                   if(minLen>r-l+1){
                       minLen=r-l+1;
                       res[0]=l;
                       res[1]=r;
                  }
                   if(hashMap.containsKey(big[l])){
                       int val2=hashMap.get(big[l]);
                       hashMap.put(big[l],val2+1);
                       if(val2==0){
                           sLen++;
                      }
                  }
                   l++;
              }
          }
           r++;
      }
       if(res[0]==0&&res[1]==0)return new int[]{};
       return res;
  }

35.剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

 

示例 1:

输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1] 示例 2:

输入:nums = [1,2,10,4,1,4,3,3] 输出:[2,10] 或 [10,2]

限制:

2 <= nums.length <= 10000

这道题当然是用HashSet可以很简单的写出来,可是无法满足题目要求哦

思路:做这道题,有一个很有意思的希望你能知道,假如只有一个数字单独,其余的都出现两次,该怎么快速求呢?答案是全部异或(因为相同的异或为0,最后只剩下这个不重复的数^0,结果当然就是这个不重复的数字了);

知道这个思路,我们这道题也要按照这样来做,也就是将数字分组,1.将不同的分到同一组,2.将相同的分到一组 重点是如何满足这两点是我们这道题的重点;其实我们将所有数异或的结果就是这两个不同数字的异或res,然后我们找一个数字使得&上res的某个二进制位为1(因为异或为0,说明二者二进制在这一位不相同),我们有了这个数,然后&上我们的数字,这样两个不同的数肯定分开的(满足条件1),相同的&结果肯定一样(满足条件2)

public int[] singleNumbers(int[] nums){
   int res=0;
   for(int i=0;i<nums.length;i++){
       res^=nums[i];
  }
   int div=1;
   while((res&div)==0){
       div=div<<1;
  }
   int a=0;
   int b=0;
   for(int i=0;i<nums.length;i++){
       if((nums[i]&div)==0){
           a^=nums[i];
      }
       else{
           b^=nums[i];
      }
  }
   return new int[]{a,b};
}

36.LC337. 打家劫舍 III【又一道二叉树的问题】

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]

 3
/ \

2 3 \ \ 3 1

输出: 7 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7. 示例 2:

输入: [3,4,5,1,3,null,1]

 3
/ \

4 5 / \ \ 1 3 1

输出: 9 解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

思路:只要偷得不连起来就行,因此我们分开考虑:

1.当前节点不偷获得最大的钱=左节点可以获得的最大的钱+右节点可以获得的最大的钱

2.当前节点偷获得最大的钱=当前+左节点不偷获得最大的钱+右节点不偷获得的最大的前

仍然记住二叉树问题百分之99都是递归!!!切记!!!

public int rob(TreeNode root) {
   int[] res=dfs(root);
   return Math.max(res[0],res[1]);
}
//[0]就是自己不偷
//[1]就是自己偷
//当前节点不偷获得最大的钱=左节点可以获得的最大的前+右节点可以获得的最大的前
//当前节点偷获得最大的钱=当前+左节点不偷获得最大的钱+右节点不偷获得的最大的前
private int[] dfs(TreeNode root){
   int res[]=new int[2];
   if(root==null)return res;
   int[] left=dfs(root.left);
   int[] right=dfs(root.right);
   res[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
   res[1]=root.val+left[0]+right[0];
   return res;
}

 

 

 未完待续...

 

推荐阅读