代码随想录 回溯部分
什么情况下 需要用回溯:
1.当你需要循环的次数不确定时(即根据输入的k来确定循环的次数时)
2.
回溯算法能解决如下问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
有困难题目:
1.LC491 递增子序列
2.LC332 重新安排行程
3.
回溯模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
题
代码随想录
组合问题:
找出所有相加之和为 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]]
首先确定两个参数 vector<vector<int>> result; vector<int> path; 其中result存放最终结果,path存放一条路径;
接着用backtracking 参数没确定不要紧,往下写发现需要 k startIndex 和targetSum(加减 到零即符合条件)
然后套用模板
记得回溯前的操作回溯之后都要做!!
class Solution { private: vector<vector<int>> result; vector<int> path; void backtracking(int k,int targetSum,int startIndex){ if(path.size()==k){ if(targetSum==0) result.push_back(path); return; } for(int i=startIndex;i<=9;i++) { path.push_back(i); targetSum-=i; // if(targetSum<0){ targetSum+=i; path.pop_back(); return ; } // backtracking(k,targetSum,i+1); targetSum+=i; path.pop_back(); } } public: vector<vector<int>> combinationSum3(int k, int n) { backtracking(k,n,1); return result; } };
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
基础:1.map映射
2.字符串的基本处理(size() ;push_back(i) ;pop_back())
思路:先设置映射表 再进行回溯
注意现在的path即为一维的string 所以result即为一维的vector
对于处理第几个数字:用参数中的index表示 然后对应的for 需要遍历letters的长度次数!!注意这个
class Solution { private: const string letterMap[10] = { "", // 0 "", // 1 "abc", // 2 "def", // 3 "ghi", // 4 "jkl", // 5 "mno", // 6 "pqrs", // 7 "tuv", // 8 "wxyz", // 9 }; vector<string> result; string s; void backtracking(const string& digits,int index){ if(index==(digits.size())) { result.push_back(s); return; } int digit=digits[index]-'0';//将index 指向的char转化为数字 string letters=letterMap[digit]; for(int i=0;i<letters.size();i++)// { s.push_back(letters[i]); backtracking(digits,index+1); s.pop_back(); } } public: vector<string> letterCombinations(string digits) { s.clear(); result.clear(); if(digits.size()==0) return result; backtracking(digits,0); return result; } };
LC39 组合总和
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
思路:注意这个是可以重复选的,所以对于startindex来说,下次的递归不需要+1,而是从当前开始(注意参数是i 而非index!!!!)
区别于LC17 LC17中的index独立循环,这个的循环开始点应该是i
代码:
class Solution { private: vector<vector<int>> result; vector<int>path; void backtracking(int target,vector<int>& candidates,int startIndex){ if(target<0) return; if(target==0){ result.push_back(path); return; } for(int i=startIndex;i<candidates.size();i++) { target-=candidates[i]; path.push_back(candidates[i]); backtracking(target,candidates,i);//参数应该为i 而非index target+=candidates[i]; path.pop_back(); } } public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { backtracking(target,candidates,0); return result; } };
组合总和2
LC40
主要需要sort(vector.begin(),vector.end());判重:vector[startIndex]!=vector[startindex-1];
分割回文串:
LC131
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
思路:
1.判断回文串的函数---双指针
2.如何分割--startindex处开始,然后到i=string.size()结束
3.substr的用法:substr(int 指定的位置,复制的长度);
class Solution { private: vector<vector<string>> result; vector<string> path; bool isHuiWen(const string& s,int start,int end){ for(int i=start,j=end;i<j;i++,j--) { if(s[i]!=s[j]) return false; } return true; } void backtracking(const string& s ,int startIndex){ if(startIndex>=s.size()) { result.push_back(path); return; } for(int i=startIndex;i<s.size();i++) { if(isHuiWen(s,startIndex,i)){ string str=s.substr(startIndex,i-startIndex+1);//i-startindex+1? path.push_back(str); } else{ continue; } backtracking(s,i+1); path.pop_back(); } } public: vector<vector<string>> partition(string s) { result.clear(); path.clear(); backtracking(s,0); return result; } };
LC93 复原ip地址
注意回溯的参数s不能是const 因为要添加逗号
需要理解,判断字串是否合法的函数
class Solution { private: vector<string> result; bool isValid(const string&s,int start,int end){ if(start>end)return false; if(s[start]=='0'&&start!=end)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)return false; } return true; } void backtracking( string& s,int startindex,int pointnum){ if(pointnum==3) { if(isValid(s,startindex,s.size()-1)) {result.push_back(s);} return ; } for(int i=startindex;i<s.size();i++) { string str=s.substr(startindex,i-startindex+1); if (isValid(s,startindex,i)){ char c='.'; s.insert(s.begin()+i+1 , c); pointnum++; backtracking(s,i+2,pointnum); pointnum--; s.erase(s.begin()+i+1); } else{ break; } } } public: vector<string> restoreIpAddresses(string s) { backtracking(s,0,0); return result; } };
LC78 子集问题
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。、
思路:
此题于一般的回溯题目不同,这个要收集的是全部子集,即这棵树的全部节点,所以result.push_back(path)放在每次递归的开头
同时使用startIndex 来记录每次的开始位置,防止出现重读
class Solution { private: vector<vector<int>> result; vector<int>path; void backtracking(vector<int>&nums,int startIndex){ result.push_back(path); if(startIndex>=nums.size()){ return; } for(int i=startIndex;i<nums.size();i++) { path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } public: vector<vector<int>> subsets(vector<int>& nums) { path.clear(); result.clear(); backtracking(nums,0); return result; } };
LC90 子集2
思路同上,只是多一个去重,参考组合总和2
疑问:为什么
if(nums[i]==nums[i-1]&& i>startIndex)不能过
而if(i>startIndex&&nums[i]==nums[i-1]) 能过?
class Solution { private: vector<vector<int>>result; vector<int>path; void backtracking(vector<int>& nums,int startIndex) { result.push_back(path); for(int i=startIndex;i<nums.size();i++) { if(nums[i]==nums[i-1]&& i>startIndex){continue;} path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } public: vector<vector<int>> subsetsWithDup(vector<int>& nums) { result.clear(); path.clear(); sort(nums.begin(),nums.end()); backtracking(nums,0); return result; } };
LC419
难 需要再回顾
class Solution { private: vector<vector<int>> result; vector<int> path; void backtracking(vector<int>& nums,int startIndex){ if(path.size()>1){result.push_back(path);} unordered_set<int> uset;//使用set对本层元素去重 for(int i=startIndex;i<nums.size();i++){ if((!path.empty()&&nums[i]<path.back())|| uset.find(nums[i])!=uset.end()){ continue; } uset.insert(nums[i]); path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } public: vector<vector<int>> findSubsequences(vector<int>& nums) { result.clear(); path.clear(); backtracking(nums,0); return result; } };
LC46 全排列
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
思路:正常做,多加一个used数组,记得在主函数里面使用
class Solution { private: vector<vector<int>> result; vector<int> path; vector<bool>used; void backtraking(vector<int>&nums,vector<bool>used){ if(path.size()==nums.size()){ result.push_back(path); return; } for(int i=0;i<nums.size();i++) { if(used[i])continue; if(!used[i]){ path.push_back(nums[i]); used[i]=true; backtraking(nums,used); path.pop_back(); used[i]=false; } } } public: vector<vector<int>> permute(vector<int>& nums) { result.clear(); path.clear(); vector<bool> used(nums.size(),false); backtraking(nums,used); return result; } };
LC 47 全排列2
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
注意 这里的判断条件,什么时候需要剪纸,第一次就是漏了 &&used[i-1]==true) 导致没通过
if((i>0&&nums[i]==nums[i-1]&&used[i-1]==true)||used[i]==true)continue;
class Solution { private: vector<vector<int>>result; vector<int>path; void backtracking(vector<int>& nums,vector<bool>used){ 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)||used[i]==true)continue; path.push_back(nums[i]); used[i]=true; backtracking(nums,used); path.pop_back(); used[i]=false; } } public: vector<vector<int>> permuteUnique(vector<int>& nums) { result.clear(); path.clear(); vector<bool>used(nums.size(),false); sort(nums.begin(),nums.end()); backtracking(nums,used); return result; } };
LC 332 重新安排行程
问题很大
对于map set的不理解和不熟悉用法 需要重做理解
for(pair<const string,int>& target : targets[result[result.size()-1]]) 对这句代码也不理解
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
class Solution { private: unordered_map<string,map<string,int>> targets; bool backtracking(int ticketNum,vector<string>& result){ if(result.size()==ticketNum+1){ return true; } for(pair<const string,int>& target : targets[result[result.size()-1]]){ if(target.second>0){ result.push_back(target.first); target.second--; if(backtracking(ticketNum,result)) return true; result.pop_back(); target.second++; } } return false; } public: vector<string> findItinerary(vector<vector<string>>& tickets) { targets.clear(); vector<string> result; for(const vector<string>& vec:tickets){ targets[vec[0]][vec[1]]++; } result.push_back("JFK"); backtracking(tickets.size(),result); return result; } };
LC51 N皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
思路:
1. 要有一个判断是否有效的函数 isValid
2.选择回溯的原因:能够画出树状,并进行剪枝
3.backtraking(row+1,n,chessboard); 注意要row+1
4.vector<string> chessboard(n, string(n, '.'));//主函数中,需要初始化
class Solution { private: bool isValid(int row,int col,int n,vector<string>& chessboard){ //列检查 for(int i=0;i<row;i++){ if(chessboard[i][col]=='Q') return false; } //斜角都只往上查,因为是按行来遍历,只要上方没有交叉的就行 //45°检查 for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--) { if(chessboard[i][j]=='Q') return false; } //135°检查 for(int i=row-1,j=col+1;i>=0&&j>=0;i--,j++) { if(chessboard[i][j]=='Q') return false; } return true; } vector<vector<string>>result; void backtraking(int row,int n,vector<string>& chessboard){ if(row==n){ result.push_back(chessboard); return; } for(int col=0;col<n;col++){ // if(!isValid()) break; if(isValid(row,col,n,chessboard)){ chessboard[row][col]='Q'; backtraking(row+1,n,chessboard); chessboard[row][col]='.'; } } } public: vector<vector<string>> solveNQueens(int n) { result.clear();
backtraking(0,n,chessboard); return result; } };
LC 37 解数独
需要重刷
对于二重递归不熟练。
需要写isValid;
class Solution { private: vector<vector<char>>result; bool isValid(int row, int col, char val, vector<vector<char>>& board) { for (int i = 0; i < 9; i++) { // 判断行里是否重复 if (board[row][i] == val) { return false; } } for (int j = 0; j < 9; j++) { // 判断列里是否重复 if (board[j][col] == val) { return false; } } int startRow = (row / 3) * 3; int startCol = (col / 3) * 3; for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复 for (int j = startCol; j < startCol + 3; j++) { if (board[i][j] == val ) { return false; } } } return true; } bool backtracking(vector<vector<char>>& board){ for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ if(board[i][j]!='.')continue; for(char k='1';k<='9';k++) { if(isValid(i,j,k,board)){ board[i][j]=k; if(backtracking(board))return true; board[i][j]='.'; } } return false; }//这两层的目的是找到一个空格 } return true; } public: void solveSudoku(vector<vector<char>>& board) { backtracking(board); } };