• 算法刷了也有小400了,该静下心来好好总结一下了


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

    建议:题不在多,在精,即使我刷了几百题,有些题我仍然会忘记,算法是这样,语言也是这样,杰杰给的建议:刷题要理解它的精髓,学语言一定要具备手撕代码的能力(以下绝大多数是杰杰自己在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=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;
    }

     

     未完待续...

  • 相关阅读:
    存储过程语法
    ORA-20000:ORU-10027:buffer overflow,limit of 2000 bytes.
    ORACLE 存储过程异常捕获并抛出
    Sqlldr导入txt文件内容到数据库中
    ORA-01589: 要打开数据库则必须使用 RESETLOGS 或 NORESETLOGS 选项
    oracle将一个表中字段的值赋值到另一个表中字段(批量)
    (三)Solr——Solr的基本使用
    (二)Solr——Solr界面介绍
    jrebel 7免费激活(非破解) 和 IntelliJ Idea 2017 免费激活方法
    (一)Solr——简介和安装配置
  • 原文地址:https://www.cnblogs.com/ningxinjie/p/13686766.html
Copyright © 2020-2023  润新知