日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LeetCode算法题8:递归和回溯1

發布時間:2025/6/17 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LeetCode算法题8:递归和回溯1 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
    • 回溯算法:
  • 一、合并兩個有序鏈表(簡單,可略過)
    • 迭代遍歷
    • 一開始沒有想到的遞歸解法
  • 二、反轉鏈表
    • 迭代遍歷(頭插法):
    • 遞歸:
  • 三、組合
    • 回溯:
  • 四、全排列
    • 回溯(交換):
    • 回溯:
  • 五、字母大小寫全排列
    • 回溯:
  • 總結


前言

??????Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2

??????簡單總結一下遞歸回溯相關的算法題:

回溯算法:

??????回溯思想個人覺得還是挺好理解,但目前理解的還是不夠深|><|。以下摘自百度百科:

??????回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。八皇后問題就是回溯算法的典型,第一步按照順序放一個皇后,然后第二步符合要求放第2個皇后,如果沒有位置符合要求,那么就要改變第一個皇后的位置,重新放第2個皇后的位置,直到找到符合條件的位置就可以了。回溯在迷宮搜索中使用很常見,就是這條路走不通,然后返回前一個路口,繼續下一條路。回溯算法說白了就是窮舉法。不過回溯算法使用剪枝函數,剪去一些不可能到達 最終狀態(即答案狀態)的節點,從而減少狀態空間樹節點的生成。

??????回溯法是一個既帶有系統性又帶有跳躍性的的搜索算法。它在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點出發搜索解空間樹。算法搜索至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解。如果肯定不包含,則跳過對以該結點為根的子樹的系統搜索,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜索。回溯法在用來求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜索遍才結束。而回溯法在用來求問題的任一解時,只要搜索到問題的一個解就可以結束。這種以深度優先的方式系統地搜索問題的解的算法稱為回溯法,它適用于解一些組合數較大的問題。

一、合并兩個有序鏈表(簡單,可略過)

??????題目鏈接:https://leetcode-cn.com/problems/merge-two-sorted-lists/

??????題目描述:將兩個升序鏈表合并為一個新的 升序 鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。

迭代遍歷

??????直接采用歸并思想既可。參考算法如下:

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode realh=new ListNode(0);//自定義一個頭節點。ListNode tmp=realh;while(list1!=null&&list2!=null){if(list1.val<list2.val){tmp.next=list1;list1=list1.next;}else{tmp.next=list2;list2=list2.next;}tmp=tmp.next;}tmp.next=list1==null?list2:list1;return realh.next;}

一開始沒有想到的遞歸解法

??????當發現也可以采用遞歸來做的時候,試了一下,先做終止條件判斷,遞歸主體判斷當前兩個結點值之間的大小,值小的結點需要返回,注意要在返回之前完成下一輪的遞歸合并。參考算法如下:

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if(list1==null)return list2;if(list2==null)return list1;if(list1.val<list2.val){list1.next=merge(list1.next,list2);return list1;}else{list2.next=merge(list1,list2.next);return list2;}}

二、反轉鏈表

??????題目鏈接:https://leetcode-cn.com/problems/reverse-linked-list/

??????題目描述:給你單鏈表的頭節點 head ,請你反轉鏈表,并返回反轉后的鏈表。

迭代遍歷(頭插法):

??????參考算法如下:

public ListNode reverseList(ListNode head) {if(head==null)return head;ListNode realh=new ListNode(0),tmp;//仍建立一個頭節點來保存最終的鏈表while(head!=null){tmp=head;//tmp 為待插入的結點,插入位置為 realh 的next處head=head.next; //插入之前需要保證下一次迭代元素訪問的正確性tmp.next=realh.next;realh.next=tmp;}return realh.next;}

遞歸:

??????遞歸稍微有點難理解,可參考題解。下面算法的大體思路為:遞歸終止條件有兩個,當前結點為 null 或當前結點的 next 為 null。1,當前結點為 null 僅針對 head 為一個空鏈表的情況,應返回 null;2,當前結點的 next 為 null 才是遞歸的終止條件。在對鏈表的遞歸遍歷中,每一層遞歸中,我們都可以順序的得到鏈表的每一個結點。

??????下面代碼做的是從從后往前將鏈表逆序,由于我們的終止條件,第一次調整指針是在倒數第二個結點 a處(假設 a->next = b; b->next = null),此時可采用操作: a->next->next=a; a->next =null; 來將當前僅有兩個元素的鏈表逆序。在原鏈表中依次往前,對倒數第三個結點、第四個…也是如此,最后返回結點 b 為新鏈表的第一個結點。

public ListNode reverseList(ListNode head) {if(head==null||head.next==null)return head;ListNode tmp=reverseList(head.next); //tmp 為原鏈表最后一個結點,新鏈表的第一個結點。head.next.next=head; //這兩行代碼從倒數第二個結點處開始調整結點的指向關系,一步步進行逆序。head.next=null;return tmp;}

??????目前僅有的粗淺印象為:從后往前對鏈表結點進行操作需要將遞歸語句放在操作語句的前面;從前往后按照前后順序操作,需要將操作語句放在遞歸語句的前面。

三、組合

??????題目鏈接:https://leetcode-cn.com/problems/combinations/

??????題目描述:給定兩個整數 n 和 k,返回范圍 [1, n] 中所有可能的 k 個數的組合。
你可以按 任何順序 返回答案。

回溯:

??????可參考對應的官方題解,初始的算法如下:

??????1:算法的退出條件代碼塊b放在保存合適的組合情況代碼塊a后面的原因在于代碼塊a在代碼塊c的前面,即先進行訪問、判定操作,后進行遞歸遍歷。對于 n,k = 4,2 的情況,在 start 為 5 的遞歸遍歷中,代碼塊 a 先判斷的是包含 4 的情況,然后再準備添加 5。 若將代碼塊 b 放在 a 的前面,需要更改 b 中的 if 語句為 if(start>end+1)。

//對 n 個數的組合,假設每個數均有兩種狀態,選取和不選取,每一種成功的情況發生在選取的數有 k 個的情況,保存每一個可能的情況。List<List<Integer>> ans=new LinkedList<>();List<Integer> tmp=new LinkedList<>();public List<List<Integer>> combine(int n, int k) {solve(1,n,k);return ans;}public void solve(int start,int end,int k){if(tmp.size()==k){ //碰到每一種成功的情況時應該保存 aans.add(new LinkedList<>(tmp));return;}//start == end,此時表示到達最后一個元素處 bif(start>end)return; //設置算法的退出條件tmp.add(start); //選取當前值 start 的情況 c solve(start+1,end,k);tmp.remove(tmp.size()-1); //不選取的情況solve(start+1,end,k);}

??????當 n=4,k=2 時的執行結果如下(在 solve 方法的第一行添加 System.out.println(tmp);):
????????????????????????????????????????????????[]
????????????????????????????????????????????????[1]
????????????????????????????????????????????????[1, 2]
????????????????????????????????????????????????[1]
????????????????????????????????????????????????[1, 3]
????????????????????????????????????????????????[1]
????????????????????????????????????????????????[1, 4]
????????????????????????????????????????????????[1]
????????????????????????????????????????????????[]
????????????????????????????????????????????????[2]
????????????????????????????????????????????????[2, 3]
????????????????????????????????????????????????[2]
????????????????????????????????????????????????[2, 4]
????????????????????????????????????????????????[2]
????????????????????????????????????????????????[]
????????????????????????????????????????????????[3]
????????????????????????????????????????????????[3, 4]
????????????????????????????????????????????????[3]
????????????????????????????????????????????????[]
????????????????????????????????????????????????[4]
????????????????????????????????????????????????[]
??????2:進一步修改,添加剪枝條件并在此時可以刪除代碼塊 b 的算法參考如下:

??????現在我們知道,代碼塊 a 中判斷的元素范圍不包括當前的 start ,而 end-start+1 表示還未添加進 tmp 集合的的剩余元素,如果 tmp 中元素數量加上剩余元素數量之和小于 k 的話,絕對不可能出現成功的情況,所以程序可直接返回,不需要進行冗余的判斷。

??????并且在此時,代碼塊 b 可以刪除,它不會再被執行。原因在于 b 針對的是當前 tmp 元素數量達不到 k ,且即將要擴大區間到 n+1 的時候,作為此時的邊界條件,程序應該返回。

??????而新加的代碼塊 d,目的是為了避免沒必要的遞歸運算,即剪枝。那么 b 表示的返回條件是否被 d 囊括呢?答案是肯定的。假設對于 n=4,k=2 的情況,當前 tmp 中的元素為 1,start 為 5 時,此時在 d 中程序直接就返回了(即代碼塊 b 的作用)。

??????b 針對的邊界條件:如果當前 tmp 元素數量達不到 k ,且即將要擴大區間到 n+1 的時候,此時 end-start+1 的值為 0,而 tmp.size() 又是小于 k 的,在 d 中程序也就直接返回了。 所以代碼塊 b 可刪除掉。

List<List<Integer>> ans=new LinkedList<>();List<Integer> tmp=new LinkedList<>();public List<List<Integer>> combine(int n, int k) {solve(1,n,k);return ans;}public void solve(int start,int end,int k){if(tmp.size()+(end-start+1)<k) dreturn;if(tmp.size()==k){ //碰到每一種成功的情況時應該保存 aans.add(new LinkedList<>(tmp));return;} /*//start == end,此時表示到達最后一個元素處 bif(start>end)return; //設置算法的退出條件 */tmp.add(start); //選取當前值 start 的情況 c solve(start+1,end,k);tmp.remove(tmp.size()-1); //不選取的情況solve(start+1,end,k);}

四、全排列

??????題目鏈接:https://leetcode-cn.com/problems/permutations/

??????題目描述:給定一個不含重復數字的數組 nums ,返回其所有可能的全排列 。你可以 按任意順序 返回答案。

回溯(交換):

??????先將初始數組保存到 tmp 列表中,方便直接采用 swap 方法來交換。大體思想是:假設數組元素為{1,2,3,4},全排列的順序為:先得到以 1 開頭的所有排列,然后是 2,3,4;假如在以 1 開頭的排列中,先得到其余的 2,3,4開頭的排列;假如在以1 ,2 開頭的排列,分別得到 3,4 開頭的排列;假如在以 1,2,3開頭的排列中,最后一個數只能是 4。

ArrayList<Integer> tmp=new ArrayList<>();List<List<Integer>> ans=new LinkedList<List<Integer>>();//用交換的方法做!public List<List<Integer>> permute(int[] nums) {for (int num : nums)tmp.add(num);solve(0,nums.length-1);return ans;}public void solve(int cur,int end) {if (cur == end) {// cur 表示當前的元素下標ans.add(new ArrayList<>(tmp));return;}else{for(int i=cur;i<=end;i++){Collections.swap(tmp,cur,i);//交換solve(cur+1,end);Collections.swap(tmp,cur,i);//復位}}}

回溯:

??????原文鏈接為對應題解:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/ 不同于交換,這里為依次選擇每一個元素。見下圖:

??????對應的代碼為(稍加修改后):

LinkedList<Integer> tmp=new LinkedList<>();List<List<Integer>> ans=new LinkedList<>();boolean[] flag;public List<List<Integer>> permute(int[] nums) {int len=nums.length;flag=new boolean[len];//由于每次選擇一個元素之后,該元素就不能再被選取了,所以需要標記狀態solve(nums,len-1);return ans;}public void solve(int[] nums,int end) {if(tmp.size()==end+1){//得到一次排列時,保存結果ans.add(new LinkedList<>(tmp));return;}for(int i=0;i<=end;i++){//依次選擇每一個元素,由于有標記數組存在,所以此處循環每次都從下標 0 開始。if(flag[i]==false){tmp.addLast(nums[i]);flag[i]=true;solve(nums,end);flag[i]=false;tmp.removeLast();}}}

五、字母大小寫全排列

??????題目鏈接:https://leetcode-cn.com/problems/letter-case-permutation/

??????題目描述:給定一個字符串 s ,通過將字符串 s 中的每個字母轉變大小寫,我們可以獲得一個新的字符串。

返回 所有可能得到的字符串集合 。以 任意順序 返回輸出。

回溯:

??????這道題和求組合的思路有些類似,組合是一個元素選或不選,本體是一個字母是否發生的大小寫轉換,然后給出所有的字符串集合。參考算法如下:

List<String> ans=new LinkedList<>();public List<String> letterCasePermutation(String s) {char[] ss=s.toCharArray();int start=0,end=ss.length-1;solve(ss,start,end);return ans;}public void solve(char[] ss,int start,int end){if(start>end){ans.add(new String(ss));return;}if(ss[start]>='0'&&ss[start]<='9')solve(ss,start+1,end); //數字跳過else{change(ss,start);solve(ss,start+1,end);change(ss,start);solve(ss,start+1,end);} }public void change(char[] ss, int i){//char tmp='a'-'A';if(ss[i]<='z'&&ss[i]>='a')ss[i]-=('a'-'A');else if(ss[i]<='Z'&&ss[i]>='A')ss[i]+=('a'-'A');}

總結

??????完

總結

以上是生活随笔為你收集整理的LeetCode算法题8:递归和回溯1的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。