回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。但当探索到某一步时,发现原先选择达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」。
许多复杂的,规模较大的问题都可以使用回溯法,有「通用解题方法」的美称。实际上,回溯算法就是暴力搜索算法,它是早期的人工智能里使用的算法,借助计算机强大的计算能帮助我们找到问题的解。
给定两个整数 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> result;
vector<int> path;
backtracking(1, n, k, path, result);
return result;
}
//设置Index的目的是该问题为组合问题,而且所选择的元素不重复
//每一层可以在数据集合中所选择的数据范围都是缩小的
//例如本题在第一层在(1,4)之间选择了1之后,下一层只能在(2,4)之间进行选择了,而不能再选择1.
void backtracking(int index, int n, int k, vector<int>& path, vector<vector<int>>& result)
{
if (path.size() == k) //回溯函数的终止条件
{
result.push_back(path);
return;
}
for (int i = index; i <= n; i++) //探索每一层的所有可能的路径选择
{
path.push_back(i); //对当前所选择的节点数据进行处理
backtracking(i + 1, n, k, path, result); //递归继续处理子问题
path.pop_back(); //从该节点往下的情况处理完毕,撤销对该节点所做的处理
} //for循环会选择该层的下一个节点继续搜索
}
//情况1:选择该数据,剩下的递归处理
//情况2:不选择该数据,for循环判断下一个数据
};
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> result;
vector<int> item;
backtracking(1, k, n, item, result);
return result;
}
void backtracking(int i, int k, int n, vector<int>& item, vector<vector<int>>& result) {
if (item.size() == k) {
result.push_back(item);
return;
}
if (i > n) return; //这个判断必须放在下面,不然会导致错误
item.push_back(i);
backtracking(i + 1, k, n, item, result);
item.pop_back();
backtracking(i + 1, k, n, item, result);
}
};
给你一个 无重复元素 的整数数组 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 输出: []
示例 4:
输入: candidates = [1], target = 1 输出: [[1]]
示例 5:
输入: candidates = [1], target = 2 输出: [[1,1]]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都 互不相同
1 <= target <= 500
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> vec;
backtracking(0, 0, vec, result, candidates, target);
return result;
}
void backtracking(int Index, int sum, vector<int>& vec, vector<vector<int>>& result, vector<int>& candidates, int target)
{
if (sum == target) //回溯函数获得正确结果的终止条件
{
result.push_back(vec);
return;
}
if (sum > target) //回溯函数走的这条路径所产生的结果行不通,需要返回
{
return;
}
for (int i = Index; i < candidates.size(); i++)
{
sum += candidates[i]; //对该层所选择的数据节点进行处理
vec.push_back(candidates[i]);
backtracking(i, sum, vec, result, candidates, target); //这里传入参数i的原因是因为该数据节点在下一层的处理中可以重复选取
sum -= candidates[i];
vec.pop_back(); //撤销,对该层所选择的数据节点的处理,for循环选择该层的下一个数据节点继续向下探索
}
}
};
给定一个数组 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] ]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
注意:这道题主要新增加了对下面这种情况的去重代码,因为candidates数组中的元素是乱序的,并且存在有重复的元素。需要先对candidates数组排序,将相同的元素放在一起,在递归过程中,于某一层选取数据节点时,如果发现后面的一个元素和前面的一个元素相同,那么就跳过该元素的选取,不然就会导致最终选择出来的数据有下面重复的情况。
输入 [10,1,2,7,6,1,5] 8 输出 [[1,2,5],[1,7],[1,6,1],[2,6],[2,1,5],[7,1]]
预期结果 [[1,1,6],[1,2,5],[1,7],[2,6]]
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> path;
// set st(candidates.begin(), candidates.end());
// candidates.assign(st.begin(), st.end());
sort(candidates.begin(), candidates.end());
backtracking(0, 0, target, candidates, path, result);
return result;
}
void backtracking(int index, int sum, int target, vector<int>& candidates, vector<int>& path, vector<vector<int>>& result)
{
if (sum == target)
{
result.push_back(path);
return;
}
if(sum > target) //剪枝
{
return;
}
for (int i = index; i < candidates.size(); i++)
{
if(i > index && candidates[i] == candidates[i-1])
{
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(i + 1, sum, target, candidates, path, result);
sum -= candidates[i];
path.pop_back();
}
}
};
该题如果用集合set去重复,最后一个测试用例会超时
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> item;
sort(candidates.begin(), candidates.end());
set<vector<int>> st; //用于对结果去重的集合
backtracking(0, target, 0, candidates, item, result, st);
return result;
}
void backtracking(int start, int target, int sum, vector<int>& candidates, vector<int>& item, vector<vector<int>>& result, set<vector<int>>& st) {
if (sum == target) {
if (st.find(item) == st.end()) {
result.push_back(item);
st.insert(item);
}
return;
}
if (sum > target) {
return;
}
for (int i = start; i < candidates.size(); i++) {
sum += candidates[i];
item.push_back(candidates[i]);
backtracking(i + 1, target, sum, candidates, item, result, st);
item.pop_back();
sum -= candidates[i];
}
}
};
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-iii
class Solution {
public:
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> result;
vector<int> path;
backtracking(1, 0, k, n, path, result);
return result;
}
void backtracking(int index, int sum, int k, int n, vector<int>& path, vector<vector<int>>& result)
{
if (path.size() == k && sum == n)
{
result.push_back(path);
return;
}
if (path.size() > k || sum > n) //回溯过程中的剪枝,在该层的处理的过程中发现结果已经不符合我们要求就直接返回,节约时间。
{
return;
}
for (int x = index; x <= 9; x++) //每个数字的取值范围为1-9,并且是数字不重复的组合
{
sum += x;
path.push_back(x);
backtracking(x + 1, sum, k, n, path, result);
sum -= x;
path.pop_back();
}
}
};
class Solution {
public:
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> result;
vector<int> path;
backtracking(1, 0, k, n, path, result);
return result;
}
void backtracking(int x, int sum, int k, int n, vector<int>& path, vector<vector<int>>& result) {
if (path.size() == k && sum == n) {
result.push_back(path);
return;
}
if (path.size() > k || sum > n) { //回溯过程中的剪枝,在该层的处理的过程中发现结果已经不符合我们要求就直接返回,节约时间。
return;
}
if (x > 9) return;
path.push_back(x); //放x的情况
backtracking(x + 1, sum + x, k, n, path, result);
path.pop_back(); //不放x的情况
backtracking(x + 1, sum, k, n, path, result);
}
};
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 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’] 的一个数字。
class Solution {
public:
vector<string> letterCombinations(string digits) {
unordered_map<char, string> ump = //存储数字字符对应字母字符串的映射
{
{'2',"abc"}, {'3',"def"}, {'4',"ghi"}, {'5',"jkl"},
{'6',"mno"}, {'7',"pqrs"}, {'8',"tuv"}, {'9',"wxyz"}
};
vector<string> result;
string path;
if(digits.size() == 0)
{
return result;
}
backtracking(0, path, result, ump, digits);
return result;
}
void backtracking(int index, string& path, vector<string>& result, unordered_map<char, string>& ump, string& digits)
{
if (index == digits.length())
{
result.push_back(path);
return;
}
string tmp = ump[digits[index]]; //index指的是递归树的深度,或者说所按数字字符串的下标
for (int i = 0; i < tmp.size(); i++)
{
path.push_back(tmp[i]);
backtracking(index + 1, path, result, ump, digits);
path.pop_back();
}
}
};
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = “aab” 输出:[[“a”,“a”,“b”],[“aa”,“b”]]
示例 2:
输入:s = “a” 输出:[[“a”]]
对于字符串aab,先切割a,再递归处理其余部分,再切割aa,再递归处理其余部分,最后切割aab,再递归处理其余部分。
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path;
backtracking(0, s, path, result);
return result;
}
void backtracking(int startIndex, string s, vector<string>& path, vector<vector<string>>& result)
{
if (startIndex >= s.size())
{
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++)
{
if (isPalindrome(s, startIndex, i)) //[startIndex,i]为回文串并切割下来
{
string st = s.substr(startIndex, i - startIndex + 1);
path.push_back(st); //保存此次切割结果
}
else //这里过滤掉非回文串的子串,相当于剪枝
{
continue;
}
backtracking(i + 1, s, path, result); //递归处理剩余的字符串,下次的起始位置startIndex = i + 1
path.pop_back(); //状态回退,选择新的第一次要切割下来的子串
}
}
bool isPalindrome(const string& str, int start, int end) //判断子串是否为回文串
{
bool flag = true;
for (int i = start, j = end; i < j; i++, j--)
{
if (str[i] != str[j])
{
flag = false;
}
}
return flag;
}
};
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “[email protected]” 是 无效 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 = “1111” 输出:[“1.1.1.1”]
示例 4:
输入:s = “010010” 输出:[“0.10.0.10”,“0.100.1.0”]
示例 5:
输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]
提示:
0 <= s.length <= 20
s 仅由数字组成
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> result;
backtracking(0, 0, s, result);
return result;
}
//在分割过程中不能重复分割,startIndex用来记录下一层递归分割的起始位置
//pointNum记录添加逗点的数量
void backtracking(int startIndex, int pointNum, string& s, vector<string>& result)
{
if (pointNum == 3 ) //terminate
{ //用分割的段数作为终止条件,pointNum为3说明字符串分成4段了
if (IsCorrect(s, startIndex, s.size() - 1))
{
result.push_back(s);
}
return;
}
for (int i = startIndex; i < s.size(); i++)
{
if (IsCorrect(s, startIndex, i))
{
s.insert(s.begin() + i + 1, '.'); //在下标i的后面插入一个小数点
pointNum++;
backtracking(i + 2, pointNum, s, result); //插入小数点后,下一次的startindex为i+2
pointNum--; //回溯
s.erase(s.begin() + i + 1);
}
else
{
break; //如果在某层截取过程中遇到不合法的数字直接结束本层的搜索,剪掉分支
}
}
}
bool IsCorrect(const string& s, int start, int end)
{
if(start > end)
{
return false;
}
string su = s.substr(start, end - start + 1);
if (su.size() > 1 && su[0] == '0') //处理字符串前导0,或者该字符串中含有非数字不能转成Int
{
return false;
}
int num = atoi(su.c_str());
if (num == -1) //atoi可能因为数字过大报错返回-1
{
return false;
}
if (num >= 0 && num <= 255)
{
return true;
}
else
{
return false;
}
}
};
给你一个整数数组 nums,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集不能包含重复的子集。你可以按任意顺序返回解集。
采用回溯法生产子集,即对于每个元素,都有试探放入或不放入集合两个选择:
选择放入该元素,递归的进行后续元素的选择,完成放入该元素后续所有元素的试探;
之后将其拿出来,即再进行一次选择不放入该元素,递归的进行后续元素的选择,完成不放入该元素后续所有元素的试探。(递归树)
本来选择放入,再选择一次不放入的这个过程,称为回溯试探法。
例如:
元素数组: nums = [1,2,3,4,5],子集生成数组path[] = []
对于元素1,
选择放入path,path = [1],继续递归处理后续[2,3,4,5]元素;path = [1,……]
选择不放入path,path = [],继续递归处理后续[2,3,4,5]元素;path = [……]
在递归树中,递归回溯的时候,需要path.pop_back(),比如说,我们现在放入了1、2、3,碰到递归结束条件return,返回到上一层,这时候我们需要状态重置,将3弹出来,处理不放入3的结果。处理完不放3的情况后,递归树再次返回到上一层,然后处理不放入2的情况。
注:如果把子集问题、组合问题、分割问题都抽象成一颗树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找到树的所有节点。
注:既然是无序,取过的元素不会重复选取,写回溯算法的时候,for就要从startIndex开始,而不是0开始。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> path;
vector<vector<int>> result;
backtracking(0, path, result, nums);
return result;
}
void backtracking(int startIndex, vector<int>& path, vector<vector<int>>& result, vector<int>& nums)
{
result.push_back(path); //收集子集要放在terminate上面,否则会丢失[]集合
if (startIndex >= nums.size()) { //terminate
return;
}
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(i + 1, path, result, nums);
path.pop_back();
}
}
};
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> item;
result.push_back(item);
backtracking(0, nums, item, result);
return result;
}
void backtracking(int i, vector<int>& nums, vector<int>& item, vector<vector<int>>& result) {
if (i >= nums.size()) { //terminate
return;
}
item.push_back(nums[i]);
result.push_back(item);
backtracking(i + 1, nums, item, result);
item.pop_back();
backtracking(i + 1, nums, item, result);
}
};
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
注:相比于上面的子集问题多了去重的操作。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
有两种重复原因:
1、不同位置的元素组成的集合是同一个子集,顺序相同:
例如:[2,1,2,2]
选择第1,2,3个元素组成的子集:[2,1,2]
选择第1,2,4个元素组成的子集:[2,1,2]
2、不同位置的元素组成的集合是同一个子集,虽然顺序不同,但仍然代表了同一个子集,因为集合中的元素是无序的。
选择第1,2,3个元素组成的子集:[2,1,2]
选择第2,3,4个元素组成的子集:[1,2,2]
解决方案:可以先对原始nums数组进行排序,再使用set去重。
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> result;
vector<int> item;
set<vector<int>> res_set;
sort(nums.begin(), nums.end());
result.push_back(item);
backtracking(0, nums, item, result, res_set);
return result;
}
void backtracking(int i, vector<int>& nums,
vector<int>& item,
vector<vector<int>>& result,
set<vector<int>>& res_set) {
if (i >= nums.size()) { //terminate
return;
}
item.push_back(nums[i]);
//result.push_back(item);
if(res_set.find(item) == res_set.end()) {
result.push_back(item);
res_set.insert(item);
}
backtracking(i + 1, nums, item, result, res_set);
item.pop_back();
backtracking(i + 1, nums, item, result, res_set);
}
};
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<int> path;
vector<vector<int>> result;
sort(nums.begin(), nums.end());
backtracking(0, path, result, nums);
return result;
}
void backtracking(int startIndex, vector<int> path, vector<vector<int>>& result, vector<int>& nums)
{
result.push_back(path);
if (startIndex >= nums.size())
{
return;
}
for (int i = startIndex; i < nums.size(); i++)
{
if (i > startIndex && nums[i] == nums[i - 1])
{
continue;
}
path.push_back(nums[i]);
backtracking(i + 1, path, result, nums);
path.pop_back();
}
}
};
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按任意顺序返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
注:在子集II中我们是通过排序,再判断本层遍历的前后元素是否相同来进行去重的。而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能用之前的去重逻辑。
注:本题也可以根据-100 <= nums[i] <= 100条件来用数组做哈希映射来判断该元素是否被使用过进行去重。
示例 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
class Solution {
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
vector<int> path;
vector<vector<int>> result;
backtracking(0, path, result, nums);
return result;
}
void backtracking(int startIndex, vector<int> path, vector<vector<int>>& result, vector<int>& nums)
{
if (path.size() >= 2)
{
result.push_back(path);
}
if (startIndex >= nums.size())
{
return;
}
unordered_set<int> uset; //记录本层元素是否重复使用,在新的一层set会被重新定义,所以set只负责本层
for (int i = startIndex; i < nums.size(); i++)
{
if (uset.find(nums[i]) != uset.end()) //该元素在这一层已经使用了,不能再使用了
{
continue;
}
if (!path.empty() && nums[i] < path.back())
{
continue;
}
uset.insert(nums[i]);
path.push_back(nums[i]);
backtracking(i + 1, path, result, nums);
path.pop_back();
}
}
};
给定一个不含重复数字的数组 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 中的所有整数 互不相同
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<int> path;
vector<vector<int>> result;
vector<bool> used(nums.size(), false);
backtracking(nums, used, path, result);
return result;
}
void backtracking(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& result)
{
if(path.size() == nums.size())
{
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++)
{
if (!used[i])
{
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, used, path, result);
used[i] = false;
path.pop_back();
}
}
}
};
给定一个可包含重复数字的序列 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
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<int> path;
vector<vector<int>> result;
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums, used, path, result);
return result;
}
void backtracking(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& result)
{
if(path.size() == nums.size())
{
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++)
{
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true)
{
continue;
}
if (!used[i])
{
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, used, path, result);
used[i] = false;
path.pop_back();
}
}
}
};
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1 输出:[[“Q”]]
提示:
1 <= n <= 9
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
string line;
vector<string> record;
vector<vector<string>> result;
vector<vector<bool>> mark; //使用二维数组mark[][]表示一张空的棋盘
for(int i = 0; i < n; i++) //对棋盘进行初始化
{
mark.push_back(vector<bool>());
for(int j = 0; j < n; j++)
{
mark[i].push_back(false);
}
record.push_back("");
record[i].append(n, '.');
}
backtracking(0, result, record, mark);
return result;
}
//index是递归的深度,指的是棋盘的行数,在特定的行尝试把棋子放在不同的列进行探索,正确的解
void backtracking(int index, vector<vector<string>>&result, vector<string>&record, vector<vector<bool>>&mark)
{
if (index == mark.size()) //terminate
{
result.push_back(record);
return;
}
for(int i = 0; i < mark.size(); i++)
{
if(!mark[index][i])
{
vector<vector<bool>> markbak = mark;
putDownTheQueue(index, i, mark);
record[index][i] = 'Q';
backtracking(index+1, result, record, mark);
mark = markbak;
record[index][i] = '.';
}
}
}
//put_down_the_queen()函数的功能就是将皇后放置在坐标(x,y)处,并且对mark数组进行修改
//使得该位置的八个方向上所有位置坐标值都为1,这些方向都不能再放置皇后了
void putDownTheQueue(int x, int y, vector<vector<bool>>& mark)
{
static const int direct[][2] = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} };
mark[x][y] = true;
for (int i = 1; i < mark.size(); i++)
{
for (int j = 0; j < 8; j++) //8个方向每个方向向外延申1-N-1
{
int newx = x + i * direct[j][0];
int newy = y + i * direct[j][1];
if (newx >= 0 && newx < mark.size() && newy >= 0 && newy < mark.size())
{
mark[newx][newy] = true;
}
}
}
}
};
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int target = 0;
for(int i = 0; i < nums.size(); i++) {
target += nums[i];
}
if(target % k != 0) return false;
target = target / k;
sort(nums.begin(), nums.end(), greater<int>()); //降序排列
if(nums.front() > target) return false; //第一个数放在哪个桶都不合适
bool flag = false;
vector<bool> visited(nums.size(), false);
BackTracking(k, 0, 0, target, nums, flag, visited);
return flag;
}
void BackTracking(int k, int i, int curSum, int target, vector<int>& nums, bool& flag, vector<bool>& visited) {
if (k == 0) {
flag = true;
return;
}
if (curSum == target) {
BackTracking(k - 1, 0, 0, target, nums, flag, visited);
return;
}
if (i >= nums.size()) {
return;
}
if(!visited[i] && curSum + nums[i] <= target) {
visited[i] = true;
BackTracking(k, i + 1, curSum + nums[i], target, nums, flag, visited);
visited[i] = false;
}
if (flag == true) return;
BackTracking(k, i + 1, curSum, target, nums, flag, visited);
}
};
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int target = 0;
for(int i = 0; i < nums.size(); i++) {
target += nums[i];
}
if(target % k != 0) return false;
target = target / k;
sort(nums.begin(), nums.end(), greater<int>());
if(nums.front() > target) return false;
vector<bool> visited(nums.size(), false);
return backtracking(k, 0, target, 0, nums, visited);
}
bool backtracking(int k, int startIndex, int target, int curSum, vector<int>& nums, vector<bool>& visited) {
if(k == 0) return true;
if(curSum == target) {
return backtracking(k-1, 0, target, 0, nums, visited);
}
for(int i = startIndex; i < nums.size(); i++) { //从startIndex开始,第一次选过的元素第二次就不能选择了
if(visited[i]) continue;
if(curSum + nums[i] > target) continue;
visited[i] = true;
curSum += nums[i];
if(backtracking(k, i+1, target, curSum, nums, visited)) {
return true;
}
curSum -= nums[i];
visited[i] = false;
}
return false;
}
};
代码摘自leetcode题解区
// 其实题 416(k = 2) 以及 473(k = 4) 都是这题的特例
// 把 nums 里的数字想象成一个个石头, 石头的总重量为 total
// 定义一个大小为 k 的数组表示有 k 个背包
// 每个背包的载重都相等, 为 total / k
// 现在问我们能不能把这些石头都装进背包里, 显然当且仅当每个背包都装满
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int target = 0;
for(int i = 0; i < nums.size(); i++) {
target += nums[i];
}
if(target % k != 0) return false;
target = target / k;
sort(nums.begin(), nums.end(), greater<int>());
// 定义 k 个背包
vector<int> bags(k, 0);
// 每个背包的容量为 total / k, 从第一块石头开始装
return backtracking(0, target, nums, bags);
}
bool backtracking(int index, int capacity, vector<int>& nums, vector<int>& bags) {
if(index >= nums.size()) return true; // 如果所有石头都被装进去了, 就找到了一种可能的方案
// 遍历每个包, 检查包的剩余容量
for(int i = 0; i < bags.size(); i++) {
if(bags[i] + nums[index] <= capacity) { //如果当前石头可以装进这个包
bags[i] += nums[index];
if(backtracking(index+1, capacity, nums, bags)) { //继续去装下一块石头
return true;
}
bags[i] -= nums[index]; //这种方案不行, 把石头从包里拿出来
}
if(bags[i] == 0) break;
}
// 非常重要的剪枝!!!
// 如果这个包里什么都没装, 那么就不在下一个包里装
// 因为必须每个包都装才行啊, 跳过这个包去装之后的包肯定不会搜索到可能的方案
return false; //如果不存在一种组合可以使每个背包都正好放下,那么返回false
}
};
每次拿到一个物品,都尝试将它放进背包中(背包的顺序是从1到4),在前面的时候,我们做了对物品的排序,使其物品的重量从大到小,这样就可以先尝试放入最大重量的物品,如果最大重量的物品超过了背包的容量,那么肯定就是false,相当于做了一些剪枝优化。如果在尝试的过程中,一个物品在4个背包中都放不进去,加入这个物品,4个背包都会出现超重的情况,那么就说明前面我们4个背包中已经放置好的物品的方案并不合理,就需要回溯,即返回到前面的结果,回退到前面的状态,然后再进行往下的递归尝试。如果尝试完了所有结果,物品刚好可以将所有的背包可以放满,那么就返回true,否则就是不存在返回false.
//对于正方形来说存在4条相等的边,这里抽象成4个背包
//对于所给数组中的元素,这里抽象成所给物品的重量
//则题目转化成了,判断将所有物品装入这四个背包中,能否恰好把背包装满
//每次拿到一个物品就尝试
class Solution {
public:
bool makesquare(vector<int>& matchsticks) {
vector<int> bags(4,0);
int sum = 0;
for(auto ele : matchsticks) {
sum += ele;
}
if(sum % 4 != 0) {
return false;
}
sort(matchsticks.begin(), matchsticks.end(), greater<int>()); //排序优化
return backtracking(0, matchsticks, sum/4, bags);
}
bool backtracking(int i, vector<int>& matchsticks, int capacity,vector<int>& bags) {
if(i >= matchsticks.size()) {
return true;
}
for(int k = 0; k < bags.size(); k++) {
if(bags[k] + matchsticks[i] <= capacity) {
bags[k] += matchsticks[i];
if(backtracking(i+1, matchsticks, capacity, bags)) {
return true;
}
bags[k] -= matchsticks[i];
}
if(bags[k] == 0) return false;
}
return false; //如果不存在一种组合可以使每个背包都正好放下,那么返回false
}
};
在处理过程中,也可以先用物品将一个背包填满,然后这些物品就不能使用了,再使用其他的物品将第二个背包填满,这样当四个背包都填满时,并且所有的物品刚好用完时,返回true。
class Solution {
public:
bool makesquare(vector<int>& matchsticks) {
vector<int> bags(4,0);
int sum = 0;
for(auto ele : matchsticks) {
sum += ele;
}
if(sum % 4 != 0) {
return false;
}
sort(matchsticks.begin(), matchsticks.end(), greater<int>()); //排序优化
vector<bool> used(matchsticks.size(), 0);
return backtracking(0, 4, 0, sum/4, matchsticks, used);
}
bool backtracking(int start, int k, int curSum, int capacity, vector<int>& matchsticks, vector<bool>& used) {
if(k == 0) {
return true;
}
if(curSum == capacity) {
return backtracking(0, k-1, 0, capacity, matchsticks, used);
}
for(int i = start; i < matchsticks.size(); i++) {
if(used[i]) {
continue;
}
if(curSum + matchsticks[i] > capacity) {
continue;
}
used[i] = true;
curSum += matchsticks[i];
if(backtracking(i+1, k, curSum, capacity, matchsticks, used)) {
return true;
}
curSum -= matchsticks[i];
used[i] = false;
}
return false;
}
};