代码随想录-笔记-其六
回溯算法
模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
回溯算法本质上是穷举方法,并不是很聪明的算法,所以基本只在特定的无其他方法可用的前提下使用,包括组合问题,分割问题,子集问题等。
组合问题
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(int n, int k,int index){
if(temp.size()==k){
res.push_back(temp);
return;
}
for(int i=index;i<=n;++i){
temp.push_back(i);
backtrack(n, k,i+1);
temp.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
backtrack(n, k, 1);
return res;
}
};
这个题是比较标准的回溯模板题,我们只需要记录一个序号即可。
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(int k, int n,int sum,int index){
if(sum==n&&temp.size()==k){
res.push_back(temp);
return;
}
if(sum>n||temp.size()>k)return;
for(int i=index;i<=9;i++){
temp.push_back(i);
backtrack(k, n, sum+i,i+1);
temp.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtrack(k, n, 0, 1);
return res;
}
};
这个题相比起之前的题来说是多了三个限制条件的回溯算法题,一是要求和为n,二是要求数字的个数为k,三就是要求数字在1到9之间,不过本身逻辑并没有更加复杂,我们只需要依然按着模板来写即可。
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
class Solution {
public:
unordered_map<char,string> mp=
{
{'2',"abc"},{'3',"def"},{'4',"ghi"},{'5',"jkl"},{'6',"mno"},{'7',"pqrs"},{'8',"tuv"},{'9',"wxyz"}
};
vector<string> res;
string temp;
void backtrack(string digits,int index){
if(index==digits.size()){
res.push_back(temp);
return;
}
char digit=digits[index];
string str=mp[digit];
for(char ch:str){
temp.push_back(ch);
backtrack(digits, index+1);
temp.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0)return res;
backtrack(digits, 0);
return res;
}
};
如果可以的话不想再写这个题了(这个哈希表写起来太麻烦了),我们用一个哈希表构建起字符与字符串的联系,然后维护一个序号之后就进行正常的回溯过程即可。
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& candidates, int target, int index) {
if (target == 0) {
res.push_back(temp);
return;
}
if (index >= candidates.size() || target < 0) {
return;
}
if (target - candidates[index] >= 0) {
temp.push_back(candidates[index]);
backtrack(candidates, target - candidates[index], index);
temp.pop_back();
}
backtrack(candidates, target, index + 1);
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtrack(candidates, target, 0);
return res;
}
};
这个题是可以允许元素重复的情况,所以我们要分两步考虑:第一步考虑只使用当前元素的情况,第二步就是将序号更新的情况。
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& candidates, int target,int index){
if(target==0){
res.push_back(temp);
return;
}
if(target<0||index>=candidates.size())return;
for(int i=index;i<candidates.size();++i){
if(i>index&&candidates[i]==candidates[i-1])continue;
if(target-candidates[i]>=0){
temp.push_back(candidates[i]);
backtrack(candidates, target-candidates[i], i+1);
temp.pop_back();
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
backtrack(candidates, target, 0);
return res;
}
};
这个是需要考虑情况最复杂的一题,我们需要去重,所以需要进行排序(不排序的话无法利用这段代码中的方法去重)。
分割问题
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
class Solution {
public:
vector<vector<string>> res;
vector<string> temp;
vector<vector<int>> f;
vector<vector<string>> partition(string s) {
int n=s.size();
f.assign(n,vector<int>(n,true));
for(int i=n-1;i>=0;--i){
for(int j=i+1;j<n;++j){
f[i][j]=(s[i]==s[j]&&f[i+1][j-1]);
}
}
backtrack(s, 0);
return res;
}
void backtrack(string s,int index){
if(index==s.size()){
res.push_back(temp);
return;
}
for(int i=index;i<s.size();i++){
if(f[index][i]){
temp.push_back(s.substr(index,i-index+1));
backtrack(s, i+1);
temp.pop_back();
}
}
}
};
子集问题
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& nums,int index){
res.push_back(temp);
for(int i=index;i<nums.size();++i){
temp.push_back(nums[i]);
backtrack(nums, i+1);
temp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtrack(nums, 0);
return res;
}
};
对于所有的子集,我们每一次递归调用backtrack的最开始都将temp加入到res即可,由于原数组中不包含重复的元素,我们回溯的过程会自然而然地去掉重复的子集。
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& nums,int index,vector<bool>& used){
res.push_back(temp);
if(index==nums.size()){
return;
}
for(int i=index;i<nums.size();++i){
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
continue;
}
used[i]=true;
temp.push_back(nums[i]);
backtrack(nums, i+1, used);
temp.pop_back();
used[i]=false;
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool> used(nums.size(),false);
sort(nums.begin(), nums.end());
backtrack(nums, 0,used);
return res;
}
};
这个题相比起上一道子集题来说,主要是添加了对树层遍历的去重要求,所以我们需要添加一些去重的功能。
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& nums,int index){
if(temp.size()>=2){
res.push_back(temp);
}
int used[201]={0};
for(int i=index;i<nums.size();++i){
if(!temp.empty()&&nums[i]<temp.back()||used[nums[i]+100]==1){
continue;
}
used[nums[i]+100]=1;
temp.push_back(nums[i]);
backtrack(nums, i+1);
temp.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtrack(nums, 0);
return res;
}
};
这个题相比起之前的主要区别在于:他要求顺序上的一贯,因此我们不能直接使用sort函数对原数组的顺序进行变动,所以我们就需要一个哈希表来对每一个树枝(每一个递归过程)使用过的数字进行记录。用数组记录的运算速度会显著提升,但问题在于数组的索引大小(数组的索引不能为负),所以我们根据给的数的范围将其修正为非负数即可。
排列问题
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& nums,vector<bool>& used){
if(temp.size()==nums.size()){
res.push_back(temp);
return;
}
for(int i=0;i<nums.size();++i){
if(used[i]==true){
continue;
}
used[i]=true;
temp.push_back(nums[i]);
backtrack(nums, used);
temp.pop_back();
used[i]=false;
}
}
vector<vector<int>> permute(vector<int>& nums)
{
vector<bool> used(nums.size(),false);
backtrack(nums, used);
return res;
}
};
排列问题相比起子集问题有两个显著的差别:回溯中的序号变化都是从0开始(backtrack函数中的for循环),以及去重的时候检查的元素也显然不同(子集问题中我们要检查的是树层的重复元素,排列的时候我们要检查的是树枝的重复元素)。
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
void backtrack(vector<int>& nums,vector<bool>& used){
if(temp.size()==nums.size()){
res.push_back(temp);
return;
}
for(int i=0;i<nums.size();++i){
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
continue;
}
if(used[i]==false){
used[i]=true;
temp.push_back(nums[i]);
backtrack(nums, used);
temp.pop_back();
used[i]=false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used(nums.size(),false);
sort(nums.begin(), nums.end());
backtrack(nums, used);
return res;
}
};
不难发现,当原数组包含重复元素时,我们常常考虑使用used数组(其实是一个bool类型的容器)来记录是否使用过重复元素。
一些困难题
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
class Solution {
public:
unordered_map<string,map<string,int>> mp;
bool backtrack(int ticketNum,vector<string>& res){
if(ticketNum+1==res.size())return true;
for(pair<const string,int>& temp:mp[res.back()]){
if(temp.second>0){
temp.second--;
res.push_back(temp.first);
if(backtrack(ticketNum, res))return true;
res.pop_back();
temp.second++;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
vector<string> res;
for(vector<string>& vec:tickets){
mp[vec[0]][vec[1]]++;
}
res.push_back("JFK");
backtrack(tickets.size(), res);
return res;
}
};
这个题还是有一定难度的,本质上其实是dfs算法,但是我们需要用回溯来穷举所有的可能的行程。
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
class Solution {
public:
vector<vector<string>> res;
void backtrack(int n,int row,vector<string>& chessboard){
if(row==n){
res.push_back(chessboard);
return;
}
for(int col=0;col<n;++col){
if(isVaild(row, col, chessboard, n)){
chessboard[row][col]='Q';
backtrack(n, row+1, chessboard);
chessboard[row][col]='.';
}
}
}
bool isVaild(int row,int col,vector<string>& chessboard,int n){
for(int i=0;i<row;++i){
if(chessboard[i][col]=='Q')return false;
}
for(int i=row-1,j=col-1;i>=0&&j>=0;--i,--j){
if(chessboard[i][j]=='Q')return false;
}
for(int i=row-1,j=col+1;i>=0&&j<n;--i,++j){
if(chessboard[i][j]=='Q')return false;
}
return true;
}
vector<vector<string>> solveNQueens(int n) {
res.clear();
vector<string> chessboard(n,string(n,'.'));
backtrack(n, 0, chessboard);
return res;
}
};
大名鼎鼎的N皇后问题,核心就在于如何检测在同行、同列、45度方向与135度方向是否已存在棋子。
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
class Solution {
public:
bool backtrack(vector<vector<char>>& board){
for(int i=0;i<board.size();++i){
for(int j=0;j<board[0].size();++j){
if(board[i][j]=='.'){
for(char k='1';k<='9';++k){
if(isvaild(i,j,k,board)){
board[i][j]=k;
if(backtrack(board))return true;
board[i][j]='.';
}
}
return false;
}
}
}
return true;
}
bool isvaild(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 i=0;i<9;++i){
if(board[i][col]==val)return false;
}
int startRow=(row/3)*3,startCol=(col/3)*3;
for(int i=startRow;i<startRow+3;++i){
for(int j=startCol;j<startCol+3;++j){
if(board[i][j]==val)return false;
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backtrack(board);
}
};
原文地址:https://blog.csdn.net/lastXuanGod/article/details/144212587
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!