回溯总结
组合问题
509. 斐波那契数
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
class Solution {
public:
int reverse(int n){
if(n<2) return n;
return reverse(n-1)+reverse(n-2);
}
int fib(int n) {
return reverse(n);
}
};
\77. 组合
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
class Solution {
public:
vector<vector<int> > result;
vector<int> path;
void backtracking(int n, int k,int index){
if(path.size()==k){
result.push_back(path);
return;
}
for(int i = index;i<=n;i++){
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
};
一层递归就是一层for,看代码顺序先遍历到满足k的最后一个数,然后依次向上返回判断逻辑
2611 老鼠奶酪
有两只老鼠和 n 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。
下标为 i 处的奶酪被吃掉的得分为:
如果第一只老鼠吃掉,则得分为 reward1[i] 。
如果第二只老鼠吃掉,则得分为 reward2[i] 。
给你一个正整数数组 reward1 ,一个正整数数组 reward2 ,和一个非负整数 k 。
请你返回第一只老鼠恰好吃掉 k 块奶酪的情况下,最大 得分为多少
class Solution {
public:
int sum(vector<int>& num){
int sum =0;
for(auto c:num) sum+=c;
return sum;
}
vector<int> path;
int sum_max=0;
void backtracking(vector<int>& reward1, vector<int>& reward2,int& sum_2, int& k,int index){
if(path.size()==k){
sum_max = max(sum_max,sum(path)+sum_2);
return;
}
for(int i =index;i<reward1.size();i++){
path.push_back(reward1[i]-reward2[i]);
backtracking(reward1,reward2,sum_2,k,i+1);
path.pop_back();
}
}
int miceAndCheese(vector<int>& reward1, vector<int>& reward2, int k) {
int sum_2 = sum(reward2);
backtracking(reward1,reward2,sum_2,k,0);
return sum_max;
}
};
这道题目中,需要通过数学公式,把两个数组抽象为1个,然后进行组合的回溯
1010. 总持续时间可被 60 整除的歌曲
在歌曲列表中,第 i 首歌曲的持续时间为 time[i] 秒。
返回其总持续时间(以秒为单位)可被 60 整除的歌曲对的数量。形式上,我们希望下标数字 i 和 j 满足 i < j 且有 (time[i] + time[j]) % 60 == 0。
class Solution {
public:
int numPairsDivisibleBy60(vector<int>& time) {
int vec[61] = {};
int result=0;
for(int i=0;i<time.size();i++){
int tmp = time[i];
result+=vec[(60-tmp%60)%60];
vec[tmp%60]++;
}
return result;
}
};
本题回溯时间复杂度太高,考虑通过数组存一个遍历,将问题转化为单变量问题
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> result;
unordered_map<int,int> dict;
for(int i=0;i<nums.size();i++){
int tmp = nums[i];
if(dict[target-tmp]!=0){
result.push_back(i);
result.push_back(dict[target-tmp]-1);
}
dict[tmp] = i+1;
}
return result;
}
};
在通过遍历某一变量,进行变量降维时,核心思想是建立一个值到个数的索引。如果知道元素的个数,可以用数组。如果元素个数不好处理,直接用哈希表。其中map.count(a)不会增加计数,返回bool值
切割问题
131.分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
class Solution {
public:
vector<string> path;
vector<vector<string>> result;
void backtracking(string& s,int index){
if(index>=s.size()){
result.push_back(path);
return ;
}
for(int i = index;i<s.size();i++){
string str = s.substr(index,i-index+1);
if(adjust(str)){
path.push_back(str);
backtracking(s,i+1);
path.pop_back();
}
}
}
bool adjust(string& a){
int i = 0;
int j = a.size()-1;
while(i<j){
if(a[i]!=a[j]) return false;
i++;
j--;
}
return true;
}
vector<vector<string>> partition(string s) {
backtracking(s,0);
return result;
}
};
在回溯的分割任务中,index可以理解为分割的起始位置,而外部循环的i的后面为隔板,概念类似于end。所以要分割的第一个串即为index到i的字符。代码为s.substr(index,i-index+1),第二个参数为要选择的元素个数
并且在判断回文的时候,由于需要对首尾指针进行数值判断,所以j为s.size()-1.否则会导致每次都判断为false,导致result为空。
子集问题
排列问题
棋盘问题
迷宫问题
#include<bits/stdc++.h>
using namespace std;
int a, b;
vector<string> path;
vector<vector<string>> result;
void backtracking(vector<vector<int>>& data,int x,int y){
int cur = data[x][y];
int n =data[0].size();
int n1 = data.size();
if(cur!=0) return;
if(cur==0&&x==(n1-1)&&y==(n-1)){
path.push_back("(");
path.push_back(to_string(x));
path.push_back(",");
path.push_back(to_string(y));
path.push_back(")");
result.push_back(path);
return ;
}
if(cur==0){
data[x][y]=1;
path.push_back("(");
path.push_back(to_string(x));
path.push_back(",");
path.push_back(to_string(y));
path.push_back(")");
if(x<n1-1)backtracking(data, x+1, y);
if(y<n-1)backtracking(data, x, y+1);
if(x>0)backtracking(data, x-1, y);
if(y>0)backtracking(data, x, y-1);
path.pop_back();
path.pop_back();
path.pop_back();
path.pop_back();
path.pop_back();
}
}
int main() {
int tmp;
cin>>a>>b;
vector<vector<int>> data(a,vector<int>(b,1));
for(int i=0;i<a;i++){
for(int j=0;j<b;j++){
cin>>tmp;
data[i][j] = tmp;
}
}
backtracking(data, 0, 0);
for(int i =0;i<result.size();i++){
int index=0;
for(int j=0;j<result[i].size();j++){
cout<<result[i][j];
index++;
if(index%5==0) cout<<endl;
}
}
return 0;
}
// 64 位输出请用 printf("%lld")
这道题目中,由于每次都是前进一次,后退一次,所以四个方向判断,只需要回溯一次。
并且每走过一条路,将来路堵死,避免重复搜索
二叉树问题
494. 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
class Solution {
public:
int ans;
int sum=0;
void backtracking(vector<int>&nums,int target,int index){
// if(sum>target) return;
if(nums.size()==0&&target==0) return;
if(index==nums.size()){
if(sum==target) ans++;
return;
}
sum+=nums[index];
backtracking(nums,target,index+1);
sum-=nums[index];
sum-=nums[index];
backtracking(nums,target,index+1);
sum+=nums[index];
}
int findTargetSumWays(vector<int>& nums, int target) {
backtracking(nums,target,0);
return ans;
}
};
回溯不是所有情况都需要for循环,当第一层只需要选择一次时,可以不用for循环。然后在每次回溯的时候直接使用index+1