文章目录
回溯三部曲
- 确定递归函数参数
- 确定终止条件
- 单层搜索过程
回溯算法能解决如下问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
77.组合
给定两个整数
n
和k
,返回范围[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](https://img-blog.csdnimg.cn/img_convert/5dc7f3299f64ed65d4f54841b051f46d.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](https://img-blog.csdnimg.cn/img_convert/0c46397a299d8a2d0fa49beca8a81ea7.png)
![image.png](https://img-blog.csdnimg.cn/img_convert/258e9aaf857468e08fa5f292b0b5552a.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.组合总和Ⅲ
找出所有相加之和为
n
的k
个数的组合,且满足下列条件:
- 只使用数字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](https://img-blog.csdnimg.cn/img_convert/57297514c0f844c8dcf24bcdeba9b8e7.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](https://img-blog.csdnimg.cn/img_convert/9a09f7b19d5a4edd39e9c4000c2c7c89.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 (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 不对应任何字母。
![]()
示例 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](https://img-blog.csdnimg.cn/img_convert/8ae4d20139389f570a5403dbd4c81a7a.gif)
![17. 电话号码的字母组合](https://img-blog.csdnimg.cn/img_convert/7d797bb9b66ed65d2c26aaee4cd769a2.png)
- 时间复杂度: 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](https://img-blog.csdnimg.cn/img_convert/4689b414c5d78d2cf723eea0f9c5ed49.png)
- 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
- 空间复杂度: 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](https://img-blog.csdnimg.cn/img_convert/58de79efd6fbd233f90f3e0648fff8ef.png)
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.分割回文串](https://img-blog.csdnimg.cn/img_convert/1baa8aeec22b9b1bac365b2a87a05880.jpeg)
- 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)
- 空间复杂度: 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 地址 正好由四个整数(每个整数位于
0
到255
之间组成,且不能含有前导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地址](https://img-blog.csdnimg.cn/img_convert/1cfd7c56559cbf36c9531632ce34fd00.png)
- 时间复杂度: 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.子集](https://img-blog.csdnimg.cn/img_convert/6733c268e24330fcbb7e0abc254d41f1.png)
- 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)。
- 空间复杂度: 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](https://img-blog.csdnimg.cn/img_convert/3d78bfe1bae1d5410919da41fffc09e8.png)
- 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)。
- 空间复杂度: 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](https://img-blog.csdnimg.cn/img_convert/9a9405c401a92a486bc7a8b154be6727.png)
- 时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)。
- 空间复杂度: 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.全排列](https://img-blog.csdnimg.cn/img_convert/b5214c33f931ca6dd14c3d2919163b1f.png)
![image.png](https://img-blog.csdnimg.cn/img_convert/2d3998a17a1d997c10873a08cd01d9fc.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](https://img-blog.csdnimg.cn/img_convert/6032cf92b7ba30469a6bb5b687e4a996.png)
- 时间复杂度: 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:
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] 输出:["JFK","MUC","LHR","SFO","SJC"]
示例 2:
输入: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
fromi
和toi
由大写英文字母组成fromi != toi
回溯
![332.重新安排行程1](https://img-blog.csdnimg.cn/img_convert/8100b4fc8b8fd7800754dfade46ed5fe.png)
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:
![]()
输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1 输出:[["Q"]]
提示:
1 <= n <= 9
回溯+二维数组
- 时间复杂度: 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-9
在每一行只能出现一次。- 数字
1-9
在每一列只能出现一次。- 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用
'.'
表示。示例 1:
输入: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"]] 解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
提示:
board.length == 9
board[i].length == 9
board[i][j]
是一位数字或者'.'
- 题目数据 保证 输入数独仅有一个解
回溯-二维数组递归
![37.解数独](https://img-blog.csdnimg.cn/img_convert/737cb15a97feb924589b827bea494258.png)
/**
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();
};