leetcode回溯算法刷题总结

刷题大纲

在这里插入图片描述
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等

核心模板

定义两个变量 一个记录路径path 一个为最终结果result
在这里插入图片描述
1. 终止条件是什么
(1)一般是path.size()
(2)取所有结果的不需要终止条件
2. for循环 是从0开始 还是从startIndex开始(取过的不能再取了,跟顺序无关[1,2] [2,1]算一种情况)
3. backTracking函数返回值是什么 结果唯一,返回Boolean
4. 调用递归函数backTracking函数的参数
5. 是否需要去重
6. 涉及到需要k个数的 可以考虑剪枝优化,对i的范围进一步限制

整体套路

画树形结构,for表示横向遍历,for的内容表示深度,分析上面问题------------------创建path result变量---------------------------------套模板

一、子集问题

78 子集(简单回溯)

在这里插入图片描述
求所有可能且无限制不需要终止条件
跟顺序无关,1,2和2,1表示同一个,因此每次index+1开始遍历

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums,0);
        return result;
    }
    public void backTracking(int[] nums,int startIndex){
        result.add(new ArrayList<>(path));
        for(int i = startIndex;i<nums.length;i++){  
            path.add(nums[i]); 
            backTracking(nums,i+1);
            path.removeLast();  
        }
   }

90 子集2 (需要去重)

在这里插入图片描述
先排序,后去重: 当前元素与上一个元素相同
去重是在横向方向for循环上去重

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backTracking(nums,0);
        return result;
    }
    public void backTracking(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));
        for(int i = startIndex; i <nums.length;i++){
            if (i> startIndex && nums[i]==nums[i-1]){
                continue;
            }
            path.add(nums[i]);
            backTracking(nums,i+1);
            path.removeLast();
        }
    }
}

二、组合问题

77 组合(常规回溯+剪枝优化)

在这里插入图片描述
限制i的范围进行剪枝优化

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    // List<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        backTrack(n,k,1);
        return result;

        }
    public void backTrack(int n,int k, int startIndex){
        if (path.size() == k){//终止条件:path的大小与所给一致
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i<=n-(k-path.size())+1; i++){ //剪枝优化:必须有足够多的数满足
            path.add(i);
            backTrack(n,k,i+1); //递归函数要+1
            path.removeLast();
        }
    }
}

17 电话号码的字母组合(回溯+建立数字和字母映射关系)

在这里插入图片描述
画出树形结构
for循环的是每个数字对应的字母
遍历深度的是 所给字符串
把数字和字母对应起来

在这里插入图片描述

class Solution {
    List<String> result = new ArrayList<>();
    StringBuilder temp = new StringBuilder();
    public List<String> letterCombinations(String digits) {
        if (digits.length() == 0 || digits == null){
            return result;
        }
        
        String[] numStr = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; //需要把数字映射到字符串上
        backTracking(digits, numStr, 0);
        return result;

    }
    public void backTracking(String digits,String[] numStr, int index){
        if (index == digits.length()){
            result.add(temp.toString());// 把列表变为字符串
            return;
        }
        String str = numStr[digits.charAt(index) - '0'];
        for (int i = 0; i < str.length(); i++){
            temp.append(str.charAt(i));
            backTracking(digits,numStr,index+1);
            temp.deleteCharAt(temp.length()-1);
        }

    }
}

39 组合总和( 所给数组无重复元素 + 同一数字无限制重复选取)

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        backTracking(candidates,target,0,0);
        return result;
    }
    public void backTracking(int[] candidates, int target, int sum, int index){
        if (sum > target){
            return;
        }
        if (sum == target){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = index; i < candidates.length;i++){  //搜索过的不需要再搜索
            sum += candidates[i];
            path.add(candidates[i]);
            backTracking(candidates,target,sum,i);  //遍历是从i开始 
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

backTracking(candidates,target,sum,i)注意此处不需要i+1,且for循环从index开始

40 组合总和 Ⅱ (给定集合存在重复数字 + 每个数字只能使用一次, 需要去重)

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList path = new LinkedList<>(); 
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backTracking(candidates,target,0,0);
        return result;
    }
    public void backTracking(int[] candidates, int target,int sum,int index){
        if (sum > target){
            return;//深度上剪枝
        }
        if (sum == target){
            result.add(new ArrayList<>(path));
        }
        for (int i = index; i<candidates.length;i++){
            if (i>index && candidates[i] == candidates[i-1]){
                continue;//宽度上剪枝
            }
            sum += candidates[i];
            path.add(candidates[i]);
            backTracking(candidates,target,sum,i+1);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

216 组合总和 Ⅲ (回溯 + 剪枝)

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backTrack(k,n,1,0);
        return result;
    }
    public void backTrack(int k,int n,int startIndex,int sum){
        if (sum > n){ //深度剪枝
            return;
        }
        if (path.size()==k){
            if (sum == n){
                result.add(new ArrayList<>(path));
                return;
            }
        }
        for (int i=startIndex; i <= 9-(k-path.size())+1; i++){ //宽度剪枝:数目不够
            sum += i;
            path.add(i);
            backTrack(k,n,i+1,sum);
            sum -= i;
            path.removeLast();
            
        }
    }
}

三、排列问题

46.全排列

在这里插入图片描述
引入一个used数组记录已经使用过的元素即可

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];     

        backTracking(nums);
        return result;   
    }
    public void backTracking(int[] nums){
        if (path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i =0;i<nums.length;i++){
            if (used[i] == true){
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            backTracking(nums);
            path.removeLast();
            used[i] = false;
        }
    }
}

47.全排列Ⅱ(宽度上进行去重)

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];     
        backTracking(nums);
        return result;  
    }
    public void backTracking(int[] nums){
        if (path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        
        for(int i =0;i<nums.length;i++){
            if (used[i] == true){
                continue;
            }
            if (i>0 && nums[i-1] == nums[i] && used[i-1]==true){
                continue;
            }//在宽度上进行去重 而深度上可以出现重复的元素
            used[i] = true;
            path.add(nums[i]);
            backTracking(nums);
            path.removeLast();
            used[i] = false;
        }
    }
}

四、分割问题

131.分割回文串

在这里插入图片描述
类似组合问题,先求出分割的可能性,再判断是否为回文串
在这里插入图片描述
(1) if 终止条件: 切割线大于字符串长度
(2) 取过的元素不能再取了 for开始遍历的坐标为startIndex
(3) 子串的位置为 startIndex,i+1

class Solution {
    List<List<String>> result = new ArrayList<>();
    LinkedList<String> path = new LinkedList<>();
    public List<List<String>> partition(String s) {
        backTracking(s,0);
        return result;
    }
    public void backTracking(String s, int startIndex){
        if (startIndex >= s.length()){
            result.add(new ArrayList<>(path));
            return;
        }
        //for为横向遍历
        //递归为纵向遍历
        for (int i = startIndex;i<s.length();i++){
            if ( isPalindrom(s,startIndex,i)){//横向遍历
                String str = s.substring(startIndex,i+1);//取子串
                path.add(str);
            }
            else{continue;}
            backTracking(s, i + 1);
            path.removeLast();
        }
    }
    //判断回文
    public boolean isPalindrom(String s,int startIndex,int endIndex){
        for (int i = startIndex, j = endIndex;i<j;i++,j--){
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }
        }
        return true;
    }
}

93.复原ip地址

在这里插入图片描述
在这里插入图片描述
(1) if终止条件: 点数 数量为3
(2) for循环 起始位置 startIndex
(3) string.substring 取子串函数
s.charAt(i) 按索引取元素

class Solution {
    List<String> result = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) {
            return result;
        } //长度最大为12
        backTracking(s,0,0);
        return result;
    }
    public void backTracking(String s, int  startIndex, int pointNum){
        if (pointNum == 3){
            if (isValid(s,startIndex,s.length()-1)){
                result.add(s);
            }
            return;
        }
        for (int i=startIndex;i<s.length();i++){
            if (isValid(s,startIndex,i)){
                s = s.substring(0,i+1) + '.' + s.substring(i+1);
                pointNum++;
                backTracking(s,i+2,pointNum);//加了分隔号 i+2
                pointNum--;
                s = s.substring(0,i+1) + s.substring(i+2);
            }
            else{break;}
        }
    }
    //判断是否符合要求
    public Boolean isValid(String s, int start, int end){
        if (start>end){
            return false;
        } 
        if (s.charAt(start) == '0' && start != end){
            return false; //前导不能为0
        }
        int num=0;
        for (int i = start; i<=end; i++){// 遍历字符串
            if (s.charAt(i) > '9' || s.charAt(i) < '0'){
                return false;
            }
            num = num*10 + (s.charAt(i) - '0'); //字符串计算数值
            if (num>255){
                return false;
            }
        }
        return true;
    }
}

五.棋盘问题

51.N皇后

在这里插入图片描述
棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度
for循环从0开始
游戏规则
不能同行
不能同列
不能同斜线 (45度和135度角)
在这里插入图片描述

class Solution {
    List<List<String>> result = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        //初始化一个二维数组 模拟棋盘
        char[][] chessboard = new char[n][n];
        for (char[] c:chessboard){//记住写法
            Arrays.fill(c,'.');
        }
        //调用回溯函数
        backTracking(n,0,chessboard);
        return result;
    }
    public void backTracking(int n, int row, char[][] chessboard){
        if (row == n){
            List<String> path = new ArrayList<>();
            path = Array2List(chessboard);               //把二维数组变成yi维数组
            result.add(new ArrayList<>(path)); //添加结果
            return;
        }
        for (int col = 0; col < n; col++){//列遍历 即宽度
            if (isValid(n,row,col,chessboard)){//判断是否合法
                chessboard[row][col] = 'Q';
                backTracking(n, row+1, chessboard);
                chessboard[row][col] = '.';
            }
        }
    }
    //把二维棋盘变为一维数组
    public List<String> Array2List(char[][] chessboard){
        List<String> path = new ArrayList<>();
        for (char[] c:chessboard){//注意此格式
            path.add(String.copyValueOf(c));
        }
        return path;
    }
    //判断是否合法 已知棋子的行和列
    public boolean isValid(int n,int row,int col,char[][] chessboard){
//列:每一列不能出现相同的  如果该列已经存在Q了 那么将不能再放Q 即固定列 从第0行开始遍历到row行 不能出现Q
        for(int i = 0; i < row; i++){
           if(chessboard[i][col] == 'Q'){
               return false;
           } 
        }
//检查45对角线:检查 row行 col列左上角 不能存在Q
        for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
 // 检查135度对角线 右上角
        for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }

37.解数独

在这里插入图片描述
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
回溯函数返回值必须为boolean

class Solution {
    public void solveSudoku(char[][] board) {
        backTracking(board);
    }
    public boolean backTracking(char[][] board) {
        for(int i =0;i<9;i++){//行
            for(int j=0;j<9;j++){//列遍历
                if(board[i][j] != '.'){continue;}
                for (char c ='1';c<='9';c++){
                    if (isVailid(i,j,c,board)){
                        board[i][j] = c;
                        if(backTracking(board)) {return true;}
                        board[i][j] = '.';
                    }
                }
                return false;
            }              
        }
        return true;
    }
    public boolean isVailid(int i,int j,char c,char[][] board){
//行:重复
        for(int col=0;col<9;col++){
            if (board[i][col] == c){
                return false;
            }
        }
//列:重复
        for (int row=0;row<9;row++){
            if (board[row][j] == c){
                return false;
            }
        }
//九宫格
        int startRow = (i / 3) * 3;
        int startCol = (j / 3) * 3;
        for (int p = startRow; p <= startRow+2; p++){
            for (int q = startCol; q<=startCol+2;q++){
                if(board[p][q] == c){
                    return false;
                }
            }
        }
        return true;
    }
}

六、其他问题

491. 递增子序列

在这里插入图片描述
在这里插入图片描述
(1)if终止条件:path大于2
(2)for循环从startIndex开始
(3)去重 用hashmap (难点在于 同一层如何去重)

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backTracking(nums,0);
        return result;
    }
    public void backTracking(int[] nums,int startIndex){
        if (path.size() >= 2){
            result.add(new ArrayList<>(path));
        }
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for (int i = startIndex;i<nums.length;i++){
            if ( !path.isEmpty() && nums[i] < path.getLast()){
                continue;   // nums[i] 必须比path现有最后一个元素大
            }
            if (map.containsValue(nums[i]) ){
                continue;
            }
            path.add(nums[i]);
            map.put(i,nums[i]);
            backTracking(nums,i+1);
            path.removeLast();
        }
    }
}

332.重新安排行程

在这里插入图片描述
问题:

  1. 一个行程中,如果航班处理不好容易变成一个圈,成为死循环
    用used[] 记录已经使用过的机票
  2. 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢 ?
    Collections.sort(tickets, (a,b) -> a.get(1).compareTo(b.get(1))) 只要对机票的目的地排序即可
  3. 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
    path.size = 机票数+1
  4. 搜索的过程中,如何遍历一个机场所对应的所有机场。
    机票的第一个元素 == path的最后一个元素
  5. 回溯函数返回值为boolean,路径唯一
class Solution {
    LinkedList<String> path = new LinkedList<>();
    LinkedList<String> result;
    public List<String> findItinerary(List<List<String>> tickets) {
        Collections.sort(tickets, (a,b) -> a.get(1).compareTo(b.get(1)));//排序 按字母排序 写法复杂
        path.add("JFK"); //起点必然为JFK
        boolean[] used = new boolean[tickets.size()];  //记录已经用过的机票
        backTracking(tickets,used);
        return result;
    }
    public boolean backTracking(List<List<String>> tickets,boolean[] used){
        if (path.size() == tickets.size()+1){
            result = new LinkedList(path);
            return true;
        }
        for(int i = 0; i < tickets.size();i++){
            if (!used[i] && tickets.get(i).get(0).equals(path.getLast())){
                path.add(tickets.get(i).get(1));//
                used[i] = true;
                if (backTracking(tickets,used)){
                    return true;
                }
                used[i] = false;
                path.removeLast();

            }
        }
        return false;
    } 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值