回溯算法
文章目录
1. 组合
77. 组合
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
class Solution {
public:
void backing(vector<vector<int>> &combine,vector<int> &v,int num,int n,int k){
if(v.size() == k ){
combine.push_back(v);
return;
}
//优化
//v.size =已经放进去的元素
//k - v.size = 所需需要的元素个数
//列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
// i <= n - (k - v.size()) + 1,开始遍历,有个+1,因为包括起始位置
for(int i = num;i <= n - (k - v.size()) + 1;i++){
v.push_back(i);
num++;
backing(combine,v,num,n,k);
v.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> combine;
vector<int> v;
if(n < k)return combine;
int num = 1;
backing(combine,v,num,n,k);
return combine;
}
};
2.组合总和III
216.组合总和III
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
class Solution {
public:
void backtracing(vector<vector<int>>& combin,vector<int>& v,int k,int n,int numstart,int sum){
if(v.size() == k){
if(sum == n){
combin.push_back(v);
}
return;
}
for(int i = numstart;i <= 9 - (k - v.size()) + 1;i++){
v.push_back(i);
backtracing(combin,v,k,n,++numstart,sum + i);
v.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>>combin;
vector<int>v;
int sum = 0;
for(int i = 1;i <= k;i++){
sum += i;
}
if(sum > n){
return combin;
}
else{
backtracing(combin,v,k,n,1,0);
}
return combin;
}
};
3.电话号码的字母组合
17.电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
class Solution {
public:
string s2 = "abc",s3 = "def",s4 = "ghi",s5 = "jkl",s6 = "mno",s7 = "pqrs",s8 = "tuv",s9 = "wxyz";
void backtracing(string digits,string& s,vector<string>& combin,int startnum){
if(s.size() == digits.size()){
combin.push_back(s);
return;
}
int num = digits[startnum] - '0';
switch(num){
case 2:{
for(int j = 0;j < s2.size();j++){
s.push_back(s2[j]);
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 3:{
for(int j = 0;j < 3;j++){
s.push_back(s3[j]);
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 4:{
for(int j = 0;j < 3;j++){
s += s4[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 5:{
for(int j = 0;j < 3;j++){
s += s5[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 6:{
for(int j = 0;j < 3;j++){
s += s6[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 7:{
for(int j = 0;j < 4;j++){
s += s7[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 8:{
for(int j = 0;j < 3;j++){
s += s8[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
case 9:{
for(int j = 0;j < 4;j++){
s += s9[j];
backtracing(digits,s,combin,startnum + 1);
s.pop_back();
}
}break;
}
}
vector<string> letterCombinations(string digits) {
vector<string>combin;
string s;
if(digits.size() == 0)return combin;
backtracing(digits,s,combin,0);
return combin;
}
};
4. 组合总和
39. 组合总和
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
class Solution {
public:
void backtracing(vector<vector<int>>&combin,vector<int>&v,vector<int>&candidates,int target,int sum,int startnum){
if(sum == target){
combin.push_back(v);
return;
}
if(sum > target || (target - sum) < candidates[startnum]){
return;
}
for(int i = startnum;i < candidates.size();i++){
v.push_back(candidates[i]);
sum += candidates[i];
backtracing(combin,v,candidates,target,sum,startnum);
startnum++;
sum -= candidates[i];
v.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>>combin;
vector<int>v;
sort(candidates.begin(),candidates.end());
if(target < candidates[0]){
return combin;
}
int startnum = 0;
backtracing(combin,v,candidates,target,0,startnum);
return combin;
}
};
5.组合总和II
40.组合总和II
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
**注意:**解集不能包含重复的组合。
class Solution {
private:
vector<vector<int>>combin;
vector<int>v;
public:
void backtracing(vector<int>& candidates,int target,int sum,int startnum){
if(sum == target){
combin.push_back(v);
return;
}
if(sum > target || startnum < candidates.size() &&(target - sum) < candidates[startnum]){
return;
}
for(int i = startnum;i < candidates.size();i++){
if(i > startnum && candidates[i] == candidates[i - 1] ){
continue;
}
v.push_back(candidates[i]);
sum += candidates[i];
backtracing(candidates,target,sum,i + 1);
sum -= candidates[i];
v.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
if(target < candidates[0])return combin;
backtracing(candidates,target,0,0);
return combin;
}
};
6.分割回文串
131.分割回文串
class Solution {
private:
vector<vector<string>>part;
vector<string>v;
public:
bool pan(string& str){
for(int i = 0,j = str.size() - 1;i < j;i++,j--){
if(str[i] != str[j])return false;
}
return true;
}
void backtracing(string& s,int startnum){
if(startnum >= s.size()){
part.push_back(v);
return;
}
for(int i = startnum;i < s.size();i++){
string str = s.substr(startnum,i - startnum + 1);
if(pan(str) == true){
v.push_back(str);
backtracing(s,i + 1);
v.pop_back();
}
else{
continue;
}
}
}
vector<vector<string>> partition(string s) {
backtracing(s,0);
return part;
}
};
7.复原IP地址
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
中的任何数字。你可以按 任何 顺序返回答案。
class Solution {
private:
vector<string>restore;
public:
//判断语句不要搞得花里胡哨,直接按照最简单来
bool istrue(string& s,int start,int end){
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
void backtracing(string& s, int startnum,int cutnum){
if(cutnum == 3){
if(istrue(s,startnum,s.size() - 1)){
restore.push_back(s);
}
return;
}
for(int i = startnum;i < s.size();i++){
if(istrue(s,startnum,i)){
s.insert(s.begin() + i + 1,'.');
cutnum++;
}
else{
return;
}
backtracing(s,i + 2,cutnum);
cutnum--;
s.erase(s.begin() + i + 1);
}
}
vector<string> restoreIpAddresses(string s) {
string address;
if(s.size() < 4)return restore;
backtracing(s,0,0);
return restore;
}
};
8.子集
78.子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
private:
vector<vector<int>>sub;
vector<int>v;
public:
void backtracing(vector<int>&nums,int start){
if(v.size() > 0 && v[v.size() - 1] == nums[nums.size() - 1]){
sub.push_back(v);
return;
}else sub.push_back(v);
for(int i = start;i < nums.size();i++){
v.push_back(nums[i]);
backtracing(nums,i + 1);
v.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracing(nums,0);
return sub;
}
};
9.子集II
90.子集II
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
class Solution {
private:
vector<vector<int>>sub;
vector<int>v;
public:
void backtracing(vector<int>& nums,int startnum){
sub.push_back(v);
if(startnum >= nums.size())return;
for(int i = startnum;i < nums.size();i++){
if(i > startnum && nums[i] == nums[i - 1]){
continue;
}
v.push_back(nums[i]);
backtracing(nums,i + 1);
v.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
backtracing(nums,0);
return sub;
}
};
10.递增子序列
491.递增子序列
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
class Solution {
private:
vector<vector<int>>find;
vector<int>v;
public:
//用哈希表也可以
void backtracing(vector<int>&nums,int startnum){
if(v.size() > 1)find.push_back(v);
if(startnum >= nums.size())return;
vector<int>used(201,0);
for(int i = startnum;i < nums.size();i++){
if( v.size() > 0 && nums[i] < v.back() || used[nums[i] + 100] == 1){
continue;
}
v.push_back(nums[i]);
used[nums[i] + 100] = 1;
backtracing(nums,i + 1);
v.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracing(nums,0);
return find;
}
};
11.全排列
46.全排列
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution {
private:
vector<vector<int>>per;
vector<int>v;
void backtracing(vector<int>& nums,vector<int>&used){
if(v.size() == nums.size()){
per.push_back(v);
return;
}
for(int i = 0;i < nums.size();i++){
if(used[i] == 1)continue;
v.push_back(nums[i]);
used[i] = 1;
backtracing(nums,used);
v.pop_back();
used[i] = 0;
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<int>used(nums.size(),0);
backtracing(nums,used);
return per;
}
};
12.全排列 II
47.全排列 II
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列
class Solution {
private:
vector<vector<int>>per;
vector<int>v;
void backtracing(vector<int>& nums,vector<int>& used,int startnum){
if(v.size() == nums.size()){
per.push_back(v);
return;
}
int used2[21] = {0};
for(int i = 0;i < nums.size();i++){
if(used[i] == 1 || used2[nums[i] + 10] == 1)continue;
v.push_back(nums[i]);
used[i] = 1;
used2[nums[i] + 10] = 1;
backtracing(nums,used,startnum + 1);
v.pop_back();
used[i] = 0;
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<int> used(nums.size(),0);
backtracing(nums,used,0);
return per;
}
};
13.重新安排行程
332.重新安排行程
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
class Solution {
private:
vector<vector<string>>find;
vector<string>v;
//得定义静态,否则报错
static bool cmp(const vector<string>&s1,const vector<string>&s2){
return s1[1] < s2[1];
}
void backtracing(vector<vector<string>>& tickets,vector<int>&used,string s){
if(find.size() == 1)return;//有一个就返回
if(v.size() == tickets.size() + 1){
find.push_back(v);
}
for(int i = 0;i < tickets.size() ;i++){
if(used[i] == 1 || tickets[i][0] != s)continue;
v.push_back(tickets[i][1]);
used[i] = 1;
backtracing(tickets,used,tickets[i][1]);
v.pop_back();
used[i] = 0;
}
}
public:
vector<string> findItinerary(vector<vector<string>>& tickets) {
vector<int>used(tickets.size(),0);
v.push_back("JFK");
sort(tickets.begin(),tickets.end(),cmp);//太强了,自己写了总是有错误,这个方法太巧妙l
backtracing(tickets,used,"JFK");
return find[0];
}
};
*14. N皇后
51. N皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
class Solution {
private:
vector<vector<string>>solve;
vector<string>v;
bool isnot(int hang,int lie,int n){
for(int i = 0;i < hang;i++){
if(v[i][lie] == 'Q')return false;
}//上面
for(int i = 0;i < hang;i++){
if(i >= hang - lie && v[i][lie + i - hang] == 'Q'){
return false;
}
}//左上
for(int i = 0;i < hang;i++){
if(i >= lie + hang - n && v[i][lie + hang - i] == 'Q'){
return false;
}
}//右上
return true;
}
void backtracing(int n,int hang){
if( v.size() == n){
solve.push_back(v);
return;
}
for(int i = 0;i < n;i++){
string s(n,'.');
if(hang == 0 || isnot(hang,i,n)){
s[i] = 'Q';
v.push_back(s);
backtracing(n,hang + 1);
v.pop_back();
}
}
}
public:
vector<vector<string>> solveNQueens(int n) {
backtracing(n,0);
return solve;
}
};
*15. 解数独
37. 解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
class Solution {
private:
/*
如果for循环中没有if (backtracking(board)) return true;会怎样? 即不判断返回值,只进行递归。
若只去掉这句返回值判断,其他返回代码保持不变。 那么即使找到正确的一个解,最底层返回true,上一个层对返回值无处理,会直接进入下一个for循环数字。 直至所有数字遍历结束,会进行回溯,并返回上一层。 最终结果相当于填好了数独,又给全部撤销了。
*/
bool istrue(vector<vector<char>>& board,int hang ,int lie,char num){
for(int i = 0;i < 9;i++){
if(board[hang][i] == num)return false;
}//行
for(int i = 0;i < 9;i++){
if(board[i][lie] == num)return false;
}//列
int starthang = (hang / 3) * 3;
int startlie = (lie / 3) * 3;
for(int i = starthang;i < starthang + 3;i++){
for(int j = startlie;j < startlie + 3;j++){
if(board[i][j] == num)return false;
}
}
return true;
}
bool solve(vector<vector<char>>& board) {
for(int i = 0;i < 9;i++){
for(int j = 0;j < 9;j++){
if(board[i][j] != '.')continue;
for(int k = '1';k <= '9';k++){
if(istrue(board,i,j,k)){
board[i][j] = k;
if(solve(board))return true;
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
public:
void solveSudoku(vector<vector<char>>& board) {
solve(board);
}
};