1. 程式人生 > 其它 >LeetCode演算法總結-回溯法與深度優先搜尋

LeetCode演算法總結-回溯法與深度優先搜尋

轉載自LeetCode演算法總結-回溯法與深度優先搜尋

回溯法(探索與回溯法)是一種選優搜尋法,又稱為試探法,按選優條件向前搜尋,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。
例題一
牛客網-LeetCode148題之combinations問題
題目描述
    給出兩個整數n和k,返回從1到n中取k個數字的所有可能的組合
    例如:
    如果n=4,k=2,結果為
    [↵ [2,4],↵ [3,4],↵ [2,3],↵ [1,2],↵ [1,3],↵ [1,4],↵]

    Given two integers n and k, return all possible combinations of k numbers out of 1 … n.
    For example,
    If n = 4 and k = 2, a solution is:
    [↵ [2,4],↵ [3,4],↵ [2,3],↵ [1,2],↵ [1,3],↵ [1,4],↵]↵
先附帶答案-再進行講解

import java.util.*;
public class Solution {
    /**
     * 
     * @param n int整型 
     * @param k int整型 
     * @return int整型ArrayList<ArrayList<>>
     */
    public ArrayList<ArrayList<Integer>> combine (int n, int k) {
        // write code here
        ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list=new ArrayList<>();
        backTrack(n,k,1,lists,list);
        return lists;
    }
    public void backTrack(int n,int k,int start,ArrayList<ArrayList<Integer>> lists,ArrayList<Integer> list){
        if(k<0){
            return;
        }else if(k==0){
            lists.add(new ArrayList(list));
        }else{
            for(int i=start;i<=n;i++){
                list.add(i);
                backTrack(n,k-1,i+1,lists,list);
                list.remove(list.size()-1);
            }
        }
    }
}

    要求返回的型別是ArrayList<ArrayList< Integer >> 也就是說將所有可能的組合list(由整數構成)放入另一個list(由list構成)中。
    現在進行套路教學:要求返回List<List< Intege r>>,那我就給你一個List<List< Intege r>>,因此
    (1) 定義一個全域性List<List> result=new ArrayList<List>();
    (2) 定義一個輔助的方法(函式)public void backtracking(int n,int k, Listlist){}

    n k 總是要有的吧,加上這兩個引數,前面提到List 是數字的組合,也是需要的吧,這三個是必須的,沒問題吧。(可以嘗試性地寫引數,最後不需要的刪除)
    (3) 接著就是我們的重頭戲了,如何實現這個演算法?對於n=4,k=2,1,2,3,4中選2個數字,我們可以做如下嘗試,加入先選擇1,那我們只需要再選擇一個數字,注意這時候k=1了(此時只需要選擇1個數字啦)。當然,我們也可以先選擇2,3 或者4,通俗化一點,我們可以選擇(1-n)的所有數字,這個是可以用一個迴圈來描述?每次選擇一個加入我們的連結串列list中,下一次只要再選擇k-1個數字。那什麼時候結束呢?當然是k<0的時候啦,這時候都選完了。

回溯法要注意回退
例題二
牛客網-LeetCode148題之combinations問題
題目描述
    給出一組候選數C和一個目標數T,找出候選數中加起來和等於T的所有組合。
    C中的數字在組合中可以被無限次使用
注意:
     題目中所有的數字(包括目標數T)都是正整數
     你給出的組合中的數字 (a 1, a 2, … , a k) 要按非遞增排序 (ie, a 1 ≤ a 2 ≤ … ≤ a k).
     結解集中不能包含重複的組合
    例如:給定的候選數集是[2,3,6,7],目標數是7
解集是:
    [7]
    [2, 2, 3]

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list=new ArrayList<>();
        Arrays.sort(candidates);
        backTrack(candidates,target,0,lists,list);
        return lists;
    }
    public void backTrack(int[] candidates,int target,int start,ArrayList<ArrayList<Integer>> lists,ArrayList<Integer> list){
        if(target<0){
            return;
        }else if(target==0){
            ArrayList<Integer> temp=new ArrayList(list);
            Collections.sort(temp);
            lists.add(temp);
        }else{
            for(int i=start;i<candidates.length;i++){
                list.add(candidates[i]);
                backTrack(candidates,target-candidates[i],i,lists,list);
                list.remove(list.size()-1);
            }
        }
    }
}

例題三
牛客網-LeetCode148題之subsets問題
題目描述
     現在有一個沒有重複元素的整數集合S,求S的所有子集
注意:
     你給出的子集中的元素必須按不下降的順序排列
     給出的解集中不能出現重複的元素
例如:
     如果S=[1,2,3], 給出的解集應為:
     [↵ [3],↵ [1],↵ [2],↵ [1,2,3],↵ [1,3],↵ [2,3],↵ [1,2],↵ []↵]
方法一:其實這種方法完全可以輸出正確內容但是順序與oj不同,不能通過,也沒能找到合適的比較器策略。

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> subsets(int[] S) {
        ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
        Arrays.sort(S);
        backtrack(list, new ArrayList<>(),S, 0);
       // Collections.sort(list);
        return list;
    }
    private void backtrack(ArrayList<ArrayList<Integer>> list , ArrayList<Integer> tempList, int [] nums, int start){
        list.add(new ArrayList<>(tempList));
        for(int i = start; i < nums.length; i++){
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, i + 1);
            tempList.remove(tempList.size() - 1);
        }
    }
}

方法二

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> subsets(int[] S) {
        ArrayList<ArrayList<Integer>> lists = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list=new ArrayList<>();
        Arrays.sort(S);
        for(int i=0;i<=S.length;i++){
            backtrack(S,i,0,lists,list);
        }
       // Collections.sort(list);
        return lists;
    }
    //這裡的K其實就是要獲取多長的子序列
    private void backtrack(int[] S,int k,int start,ArrayList<ArrayList<Integer>> lists , ArrayList<Integer> list){
        if(k<0){
            return;
        }else if(k==0){
            ArrayList<Integer> temp=new ArrayList(list);
            Collections.sort(temp);
            lists.add(temp);
        }else{
            for(int i = start; i < S.length; i++){
                list.add(S[i]);
                backtrack(S,k-1,i+1,lists,list);
                list.remove(list.size()-1);
            }
        }
    }
}

例題四
牛客網-LeetCode148題之subsets進階問題
題目描述
    給出一個可能包含重複元素的整數集合S,返回該整數集合的所有子集。
注意:
     你給出的子集中的元素要按非遞減的順序排列
     給出的解集中不能包含重複的子集
例如:
    如果S =[1,2,2], 給出的解集應該是:
[↵ [2],↵ [1],↵ [1,2,2],↵ [2,2],↵ [1,2],↵ []↵]

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
 
public class Solution {
    public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
        ArrayList<ArrayList<Integer>> lists = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list=new ArrayList<>();
        if(num.length == 0){
            return lists;
        }
        Arrays.sort(num);
        backTrack(num,0,lists,list);
        return lists;
    }
 
    private void backTrack(int[] num, int start,ArrayList<ArrayList<Integer>> lists, ArrayList<Integer> list) {
        lists.add(new ArrayList<>(list));
        for(int i = start ; i < num.length ; i++){
            if( i != start && num[i] == num[i-1]){
                continue;
            }
            list.add(num[i]);
            backTrack(num,i+1,lists,list);
            list.remove(list.size()-1);
        }
    }
}

例題五
牛客網-LeetCode148題之permutations問題
題目描述
    給出一組數字,返回該組數字的所有排列
    例如:
    [1,2,3]的所有排列如下
    [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> permute(int[] num) {
        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
    // Arrays.sort(nums); // not necessary
        ArrayList<Integer> list=new ArrayList<>();
        backtrack(lists,list, num,0);
        return lists;
    }
    private void backtrack(ArrayList<ArrayList<Integer>> lists, ArrayList<Integer> list, int [] nums,int k){
       if(k== nums.length){
          lists.add(new ArrayList<>(list));
           return;
       } else{
          for(int i = 0; i < nums.length; i++){ 
             if(list.contains(nums[i])) continue; // element already exists, skip
             list.add(nums[i]);
             backtrack(lists, list, nums,k+1);
             list.remove(list.size() - 1);
          }
       }
    }
}

例題六
牛客網-LeetCode148題之permutations進階問題
給出一組可能包含重複項的數字,返回該組數字的所有排列
例如;
[1,1,2]的排列如下:
[1,1,2],[1,2,1], [2,1,1].

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
        ArrayList<ArrayList<Integer>> list = new ArrayList<>();
        Arrays.sort(num);
        backtrack(list, new ArrayList<>(), num, new boolean[num.length]);
        return list;
    }
    private void backtrack(ArrayList<ArrayList<Integer>> lists, ArrayList<Integer> list, int [] nums, boolean [] used){
        if(list.size() == nums.length){
            lists.add(new ArrayList<>(list));
        } else{
            for(int i = 0; i < nums.length; i++){
                if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;
                used[i] = true; 
                list.add(nums[i]);
                backtrack(lists, list, nums, used);
                used[i] = false; 
                list.remove(list.size() - 1);
            }
        }
    }
}

例題七
牛客網-LeetCode148題之letter-combination-of-phone問題
    給出一個僅包含數字的字串,給出所有可能的字母組合。
    數字到字母的對映方式如下:(就像電話上數字和字母的對映一樣)
                   

在這裡插入圖片描述

import java.util.*;
public class Solution {
    /**
     * 
     * @param digits string字串 
     * @return string字串ArrayList
     */
    public ArrayList<String> letterCombinations (String digits) {
        // write code here
        ArrayList<String> list=new ArrayList<>();
        String[] str={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        StringBuilder res=new StringBuilder();
        backTrack(digits,str,res,0,list);
        return list;
    }
    //k表示的是到了第幾個數字
    public void backTrack(String digits,String[] strs,StringBuilder sb,int k,ArrayList<String> list){
        if(k==digits.length()){
            String s=sb.toString();
            list.add(s);
            return;
        }
        for(int i=0;i<strs[digits.charAt(k)-'0'].length();i++){
            sb.append(strs[digits.charAt(k)-'0'].charAt(i));
            backTrack(digits,strs,sb,k+1,list);
            sb.delete(sb.length()-1,sb.length());
        }
    }
}

https://www.nowcoder.com/practice/f0069cfcd42649e3b6b0c759fae8cde6?tpId=46&tags=&title=&diffculty=0&judgeStatus=0&rp=1&ru=/ta/leetcode&qru=/ta/leetcode/question-ranking

劍指 Offer 12. 矩陣中的路徑
    請設計一個函式,用來判斷在一個矩陣中是否存在一條包含某字串所有字元的路徑。路徑可以從矩陣中的任意一格開始,每一步可以在矩陣中向左、右、上、下移動一格。如果一條路徑經過了矩陣的某一格,那麼該路徑不能再次進入該格子。例如,在下面的3×4的矩陣中包含一條字串“bfce”的路徑(路徑中的字母用加粗標出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
    但矩陣中不包含字串“abfb”的路徑,因為字串的第一個字元b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入這個格子。
示例 1:
    輸入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
    輸出:true
示例 2
    輸入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
    輸出:false

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        boolean[][] flag=new boolean[board.length][board[0].length];
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(dfs(board, words, flag,i, j, 0)) return true;
            }
        }
        return false;
    }
    boolean dfs(char[][] board, char[] word,boolean[][] flag, int i, int j, int k) {
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]||flag[i][j]) return false;
        if(k == word.length - 1) return true;
       // char tmp = board[i][j];
        //board[i][j] = '/';
        flag[i][j]=true;
        boolean res = dfs(board, word,flag, i + 1, j, k + 1) || dfs(board, word,flag, i - 1, j, k + 1) || 
                      dfs(board, word,flag, i, j + 1, k + 1) || dfs(board, word,flag, i , j - 1, k + 1);
       // board[i][j] = tmp;
       flag[i][j]=false;
        return res;
    }
}

題目描述:(這題其實不是回溯法 但是跟上道題很像)
    地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

public class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        boolean move[][] = new boolean[rows][cols];
        return help(0,0,threshold,rows,cols,move);
    }
    public int help(int i, int j, int threshold, int rows, int cols, boolean[][] move){
        if(i<0||j<0||i>=rows||j>=cols||bitNum(i)+bitNum(j)>threshold||move[i][j] == true)return 0;
        move[i][j] = true;
        return help(i-1,j,threshold,rows,cols,move)+
                help(i+1,j,threshold,rows,cols,move)+
                help(i,j-1,threshold,rows,cols,move)+
                help(i,j+1,threshold,rows,cols,move)+1;
    }
    public int bitNum(int i){
        int sum = 0;
        do{
            sum += i%10;
        }while((i = i/10) > 0);
        return sum;
    }
}

LeetCode 200. 島嶼數量
給你一個由 ‘1’(陸地)和 ‘0’(水)組成的的二維網格,請你計算網格中島嶼的數量。
島嶼總是被水包圍,並且每座島嶼只能由水平方向或豎直方向上相鄰的陸地連線形成。
此外,你可以假設該網格的四條邊均被水包圍。

class Solution {
    void dfs(char[][] grid, int r, int c) {
        int nr = grid.length;
        int nc = grid[0].length;

        if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
            return;
        }

        grid[r][c] = '0';
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int nr = grid.length;
        int nc = grid[0].length;
        int num_islands = 0;
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    ++num_islands;
                    dfs(grid, r, c);
                }
            }
        }

        return num_islands;
    }
}

矩陣中的單詞匹配
單詞匹配
題目描述
給出一個二維字元陣列和一個單詞,判斷單詞是否在陣列中出現,
單詞由相鄰單元格的字母連線而成,相鄰單元指的是上下左右相鄰。同一單元格的字母不能多次使用。
例如:
給出的字元陣列=
[↵ [“ABCE”],↵ [“SFCS”],↵ [“ADEE”]↵]
單詞 =“ABCCED”, -> 返回 true,
單詞 =“SEE”, ->返回 true,
單詞 =“ABCB”, -> 返回 false.

public class Solution {
    public boolean exist(char[][] board, String word){
         
          
         
         if(word == null || word == "" || board == null || board.length == 0){
             return false;
         }
          int wordLengh = word.length();
          int row = board.length ;
          int colum = board[0].length;
          
         int flag[][]= new int[row][colum];
         
         for(int i = 0 ; i < row ; i ++){
            for(int j = 0; j < colum ; j ++){
                if(dfs(i,j,row,colum,word,board,flag,0)){
                    return true;
                }
            }
             
        }
         return false;
    }
      public boolean dfs(int i ,int j ,int rowMax,int columMax,String word,char[][] board,int flag[][],int count){
            if(i >= rowMax || j >= columMax || i < 0 || j < 0 || flag[i][j] == 1 || word.charAt(count) != board[i][j]){
                 return false;
             }
             flag[i][j] = 1;
        
             if(count == word.length() -1 ){
               return true;
             }
              if( dfs(i + 1, j,rowMax,columMax,word,board,flag,count+1) ||dfs(i - 1, j,rowMax,columMax,word,board,flag,count+1)
                || dfs(i , j + 1,rowMax,columMax,word,board,flag,count+1) ||dfs(i , j -1,rowMax,columMax,word,board,flag,count+1)){
                  return true;
              }
              flag[i][j] = 0;
              return false;
    }
}

LeetCode679. 24 點遊戲
24點
你有 4 張寫有 1 到 9 數字的牌。你需要判斷是否能通過 *,/,+,-,(,) 的運算得到 24。

示例 1:

輸入: [4, 1, 8, 7]
輸出: True
解釋: (8-4) * (7-1) = 24
示例 2:

輸入: [1, 2, 1, 2]
輸出: False
注意:

除法運算子 / 表示實數除法,而不是整數除法。例如 4 / (1 - 2/3) = 12 。
每個運算子對兩個數進行運算。特別是我們不能用 - 作為一元運算子。例如,[1, 1, 1, 1] 作為輸入時,表示式 -1 - 1 - 1 - 1 是不允許的。
你不能將數字連線在一起。例如,輸入為 [1, 2, 1, 2] 時,不能寫成 12 + 12 。

import java.util.*;


public class Solution {
    public boolean judgePoint24(int[] nums) {
        List<Double> numbers = new ArrayList<Double>();
        for (int num : nums) {
            numbers.add((double) num);
        }
        return solve(numbers);
    }

    /**
     * @description 回溯法,從陣列中選出兩個數,把運算結果加到陣列中
     */
    private boolean solve(List<Double> numbers) {
        if (numbers.size() == 1) {//陣列中只剩下一個數的時候判斷結果
            return Math.abs(numbers.get(0) - 24) < 1e-6;//看是否與24相等
        }
        //從numbers中取出兩個數,把結果放入陣列中
        for (int i = 0; i < numbers.size(); i++) {
            for (int j = 0; j < numbers.size(); j++) {
                if (i != j) {//取不同的兩個數
                    //如果回溯的話,還要恢復現場,把數插回原位置,所以不如直接生成一個新陣列
                    List<Double> nums = new ArrayList<Double>();
                    for (int k = 0; k < numbers.size(); k++) {
                        if (k != i && k != j) {//把剩下的數加入到新陣列
                            nums.add(numbers.get(k));
                        }
                    }
                    Set<Double> doubles = calculate(numbers.get(i), numbers.get(j));//獲取兩個數運算的結果集
                    for (Double aDouble : doubles) {
                        nums.add(aDouble);//把兩個數運算的結果,分別加入到新陣列中
                        if (solve(nums)) {//找到一個結果,立即返回
                            return true;
                        }
                        nums.remove(nums.size() - 1);//恢復現場
                    }
                }
            }
        }
        return false;//如果沒有找到結果,返回false
    }

    /**
     * @description 返回兩個數計算得到的結果集
     */
    private Set<Double> calculate(double a, double b) {
        Set<Double> res = new HashSet<Double>();
        res.add(a - b);
        res.add(b - a);
        res.add(a + b);
        res.add(a * b);
        if (a != 0) {
            res.add(b / a);
        }
        if (b != 0) {
            res.add(a / b);
        }
        return res;
    }
}

95. 不同的二叉搜尋樹 II
給定一個整數 n,生成所有由 1 … n 為節點所組成的 二叉搜尋樹 。

class Solution {
    public List<TreeNode> generateTrees(int n) {
        if (n == 0) {
            return new LinkedList<TreeNode>();
        }
        return generateTrees(1, n);
    }

    public List<TreeNode> generateTrees(int start, int end) {
        List<TreeNode> allTrees = new LinkedList<TreeNode>();
        if (start > end) {
            allTrees.add(null);
            return allTrees;
        }

        // 列舉可行根節點
        for (int i = start; i <= end; i++) {
            // 獲得所有可行的左子樹集合
            List<TreeNode> leftTrees = generateTrees(start, i - 1);

            // 獲得所有可行的右子樹集合
            List<TreeNode> rightTrees = generateTrees(i + 1, end);

            // 從左子樹集合中選出一棵左子樹,從右子樹集合中選出一棵右子樹,拼接到根節點上
            for (TreeNode left : leftTrees) {
                for (TreeNode right : rightTrees) {
                    TreeNode currTree = new TreeNode(i);
                    currTree.left = left;
                    currTree.right = right;
                    allTrees.add(currTree);
                }
            }
        }
        return allTrees;
    }
}


class Solution {
    public int numTrees(int n) {
        if(n<=2)
        return n;
        //防止溢位
        long cn=1;
        //卡特蘭數
        for(int i=0;i<n;i++){
            cn=cn*2*(2*i+1)/(i+2);
        }
        return (int)cn;
    }
}

馬走日,判斷能否從一個點到另一個點,並返回路徑感覺這個問題應該也是回溯法,但是沒有OJ平臺可以測試。

leetcode 130. 被圍繞的區域

https://www.nowcoder.com/practice/185a87cd29eb42049132aed873273e83?tpId=46&tags=&title=&diffculty=0&judgeStatus=0&rp=1https://www.nowcoder.com/practice/185a87cd29eb42049132aed873273e83?tpId=46&tags=&title=&diffculty=0&judgeStatus=0&rp=1