LeetCode题解回溯篇(TS版)

回溯三部曲

  1. 确定递归函数参数
  2. 确定终止条件
  3. 单层搜索过程

回溯算法能解决如下问题:

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

77.组合

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

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

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

递归实现组合型枚举

  • 时间复杂度: O ( ( n k ) ∗ k ) O(\binom{n}{k} *k) O((kn)k)
  • 空间复杂度: O ( n + k ) = O ( n ) O(n+k)=O(n) O(n+k)=O(n)
根据搜索起点画出二叉树
image.png
function combine(n: number, k: number): number[][] {
    const res: number[][] = [];
    let path: number[] = [];
    const backtracking = (begin: number) => {
        if (path.length === k) {
            res.push(path.slice());
            return;
        }
        for (let i = begin; i <= n; i++) {
            path.push(i);
            backtracking(i + 1);
            path.pop(); // 回溯
        }
    }
    backtracking(1);
    return res;
};
优化:分析搜索起点的上界进行剪枝

如果 n = 7, k = 4从 5 开始搜索就已经没有意义了,这是因为:即使把 5 选上,后面的数只有 6 和 7,一共就 3 个候选数,凑不出 4 个数的组合。因此,搜索起点有上界

image.png image.png

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

归纳出:

搜索起点的上界 + 接下来要选择的元素个数 - 1 = n

其中,接下来要选择的元素个数 = k - path.size(),整理得到:

搜索起点的上界 = n - (k - path.size()) + 1
function combine(n: number, k: number): number[][] {
    const res: number[][] = [];
    let path: number[] = [];
    const backtracking = (begin: number) => {
        if (path.length === k) {
            res.push(path.slice());
            return;
        }
        for (let i = begin; i <= n - (k - path.length) + 1; i++) {
            path.push(i);
            backtracking(i + 1);
            path.pop(); // 回溯
        }
    }
    backtracking(1);
    return res;
};

216.组合总和Ⅲ

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

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

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

递归+for循环实现枚举

image.png
未剪枝
function combinationSum3(k: number, n: number): number[][] {
    if (k > n) return [];
    let res: number[][] = [];
    let path: number[] = [];
    const dfs = (begin: number, sum: number) => {
        if (path.length === k) {
            if (sum === n) {
                res.push(path.slice());
            }
            return;
        }

        for (let i = begin; i < 10; i++) {
            path.push(i);
            sum += i;
            dfs(i + 1, sum);
            sum -= i; // 回溯
            path.pop(); // 回溯
        }
    }
    dfs(1, 0);
    return res;
};
剪枝
216.组合总和III1
function combinationSum3(k: number, n: number): number[][] {
    if (k > n) return [];
    let res: number[][] = [];
    let path: number[] = [];
    const dfs = (begin: number, sum: number) => {
        if (sum > n) return; // 剪枝
        if (path.length === k) {
            if (sum === n) {
                res.push(path.slice());
            }
            return;
        }

        for (let i = begin; i < 10; i++) {
            path.push(i);
            sum += i;
            dfs(i + 1, sum);
            sum -= i; // 回溯
            path.pop(); // 回溯
        }
    }
    dfs(1, 0);
    return res;
};

17.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

img

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

回溯

iShot_2023-06-20_14.29.01 17. 电话号码的字母组合
  • 时间复杂度: O ( 3 m × 4 n ) O(3^m \times 4^n) O(3m×4n),其中 m 是输入中对应3个字母的数字个数,n 是输入中对应4个字母的数字个数,m+n 是输入数字的总个数。当输入包含m个对应3个字母的数字和n个对应4个字母的数字时,不同的字母组合一共有 3 m × 4 n 3^m \times 4^n 3m×4n 种,需要遍历每一种字母组合。
  • 空间复杂度: O ( m + n ) O(m+n) O(m+n),空间复杂度主要取决于哈希表以及回溯过程中的递归调用层数,哈希表的大小与输入无关,可以看成常数,递归调用层数最大为 m + n m+n m+n
function letterCombinations(digits: string): string[] {
    if (digits.length === 0) return [];
    const digitsMap = {
        '2': ['a', 'b', 'c'],
        '3': ['d', 'e', 'f'],
        '4': ['g', 'h', 'i'],
        '5': ['j', 'k', 'l'],
        '6': ['m', 'n', 'o'],
        '7': ['p', 'q', 'r', 's'],
        '8': ['t', 'u', 'v'],
        '9': ['w', 'x', 'y', 'z']
    }
    const res: string[] = [];
    const backtracking = (index: number, combination: string) => {
        if (index === digits.length) {
            res.push(combination);
            return;
        }
        const letters: string[] = digitsMap[digits[index]];
        for (let i = 0; i < letters.length; i++) {
            combination += letters[i];
            backtracking(index + 1, combination);
            combination = combination.slice(0, -1); // 回溯
        }
    }
    backtracking(0, '');
    return res;
};

39.组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

回溯

img
  • 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n2n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
  • 空间复杂度: O ( t a r g e t ) O(target) O(target)
function combinationSum(candidates: number[], target: number): number[][] {
    let res: number[][] = [];
    let path: number[] = [];
    let backtracking = (sum: number, begin: number) => {
        if (sum > target) return; // 剪枝
        if (sum === target) {
            res.push(path.slice());
            return;
        }
        for (let i = begin; i < candidates.length; i++) {
            sum += candidates[i];
            path.push(candidates[i]);
            backtracking(sum, i);
            path.pop(); // 回溯
            sum -= candidates[i]; // 回溯
        }
    }
    backtracking(0, 0);
    return res;
};

40.组合总和Ⅱ

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

**注意:**解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

回溯+排序+数组记录

img
function combinationSum2(candidates: number[], target: number): number[][] {
    candidates.sort((a, b) => a - b);
    const res: number[][] = [];
    const path: number[] = [];
    const used: boolean[] = new Array(candidates.length).fill(false);
    const backtracking = (sum: number, begin: number) => {
        if (sum > target) return; // 剪枝
        if (sum === target) {
            res.push(path.slice());
        }
        for (let i = begin; i < candidates.length; i++) {
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] === false) {
                continue;
            }
            path.push(candidates[i]);
            sum += candidates[i];
            used[i] = true;
            backtracking(sum, i + 1);
            path.pop(); // 回溯
            sum -= candidates[i]; // 回溯
            used[i] = false; // 回溯
        }
    }
    backtracking(0, 0);
    return res;
};

131.分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

回溯

131.分割回文串
  • 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n2n)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)
function isPalindrome(s: string, start: number, end: number) {
    for (let i = start, j = end; i < j; i++, j--) {
        if (s[i] !== s[j]) {
            return false;
        }
    }
    return true;
}

function partition(s: string): string[][] {
    const res: string[][] = [];
    const path: string[] = [];
    const backtracking = (begin: number) => {
        if (begin >= s.length) {
            res.push(path.slice());
            return;
        }
        for (let i = begin; i < s.length; i++) {
            if (isPalindrome(s, begin, i)) {
                const str = s.slice(begin, i + 1);
                path.push(str);
            } else { // 剪枝
                continue;
            }
            backtracking(i + 1);
            path.pop(); // 回溯
        }
    }
    backtracking(0);
    return res;
};

回溯+动态规划

待补充

93.复原IP地址

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" "192.168.1.1"有效 IP 地址,但是"0.011.255.245""192.168.1.312""192.168@1.1"无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

输入:s = "0000"
输出:["0.0.0.0"]

示例 3:

输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

回溯

93.复原IP地址
  • 时间复杂度: O ( 3 4 ) O(3^4) O(34),IP地址最多包含4个数字,每个数字最多有3种可能的分割方式,则搜索树的最大深度为4,每个节点最多有3个子节点。
  • 空间复杂度: O ( n ) O(n) O(n)
function restoreIpAddresses(s: string): string[] {
  const res = [];
  const path = [];
  function backtrack(begin: number) {
      // 满足条件
      if (begin === s.length && path.length === 4) {
          res.push(path.join('.'));
          return;
      }
      for (let i = begin; i < s.length; i++) {
          // 头是0且多于一位,剪枝
          if (s[begin] === '0' && i > begin) return; 
          // 截取数大于255,剪枝
          if (Number(s.slice(begin, i + 1)) > 255) return;
          path.push(s.slice(begin, i + 1));
          backtrack(i + 1);
          path.pop();
      }
  }
  backtrack(0);
  return res;
};

78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

回溯

78.子集
  • 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n2n)
  • 空间复杂度: O ( n ) O(n) O(n)
function subsets(nums: number[]): number[][] {
    const res: number[][] = [];
    const collection: number[] = [];
    const backtracking = (begin: number) => {
        res.push(collection.slice());
        if (begin === nums.length) {
            return;
        }
        for (let i = begin; i < nums.length; i++) {
            collection.push(nums[i]);
            backtracking(i + 1);
          	collection.pop(); // 回溯
        }
    }
    backtracking(0);
    return res;
};

90.子集Ⅱ

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

回溯

90.子集II
  • 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n2n)
  • 空间复杂度: O ( n ) O(n) O(n)
function subsetsWithDup(nums: number[]): number[][] {
    nums.sort((a, b) => a - b); // 排序
    const res: number[][] = [];
    const collection: number[] = [];
    const backtracking = (begin: number) => {
        res.push(collection.slice());
        if (begin === nums.length) {
            return
        }
        for (let i = begin; i < nums.length; i++) {
            if (i > 0 && begin !== i && nums[i] === nums[i - 1]) { // 过滤重复元素
                continue;
            }
            collection.push(nums[i]);
            backtracking(i + 1);
          	collection.pop(); // 回溯
        }
    }
    backtracking(0);
    return res;
};

491.递增子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 1:

输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

示例 2:

输入:nums = [4,4,3,2,1]
输出:[[4,4]]

提示:

  • 1 <= nums.length <= 15
  • -100 <= nums[i] <= 100

回溯+哈希表

491. 递增子序列1
  • 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n2n)
  • 空间复杂度: O ( n ) O(n) O(n)
function findSubsequences(nums: number[]): number[][] {
    const res: number[][] = [];
    const path: number[] = [];
    const backtracking = (begin: number) => {
        if (path.length >= 2) {
            res.push(path.slice());
        }
        if (begin === nums.length) {
            return;
        }
        const set = new Set<number>();
        for (let i = begin; i < nums.length; i++) {
            if (begin !== i && set.has(nums[i])) continue; // 同层相同的元素排除
            set.add(nums[i]);
            if (path.length === 0 || path[path.length - 1] <= nums[i]) {
                path.push(nums[i]);
            } else {
                continue; // 剪枝
            }
            backtracking(i + 1);
            path.pop(); // 回溯
        }
    }
    backtracking(0);
    return res;
};

46.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

回溯+数组哈希

46.全排列 image.png
  • 时间复杂度: O ( n ! ) O(n!) O(n!)
  • 空间复杂度: O ( n ) O(n) O(n)
function permute(nums: number[]): number[][] {
    const res: number[][] = [];
    const path: number[] = [];
    const arrMap: number[] = new Array(nums.length).fill(0);
    const backtracking = () => {
        if (path.length === nums.length) {
            res.push(path.slice());
            return;
        }
        for (let i = 0; i < arrMap.length; i++) {
            if (arrMap[i] === 0) {
                path.push(nums[i]);
                arrMap[i] = 1;
                backtracking();
                arrMap[i] = 0; // 回溯
                path.pop(); // 回溯
            }
        }
    }
    backtracking();
    return res;
};

47.全排列Ⅱ

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

回溯+排序+相邻判断去重

1687760945592
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
function permuteUnique(nums: number[]): number[][] {
    nums.sort((a, b) => a - b);
    const res: number[][] = [];
    const path: number[] = [];
    const backtracking = (used: boolean[]) => {
        if (path.length === nums.length) {
            res.push(path.slice());
            return;
        }
        for (let i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] === nums[i - 1] && !used[i-1]) { // 相邻之间去重
                continue;
            }
            if (!used[i]) {
                used[i] = true;
                path.push(nums[i]);
                backtracking(used);
                used[i] = false; // 回溯
                path.pop(); // 回溯
            }
        }
    }
    backtracking([]);
    return res;
};

回溯+非排序+哈希表去重

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
function permuteUnique(nums: number[]): number[][] {
    const res: number[][] = [];
    const path: number[] = [];
    const arrMap: number[] = new Array(nums.length).fill(0);
    const backtracking = () => {
        if (path.length === nums.length) {
            res.push(path.slice());
            return;
        }
        const set = new Set<number>(); // 哈希表,只在当前层,所以无须回溯
        for (let i = 0; i < arrMap.length; i++) {
            if (arrMap[i] === 0 && !set.has(nums[i])) {
                set.add(nums[i]);
                path.push(nums[i]);
                arrMap[i] = 1;
                backtracking();
                arrMap[i] = 0; // 回溯
                path.pop(); // 回溯
            }
        }
    }
    backtracking();
    return res;
};

332.重新安排行程

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"]["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

示例 1:

img

输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

示例 2:

img

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。

提示:

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromitoi 由大写英文字母组成
  • fromi != toi

回溯

332.重新安排行程1
function findItinerary(tickets: string[][]): string[] {
    // 按字典排序
    tickets.sort((a, b) => {
        return a[1] < b[1] ? -1 : 1;
    })

    // Map会的键会按插入顺序排序
    type TicketsMap = {
        [index: string]: Map<string, number>
    }
    const ticketMap: TicketsMap = {};
    for (const [from, to] of tickets) {
        if (ticketMap[from] === undefined) {
            ticketMap[from] = new Map();
        }
        ticketMap[from].set(to, (ticketMap[from].get(to) || 0) + 1);
    }
    const resRoute = ['JFK'];
    
    const backtracking = (): boolean => {
        if (resRoute.length === tickets.length + 1) return true;
        const targetMap = ticketMap[resRoute[resRoute.length - 1]];
        if (targetMap !== undefined) {
            for (const [to, count] of targetMap.entries()) {
                if (count > 0) {
                    resRoute.push(to);
                    targetMap.set(to, count - 1);
                    if (backtracking() === true) return true;
                    targetMap.set(to, count);
                    resRoute.pop();
                }
            }
        }
        return false;
    }

    backtracking();
    return resRoute;
};

51.N皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

示例 1:

img
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

回溯+二维数组

img

  • 时间复杂度: O ( n ! ) O(n!) O(n!)
  • 空间复杂度: O ( n ) O(n) O(n)
function solveNQueens(n: number): string[][] {
    const res: string[][] = [];
    const chessboard: string[][] = Array.from({ length: n }, () => Array.from({ length: n }, () => '.'));

    function isValid(row: number, col: number) {
        // 检查列
        for (let i = 0; i < row; i++) {
            if (chessboard[i][col] === 'Q') {
                return false;
            }
        }
        // 检查45度斜线
        for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (chessboard[i][j] === 'Q') {
                return false
            }
        }
        // 检查135度斜线
        for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (chessboard[i][j] === 'Q') {
                return false
            }
        }
        return true;
    }

    function backtraking(row: number) {
        if (row === n) {
            res.push(chessboard.map(item => item.join('').slice()));
            return;
        }
        for (let col = 0; col < n; col++) {
            if (isValid(row, col)) {
                chessboard[row][col] = 'Q';
                backtraking(row + 1);
                chessboard[row][col] = '.';
            }
        }
    }

    backtraking(0);
    return res;
};

37.解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

img

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

img

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

回溯-二维数组递归

37.解数独
/**
 Do not return anything, modify board in-place instead.
 */
function solveSudoku(board: string[][]): void {
    const N = board.length;
    function isValid(row: number, col: number, val: string) {
        // 检查行
        for (let i = 0; i < N; i++) {
            if (board[row][i] === val) return false;
        }
        // 检查列
        for (let j = 0; j < N; j++) {
            if (board[j][col] === val) return false;
        }
        // 检查9宫格
        const startRow = 3 * Math.floor(row / 3);
        const startCol = 3 * Math.floor(col / 3);
        for (let i = startRow; i < startRow + 3; i++) {
            for (let j = startCol; j < startCol + 3; j++) {
                if (board[i][j] === val) return false;
            }
        }
        return true;
    }
    function backtracking(): boolean {
        for (let row = 0; row < N; row++) {
            for (let col = 0; col < N; col++) {
                if (board[row][col] !== ".") continue;
                for (let val = 1; val <= 9; val++) {
                    if (isValid(row, col, String(val))) {
                        board[row][col] = String(val);
                        if (backtracking()) return true;
                        board[row][col] = "."; // 回溯
                    }
                }
                return false; // 没有找到合适的,返回false
            }
        }
        return true; // 遍历后没有返回false,说明找到了合适的棋盘位置
    }
    backtracking();
};
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值