Plantre Plantre
首页
  • 算法

    • 查找
    • 排序
  • 力扣

    • 排序
技术
硬件
逆向
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 技术

    • 技术文档
    • GitHub技巧
    • Nodejs
    • 博客搭建
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

plantre

一个后端开发者
首页
  • 算法

    • 查找
    • 排序
  • 力扣

    • 排序
技术
硬件
逆向
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 技术

    • 技术文档
    • GitHub技巧
    • Nodejs
    • 博客搭建
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

  • 计算机网络

  • 设计模式

  • Java

  • Spring

  • SpringCloud

  • MySQL

  • Redis

  • 分布式

  • Zookeeper

  • Dubbo

  • Kafka

  • 数据结构

  • 算法

  • OJ

    • 自定义
    • 排序
    • 位运算
    • 二分查找
    • 递归
    • 双指针(逆向,快慢)
    • 滑动窗口
    • 辅助栈
    • 贪心
    • 回溯
      • 回溯法和DFS什么关系
        • 22. 括号生成 - 力扣(LeetCode)
        • 93. 复原 IP 地址 - 力扣(LeetCode)
        • 51. N 皇后 - 力扣(LeetCode)
      • 元素无重不可复选
        • 78. 子集 - 力扣(LeetCode)
        • 77. 组合 - 力扣(LeetCode)
        • 46. 全排列 - 力扣(LeetCode)
      • 元素可重不可复选(排序+剪枝)
        • 90. 子集 II - 力扣(LeetCode)
        • 40. 组合总和 II - 力扣(LeetCode)
        • 47. 全排列 II - 力扣(LeetCode)
      • 元素无重可复选( i + 1 ---> i )
        • 39. 组合总和 - 力扣(LeetCode)
    • 动态规划
    • 二叉树
    • DFS
    • 拼接&拆分
    • 模拟
    • 工业实现原理
    • 数学
    • 字符串
  • 从道家哲学看计算机?
  • 后端
  • OJ
plantre
2025-06-11
目录

回溯

# 回溯法和DFS什么关系

def dfs(node, visited):
    if node in visited:  # 仅访问未遍历节点
        return
    visited.add(node)
    for neighbor in node.neighbors:
        dfs(neighbor, visited)  # 无状态重置操作
        
def backtrack(path, used, nums, res):
    if len(path) == len(nums):  # 终止条件:找到解
        res.append(path.copy())
        return
    for i in range(len(nums)):
        if used[i]: continue    # 剪枝:跳过已用元素
        used[i] = True          # 记录状态
        path.append(nums[i])
        backtrack(path, used, nums, res)  # 递归探索
        path.pop()              # 回溯:撤销选择
        used[i] = False         # 回溯:重置状态
        
        
​​所有回溯法都是DFS的应用,但并非所有DFS都需回溯机制​​。实际应用中,回溯法的“选择→递归→撤销”模板(见)是解决组合优化问题的核心范式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 22. 括号生成 - 力扣(LeetCode) (opens new window)

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        backtrack(res, new StringBuilder(), 0, 0, n);
        return res;
    }
    /**
        List<String> res 存储所有合法的括号组合结果集
        StringBuilder cur 动态记录当前递归路径下已生成的括号序列
        int open 当前路径中​​已使用的左括号数量
        int close 当前路径中​​已使用的右括号数量
        int max 目标括号​​总对数​​(如生成 n=3 对括号时,max=3)
     */
    private void backtrack(List<String> res, StringBuilder cur, int open, int close, int max) {
        // 收集结果
        if (cur.length() == 2 * max) {
            res.add(cur.toString());
            return;
        }
        if (open < max) {
            cur.append("(");
            backtrack(res, cur, open + 1, close, max);
            cur.deleteCharAt(cur.length() - 1); // 回溯
        }
        if (close < open) {
            cur.append(")");
            backtrack(res, cur, open, close + 1, max);
            cur.deleteCharAt(cur.length() - 1); // 回溯
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 93. 复原 IP 地址 - 力扣(LeetCode) (opens new window)

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> res = new ArrayList<>();
        backtrack(s, 0, new ArrayList<>(), res);
        return res;
    }

    /**
    s 原字符串
    begin 将要分割的位置(begin, begin + len)为下一选择要分割的字符串
    path 记录当前递归路径下已做出的选择序列
    res 结果
     */
    private void backtrack(String s, int begin, List<String> path, List<String> res) {
        // 终止条件:4段且用尽字符
        if (path.size() == 4) { // 为4段
            if (begin == s.length())  // 并且字符刚好被分割完
                res.add(String.join(".", path));
            return;
        }
        // 剪枝1:剩余字符数不合法
         int remainSegments = 4 - path.size();
         if (s.length() - begin > remainSegments * 3 || s.length() - begin < remainSegments) return;

        // 尝试截取1-3位
         for (int len = 1; len <= 3 && begin + len <= s.length(); len++) {
            
            String segment = s.substring(begin, begin + len);
             // 剪枝2:前导零或数值超范围
            if ((segment.startsWith("0") && segment.length() > 1) || Integer.parseInt(segment) > 255) 
                continue;
            path.add(segment);              // 选择当前段
            backtrack(s, begin + len, path, res); // 递归后续
            path.remove(path.size() - 1); // 撤销选择
         }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 51. N 皇后 - 力扣(LeetCode) (opens new window)

class Solution {

    private List<List<String>> res = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {
        // 每个字符串代表一行,字符串列表代表一个棋盘
        // '.' 表示空,'Q' 表示皇后,初始化空棋盘
        List<String> board = new ArrayList<>();
        for (int i = 0;i < n; i++) {
            board.add(".".repeat(n));
        }
        backtrack(board, 0);
        return res;
    }
    // 结束条件:row 超过 board 的最后一行
    private void backtrack(List<String> board, int row) {
        // 结束条件:row 超过 board 的最后一行
        if (row == board.size()) {
            res.add(new ArrayList<>(board));
            return;
        }
        int n = board.get(row).length();
        for (int col = 0; col < n; col++) {
            if (!isValid(board,row,col)) {
                continue;
            }
            // 做选择
            char[] rowChars = board.get(row).toCharArray();
            rowChars[col] = 'Q';
            board.set(row, new String(rowChars));
            // 进入下一行决策
            backtrack(board, row + 1);
            // 撤销选择
            rowChars[col] = '.';
            board.set(row, new String(rowChars));
        }
    }
    // 是否可以在 board[row][col] 放置皇后?
    private boolean isValid(List<String> board, int row, int col) {
        int n = board.size();
        // 检查列是否有皇后互相冲突
        for (int i =0; i <= row; i++) {
            if (board.get(i).charAt(col) == 'Q') {
                return false;
            }
        }
        // 检查右上方是否有皇后互相冲突
        for (int i = row - 1,j = col + 1; i >= 0 && j < n; i--,j++) {
            if (board.get(i).charAt(j) == 'Q') {
                return false;
            }
        }
        // 检查左上方是否有皇后互相冲突
        for (int i = row - 1,j = col - 1; i >= 0 && j >= 0; i--,j--) {
            if (board.get(i).charAt(j) == 'Q') {
                return false;
            }
        }
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 元素无重不可复选

# 78. 子集 - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtrack(nums, 0);
        return res;
    }

    void backtrack(int[] nums, int start) {
        // 前序位置,每个节点的值都是一个子集
        res.add(new LinkedList<>(track));
        // 回溯算法标准框架
        for (int i = start; i < nums.length; i++) {
            //做选择
            track.addLast(nums[i]);
            // 通过 start 参数控制树枝的遍历,避免产生重复的子集
            backtrack(nums,i + 1);
            // 撤销选择
            track.removeLast();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 77. 组合 - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtrack(1, n, k);
        return res;
    }

    void backtrack(int start, int n, int k) {
        // 前序位置,每个节点的值都是一个子集
        if (k == track.size()) {
            //遍历到了第 k 层,收集当前节点的值
            res.add(new LinkedList<>(track));
            return;
        }
        // 回溯算法标准框架
        for (int i = start; i <= n; i++) {
            //做选择
            track.addLast(i);
            // 通过 start 参数控制树枝的遍历,避免产生重复的子集
            backtrack(i + 1, n, k);
            // 撤销选择
            track.removeLast();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 46. 全排列 - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    // track 中的元素会被标记为 true
    boolean[] used;

    // 主函数,输入一组不重复的数字,返回它们的全排列
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backtrack(nums);
        return res;
    }

    // 回溯算法核心函数
    void backtrack(int[] nums) {
        // base case,到达叶子节点
        if (track.size() == nums.length) {
            // 收集叶子节点上的值
            res.add(new LinkedList(track));
            return;
        }

        // 回溯算法标准框架
        for (int i = 0; i < nums.length; i++) {
            // 已经存在 track 中的元素,不能重复选择
            if (used[i]) {
                continue;
            }
            // 做选择
            used[i] = true;
            track.addLast(nums[i]);
            // 进入下一层回溯树
            backtrack(nums);
            // 取消选择
            track.removeLast();
            used[i] = false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 元素可重不可复选(排序+剪枝)

# 90. 子集 II - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 先排序,让相同的元素靠在一起
        Arrays.sort(nums);
        backtrack(nums, 0);
        return res;
    }

    void backtrack(int[] nums, int start) {
        // 前序位置,每个节点的值都是一个子集
        res.add(new LinkedList<>(track));
        // 回溯算法标准框架
        for (int i = start; i < nums.length; i++) {
            // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
            if (i > start && nums[i] == nums[i - 1]) {
                continue;
            }
            //做选择
            track.addLast(nums[i]);
            // 通过 start 参数控制树枝的遍历,避免产生重复的子集
            backtrack(nums,i + 1);
            // 撤销选择
            track.removeLast();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 40. 组合总和 II - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    // 记录 track 中的元素之和
    int trackSum = 0;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if (candidates.length == 0) {
            return res;
        }
        // 先排序,让相同的元素靠在一起
        Arrays.sort(candidates);
        backtrack(candidates, 0, target);
        return res;
    }

    void backtrack(int[] nums, int start, int target) {
        // base case,达到目标和,找到符合条件的组合
        if (trackSum == target) {
            res.add(new LinkedList<>(track));
            return;
        }
        // base case,超过目标和,直接结束
        if (trackSum > target) {
            return;
        }
        // 回溯算法标准框架
        for (int i = start; i < nums.length; i++) {
            // 剪枝逻辑,值相同的树枝,只遍历第一条
            if (i > start && nums[i] == nums[i - 1]) {
                continue;
            }
            //做选择
            track.addLast(nums[i]);
            trackSum += nums[i];
            // 递归遍历下一层回溯树
            backtrack(nums, i + 1, target);
            // 撤销选择
            track.removeLast();
            trackSum -= nums[i];
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 47. 全排列 II - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    // track 中的元素会被标记为 true
    boolean[] used;

    // 主函数,输入一组不重复的数字,返回它们的全排列
    public List<List<Integer>> permuteUnique(int[] nums) {
        // 先排序,让相同的元素靠在一起
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backtrack(nums);
        return res;
    }

    // 回溯算法核心函数
    void backtrack(int[] nums) {
        // base case,到达叶子节点
        if (track.size() == nums.length) {
            // 收集叶子节点上的值
            res.add(new LinkedList(track));
            return;
        }

        // 回溯算法标准框架
        for (int i = 0; i < nums.length; i++) {
            // 已经存在 track 中的元素,不能重复选择
            if (used[i]) {
                continue;
            }
            // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            // 做选择
            used[i] = true;
            track.addLast(nums[i]);
            // 进入下一层回溯树
            backtrack(nums);
            // 取消选择
            track.removeLast();
            used[i] = false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 元素无重可复选( i + 1 ---> i )

# 39. 组合总和 - 力扣(LeetCode) (opens new window)

class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    // 记录 track 中的元素之和
    int trackSum = 0;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if (candidates.length == 0) {
            return res;
        }
        backtrack(candidates, 0, target);
        return res;
    }

    void backtrack(int[] nums, int start, int target) {
        // base case,达到目标和,找到符合条件的组合
        if (trackSum == target) {
            res.add(new LinkedList<>(track));
            return;
        }
        // base case,超过目标和,直接结束
        if (trackSum > target) {
            return;
        }
        // 回溯算法标准框架
        for (int i = start; i < nums.length; i++) {
            //做选择
            track.addLast(nums[i]);
            trackSum += nums[i];
            // 递归遍历下一层回溯树
            backtrack(nums, i , target);// 同一元素可重复使用,注意参数  i+1 ---> i
            // 撤销选择
            track.removeLast();
            trackSum -= nums[i];
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
编辑 (opens new window)
上次更新: 2025/07/16, 10:21:39
贪心
动态规划

← 贪心 动态规划→

最近更新
01
加油鸭
07-30
02
要点总结
07-28
03
字符串
07-15
更多文章>
Theme by Vdoing | Copyright © 2025-2025 plantre | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式