回溯算法大汇总

本文详细介绍了回溯算法在排列、组合、子集等信息技术问题中的应用,包括全排列、组合总和、子集问题以及第k个排列等经典题目。通过实例解析了如何使用回溯算法解决这类问题,强调了剪枝技巧、状态变量设计和避免重复的重要性,并提供了相应的Java代码实现。此外,还探讨了泛洪填充、字符串回溯等问题,展示了回溯算法在不同场景下的灵活运用。
摘要由CSDN通过智能技术生成

第一 排列、组合、子集问题

提示:这部分练习可以帮助我们熟悉「回溯算法」的一些概念和通用的解题思路。解题的步骤是:先画图,再编码。去思考可以剪枝的条件, 为什么有的时候用 used 数组,有的时候设置搜索起点 begin 变量,理解状态变量设计的想法。

  • 46.全排列(中等)
  • 47.全排列 II(中等):思考为什么造成了重复,如何在搜索之前就判断这一支会产生重复;
  • 39.组合总和(中等)
  • 40.组合总和 II(中等)
  • 77.组合(中等)
  • 78.子集(中等)
  • 90.子集 II(中等):剪枝技巧同 47 题、39 题、40 题;
  • 60.第 k 个排列(中等):利用了剪枝的思想,减去了大量枝叶,直接来到需要的叶子结点;
  • 93.复原 IP 地址(中等)

46.全排列(中等)

这里是引用

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        if(nums.length==0)return null;
        List<Integer> temp = new ArrayList<>();
        dfs(0,nums,temp);
        return list;
    }
    void dfs(int k,int[] nums,List<Integer> temp){
        if(temp.size()==nums.length){
            list.add(new ArrayList<>(temp));
            return;
        }
        // 注意这里的 起始点 i 要等于0 这里是从头算
        for(int i=0;i<nums.length;i++){
            if(temp.contains(nums[i]))continue;
            temp.add(nums[i]);
            dfs(i+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }
}

47.

在这里插入图片描述

class Solution {
    public List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums.length == 0){
            return result;
        }
        Arrays.sort(nums);
        findUnique(nums,new boolean[nums.length],new LinkedList<Integer>());
        return result;
    }
    public void findUnique(int[] nums, boolean[] visited,List<Integer> temp){
        //结束条件
        if(temp.size() == nums.length){
            result.add(new ArrayList<>(temp));
            return ;
        }
        //选择列表
        for(int i = 0; i<nums.length; i++){
            //已经选择过的不需要再放进去了
            if(visited[i]) continue;
            //去重
            if(i>0 && nums[i] == nums[i-1] && visited[i-1]) break;
            
            temp.add(nums[i]);
            visited[i] = true;
 
            findUnique(nums,visited,temp);
 
            temp.remove(temp.size()-1);
            visited[i] = false;
        }
    }
}

39.

在这里插入图片描述

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if(candidates.length == 0 || target < 0){
            return list;
        }
        List<Integer> temp = new ArrayList<>();
        backTrace(0,candidates,target,temp);
        return list;
    }
 
    public void backTrace(int start, int[] candidates, int target, List<Integer> temp){
        //递归的终止条件
        if (target < 0) {
            return;
        }
 
        if(target == 0){
            list.add(new ArrayList<>(temp));
        } 
 
        for(int i = start; i < candidates.length; i++){
            temp.add(candidates[i]);
            backTrace(i,candidates,target-candidates[i], temp);
            temp.remove(temp.size()-1);
        }
    }
}

这个时候需要注意一点 就是首先 这个dfs是带着参数k进行往下遍历的 如果不带着k 就会出现下面的输出情况 和不带参数是一个样子
这里是引用

在这里面 就会出现重复排列 即223 和 232 是同一个
这与全排列的概念不一样

第二个需要注意的点就是 在for循环中下一次dfs的起始位置 是i还是i+1!!!

如果是i+1 就会返回如下内容
在这里插入图片描述

40.

在这里插入图片描述

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates.length==0 || target<0)return null;
        Arrays.sort(candidates);
        List<Integer> temp = new ArrayList<>();
        dfs(0,candidates,temp,target);
        return list;
    }

    void dfs(int k,int[] candidates,List<Integer> temp,int target){
        if(target<0)return;
        if(target==0){
            list.add(new ArrayList<>(temp));
        }
        for(int i=k;i<candidates.length;i++){
            if(i>k && candidates[i]==candidates[i-1])continue;
            temp.add(candidates[i]);
            dfs(i+1,candidates,temp,target-candidates[i]);
            temp.remove(temp.size()-1);
        }
    }
   

}

本题首先注意!题目中含有重复的元素 所以先sort
其次是 同一个数字只能被使用一次
所以需要去重操作
if(i>k&&nums[i]==nums[i-1])continue;
这句话是关键 而且要注意 是i>k 不是>0!!!

这里是引用

77.

在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        List<Integer> temp = new ArrayList<>();
        dfs(1,n,k,temp);
        return res;
    }
    void dfs(int j,int n,int k,List<Integer> temp){
        if(temp.size()==k){
            res.add(new ArrayList(temp));
            return;
        }
        for(int i=j;i<=n;i++){
            temp.add(i);
            dfs(i+1,n,k,temp);
            temp.remove(temp.size()-1);
        }
    }
}

78.

在这里插入图片描述

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length == 0)return null;
        List<Integer> temp = new ArrayList<>();
        dfs(0,nums,temp);
        return list;
    }
    void dfs(int k, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        for(int j= k;j<nums.length;j++){
            temp.add(nums[j]);
            dfs(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }
}

90.

在这里插入图片描述

class Solution {
    // List<List<Integer>> list = new ArrayList<>();
    // Set<List<Integer>> set = new HashSet<>();
    // public List<List<Integer>> subsetsWithDup(int[] nums) {
    //     if(nums.length==0)return null;
    //     Arrays.sort(nums);
    //     List<Integer> temp = new ArrayList<>();
    //     dfs(0,nums,temp);
    //     return list;
    // }

    // void dfs(int k,int[] nums,List<Integer> temp){
    //     if(!set.contains(temp)){
    //         set.add(new ArrayList<>(temp));
    //         list.add(new ArrayList<>(temp));
    //     }
    //     for(int j=k;j<nums.length;j++){
    //         temp.add(nums[j]);
    //         dfs(j+1,nums,temp);
    //         temp.remove(temp.size()-1);
    //     }
    // }
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        Arrays.sort(nums);
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }
 
    public void backTrace(int start, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        
        for(int i = start; i < nums.length; i++){
            //剪枝策略
            if(i > start && nums[i] == nums[i-1]){
                continue;
            }
            temp.add(nums[i]);
            backTrace(i+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

}

60.排列序列

在这里插入图片描述

import java.util.Arrays;

public class Solution {

    /**
     * 记录数字是否使用过
     */
    private boolean[] used;

    /**
     * 阶乘数组
     */
    private int[] factorial;

    private int n;
    private int k;

    public String getPermutation(int n, int k) {
        this.n = n;
        this.k = k;
        calculateFactorial(n);

        // 查找全排列需要的布尔数组
        used = new boolean[n + 1];
        Arrays.fill(used, false);

        StringBuilder path = new StringBuilder();
        dfs(0, path);
        return path.toString();
    }


    /**
     * @param index 在这一步之前已经选择了几个数字,其值恰好等于这一步需要确定的下标位置
     * @param path
     */
    private void dfs(int index, StringBuilder path) {
        if (index == n) {
            return;
        }

        // 计算还未确定的数字的全排列的个数,第 1 次进入的时候是 n - 1
        int cnt = factorial[n - 1 - index];
        for (int i = 1; i <= n; i++) {
            if (used[i]) {
                continue;
            }
            if (cnt < k) {
                k -= cnt;
                continue;
            }
            path.append(i);
            used[i] = true;
            dfs(index + 1, path);
            // 注意 1:不可以回溯(重置变量),算法设计是「一下子来到叶子结点」,没有回头的过程
            // 注意 2:这里要加 return,后面的数没有必要遍历去尝试了
            return;
        }
    }

    /**
     * 计算阶乘数组
     *
     * @param n
     */
    private void calculateFactorial(int n) {
        factorial = new int[n + 1];
        factorial[0] = 1;
        for (int i = 1; i <= n; i++) {
            factorial[i] = factorial[i - 1] * i;
        }
    }
}

93.复原IP地址

在这里插入图片描述

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Stack;

public class Solution {

    public List<String> restoreIpAddresses(String s) {
        int len = s.length();
        List<String> res = new ArrayList<>();
        // 如果长度不够,不搜索
        if (len < 4 || len > 12) {
            return res;
        }

        Deque<String> path = new ArrayDeque<>(4);
        int splitTimes = 0;
        dfs(s, len, splitTimes, 0, path, res);
        return res;
    }

    /**
     * 判断 s 的子区间 [left, right] 是否能够成为一个 ip 段
     * 判断的同时顺便把类型转了
     *
     * @param s
     * @param left
     * @param right
     * @return
     */
    private int judgeIfIpSegment(String s, int left, int right) {
        int len = right - left + 1;

        // 大于 1 位的时候,不能以 0 开头
        if (len > 1 && s.charAt(left) == '0') {
            return -1;
        }

        // 转成 int 类型
        int res = 0;
        for (int i = left; i <= right; i++) {
            res = res * 10 + s.charAt(i) - '0';
        }

        if (res > 255) {
            return -1;
        }
        return res;
    }

    private void dfs(String s, int len, int split, int begin, Deque<String> path, List<String> res) {
        if (begin == len) {
            if (split == 4) {
                res.add(String.join(".", path));
            }
            return;
        }

        // 看到剩下的不够了,就退出(剪枝),len - begin 表示剩余的还未分割的字符串的位数
        if (len - begin < (4 - split) || len - begin > 3 * (4 - split)) {
            return;
        }

        for (int i = 0; i < 3; i++) {
            if (begin + i >= len) {
                break;
            }

            int ipSegment = judgeIfIpSegment(s, begin, begin + i);
            if (ipSegment != -1) {
                // 在判断是 ip 段的情况下,才去做截取
                path.addLast(ipSegment + "");
                dfs(s, len, split + 1, begin + i + 1, path, res);
                path.removeLast();
            }
        }
    }
}

题型2、泛洪填充

733. 图像渲染

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

class Solution {
    int[][] image;
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        this.image = image;
        int m = image.length;
        int n = image[0].length;
        boolean[][] vis = new boolean[m][n];
        int flag = image[sr][sc];
        dfs(image,sr,sc,vis,color,flag);
        return image;
    }
    void dfs(int[][] image,int sr,int sc,boolean[][] vis,int color,int flag){
        if(sr<0||sr>=image.length||sc<0||sc>=image[0].length||vis[sr][sc]||image[sr][sc]!=flag)return;
        image[sr][sc] = color;
        vis[sr][sc]=true;
        dfs(image,sr+1,sc,vis,color,flag);
        dfs(image,sr-1,sc,vis,color,flag);
        dfs(image,sr,sc+1,vis,color,flag);
        dfs(image,sr,sc-1,vis,color,flag);
    }
}

200.岛屿数量

在这里插入图片描述

class Solution {
    public int numIslands(char[][] grid) {
        int count =0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    dfs(grid,i,j);
                    count++;
                }
            }
        }
        return count;
    }
    void dfs(char[][] grid, int i,int j){
        if(i<0 || j<0 || i>=grid.length || j>=grid[0].length || grid[i][j]=='0')return;
        grid[i][j] = '0';
        dfs(grid,i+1,j);
        dfs(grid,i-1,j);
        dfs(grid,i,j+1);
        dfs(grid,i,j-1);
    }
}

130. 被围绕的区域

在这里插入图片描述

class Solution {
    public void solve(char[][] board) {
        if (board == null || board.length == 0) return;
        int m = board.length;
        int n = board[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 从边缘o开始搜索
                boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1;
                if (isEdge && board[i][j] == 'O') {
                    dfs(board, i, j);
                }
            }
        }

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                }
                if (board[i][j] == '#') {
                    board[i][j] = 'O';
                }
            }
        }
    }

    public void dfs(char[][] board, int i, int j) {
        if (i < 0 || j < 0 || i >= board.length  || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') {
            // board[i][j] == '#' 说明已经搜索过了. 
            return;
        }
        board[i][j] = '#';
        dfs(board, i - 1, j); // 上
        dfs(board, i + 1, j); // 下
        dfs(board, i, j - 1); // 左
        dfs(board, i, j + 1); // 右
    }
}

79. 单词搜索

在这里插入图片描述

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

题型三、字符串中的回溯问题

17.电话号码字母组合

在这里插入图片描述

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if(digits == "")return res;
        Map<Character,String> map = new HashMap<>();
        map.put('2', "abc");
        map.put('3', "def");
        map.put('4', "ghi");
        map.put('5', "jkl");
        map.put('6', "mno");
        map.put('7', "pqrs");
        map.put('8', "tuv");
        map.put('9', "wxyz");
        backtrack(res,map,digits,0,new StringBuilder());
        return res;
    }
    void backtrack(List<String> res,Map<Character,String> map,String digits,int i,StringBuilder temp){
        if(i==digits.length()){
            res.add(temp.toString());
        }else{
            char d = digits.charAt(i);
            String letter = map.get(d);
            for(int j=0;j<letter.length();j++){
                temp.append(letter.charAt(j));
                backtrack(res,map,digits,i+1,temp);
                temp.deleteCharAt(i);
            }
        }
        
    }
}

784. 字母大小写全排列

在这里插入图片描述

class Solution {
    private List<String> res = new ArrayList<>();
    public List<String> letterCasePermutation(String s) {
        char[] ch = s.toCharArray();
        int n = ch.length;
        backTracking(ch, 0, n);
        return res;
    }

    private void backTracking(char[] ch, int index, int n) {
        res.add(new String(ch)); 
        for (int i = index; i < n; i++) {
            if (isDigit(ch[i])) {
                continue;
            }
            ch[i] ^= 32;
            backTracking(ch, i + 1, n);
            ch[i] ^= 32;
        }
    }

    private boolean isDigit(char c) {
        if (c >= '0' && c <= '9') {
            return true;
        }
        return false;
    }
}

21.括号生成

在这里插入图片描述

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        if(n==0)return res;
        dfs("",n,n,res);
        return res;
    }
    void dfs(String curStr,int left,int right,List<String> res){
        if(left==0&&right==0){
            res.add(curStr);
            return;
        }
        if(left>right)return;
        if(left>0){
            dfs(curStr+"(",left-1,right,res);
        }
        if(right>0)dfs(curStr+")",left,right-1,res);
    }
}

51. n皇后问题

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

class Solution {

    // 符合条件的单个结果(路径)
    private List<String> path = new LinkedList<>();

    // 符合条件的结果集合
    private List<List<String>> ans = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {
        // 新建字符棋盘,相比操作字符串,更方便
        char[][] board = new char[n][n];
        // 初始化棋盘
        for (int i=0; i<board.length; ++i) {
            Arrays.fill(board[i], '.');
        }

        // 从 row=0 第一行开始遍历
        backtracking(n, 0, board);
        return ans;
    }

    private void backtracking(int n, int row, char[][] board) {
        // 结束条件:所有行都遍历完成,注意是n不是n-1
        if (row == n) {
            path = array2List(board);
            ans.add(path);
            return;
        }

        // 遍历选择列表,这里的一个选择就是 row+col
        for (int col=0; col<n; ++col) {
            if (!isValid(n, row, col, board)) {
                // 排除不合法的选择(皇后存在冲突)
                continue;
            }

            // 做选择
            board[row][col] = 'Q';
            // 递归调用,进入下一行的决策
            backtracking(n, row+1, board);
            // 撤销选择
            board[row][col] = '.';
        }
    }

    /**
     * 将符合条件的某个棋盘数组转换成字符串列表
     */
    private List<String> array2List(char[][] board) {
        List<String> ans = new ArrayList<>();
        for (int i=0; i<board.length; ++i) {
            ans.add(String.valueOf(board[i]));
        }
        return ans;
    }

    /**
     * 判断是否可以在当前棋盘 board[row][col] 这个位置放置皇后Q
     * n是棋盘的大小,避免重复计算,所以作为参数传入
     */
    private boolean isValid(int n, int row, int col, char[][] board) {
        // 检查board[row][col]这个位置所在这一列正上方中,看是否已经存在 Q,存在说明列存在冲突,不能再放置皇后Q
        for (int i=0; i<row; ++i) {
            if (board[i][col] == 'Q') {
                return false;
            }
        }

        // 检查board[row][col]这个位置左上方对角线是否已经存在皇后,左下方不用检查,因为backtracking函数是一行一行遍历的,下方的还没遍历到呢
        for (int i=row-1,j=col-1; i>=0 && j>=0; --i,--j) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        // 检查board[row][col]这个位置右上方对角线是否已经存在皇后,右下方不用检查,因为 backtracking函数是一行一行遍历的,下方的还没遍历到呢
        for (int i=row-1,j=col+1; i>=0 && j<n; --i,++j) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值