算法刷了也有小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;
}

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;
}

 

 

 未完待續...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM