Day22-回溯,leetcode77,216,17

回溯

  • 回溯,通过试探性搜索来解决问题,采用试错的思想,逐步构建解决方案,当发现当前路径不能得到有效解时,就会退到上一步,尝试其他可能性。

  • 回溯核心:深度优先搜索 + 剪枝

  • 回溯到本质是穷举,穷举所有可能,然后选出我们想要的答案。

  • 回溯工作流程:

  1. 逐步构建候选解
  2. 验证当前部分解是否满足条件
  3. 如果满足则继续向下探索
  4. 如果不满足则回溯(撤销最近到选择)
  5. 重复上述过程直到找到所有解或遍历完所有可能性
  • 系统性:按一定顺序枚举所有可能性

  • 跳跃性:当发现不满足条件时,跳过该分支的后续搜索

  • 递归实现:通常用递归实现深度优先搜索

  • 解决问题

    • 组合问题,N个数里面按一定规则找出k个数的集合
    • 切割问题,一个字符串按一定规则有几种切割方式
    • 子集问题,一个N个数的集合里有多少符合条件的子集
    • 排列问题,N个数按一定规则全排列,有几种排列方式
    • 棋盘问题,N皇后,解数独等等
    • 图着色问题
  • 回溯三部曲

  1. 回溯函数模版返回值和参数
  2. 回溯函数终止条件
  3. 回溯搜索的遍历过程
// 回溯法模版
function backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}



题目

  1. 组合
  • 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

  • 你可以按 任何顺序 返回答案。

  • 思路

// 未剪枝
var combine = function (n, k) {
  // 回溯法
 // result,存放符合条件结果的集合
// path,用来存放符合条件结果
  let result = [],
    path = [];
// startIndex来记录下一层递归,搜索的起始位置
  let backtracking = (_n, _k, startIndex) => {
    // 终止条件
    if (path.length === _k) {
      result.push(path.slice());
      return;
    }
    // 循环本层集合元素
    for (let i = startIndex; i <= _n; i++) {
      path.push(i);
      //   递归
      backtracking(_n, _k, i + 1);
      //   回溯操作
      path.pop();
    }
  };
  backtracking(n, k, 1);
  return result;
};
var combine = function (n, k) {
  // 回溯法
  let result = [],
    path = [];
  let backtracking = (_n, _k, startIndex) => {
    // 终止条件
    if (path.length === _k) {
      result.push(path.slice());
      return;
    }
    // 循环本层集合元素
    for (let i = startIndex; i <= _n - (_k - path.length) + 1; i++) {
      path.push(i);
      //   递归
      backtracking(_n, _k, i + 1);
      //   回溯操作
      path.pop();
    }
  };
  backtracking(n, k, 1);
  return result;
};

  1. 组合总和 III
  • 找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9

  • 每个数字 最多使用一次

  • 返回所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

  • 思路

  • 回溯三部曲

  • (1)确定递归函数参数,backtracking(n, k, sum, startIndex)

  • (2)确定终止条件path.size === k

  • (3)单层搜索过程

// 未剪枝
var combinationSum3 = function (k, n) {
  // 回溯法
  let result = [],
    path = [];
  const backtracking = (_k, targetSum, sum, startIndex) => {
    // 终止条件
    if (path.length === _k) {
      if (sum === targetSum) {
        result.push(path.slice());
      }
      // 如果总和不相等,就直接返回
      return;
    }

    // 循环当前节点,因为只使用数字1到9,所以最大是9
    for (let i = startIndex; i <= 9; i++) {
      path.push(i);
      sum += i;
      // 回调函数
      backtracking(_k, targetSum, sum, i + 1);
      // 回溯
      sum -= i;
      path.pop();
    }
  };
  backtracking(k, n, 0, 1);
  return result;
};
// 剪枝
var combinationSum3 = function (k, n) {
  // 回溯法
  let result = [],
    path = [];
  const backtracking = (_k, targetSum, sum, startIndex) => {
    if (sum > targetSum) {
      return;
    }
    // 终止条件
    if (path.length === _k) {
      if (sum === targetSum) {
        result.push(path.slice());
      }
      // 如果总和不相等,就直接返回
      return;
    }

    // 循环当前节点,因为只使用数字1到9,所以最大是9
    for (let i = startIndex; i <= 9 - (_k - path.length) + 1; i++) {
      path.push(i);
      sum += i;
      // 回调函数
      backtracking(_k, targetSum, sum, i + 1);
      // 回溯
      sum -= i;
      path.pop();
    }
  };
  backtracking(k, n, 0, 1);
  return result;
};

  1. 电话号码的字母组合
  • 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
  • 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
// 用回溯法枚举所有数字对应字母的组合,递归构建路径,终止时加入结果。
// digits:输入的数字字符串(只包含2-9)。
var letterCombinations = function(digits) {
    const k = digits.length;
    // map:数字到字母的映射表(如2对应"abc")。
    const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
    if(!k) return []; // 如果输入为空,返回空数组。
    if(k === 1) return map[digits].split(""); // 如果只有一个数字,直接返回对应的字母数组。
// res:存放所有组合结果的数组。path:当前递归路径(正在拼接的字母组合)。
    const res = [], path = [];
    // 从第0位数字开始回溯,最终返回所有组合。
    backtracking(digits, k, 0);
    return res;

    function backtracking(n, k, a) {
        // 终止条件:当 path 长度等于输入数字长度 k,说明已经拼出一个完整组合,加入结果数组。
        if(path.length === k) {
            res.push(path.join(""));
            return;
        }
        for(const v of map[n[a]]) {
            // 遍历当前数字对应的所有字母,每选一个字母就递归进入下一位数字。
            path.push(v);
            backtracking(n, k, a + 1);
            // 回溯时撤销选择(path.pop()),尝试下一个字母。
            path.pop();
        }
    }
};



参考&感谢各路大神

posted @ 2025-06-18 17:52  安静的嘶吼  阅读(8)  评论(0)    收藏  举报