回溯经典例题
全排列
非常经典的例题,用回溯方法帮助我们解决排列问题,回溯主要注意三个方面,终止条件,添加选择,去除选择
class Solution {
vector<vector<int>> res;
void backTrack(vector<int> nums, vector<int> temp, vector<bool>& visit){
if (temp.size() == nums.size()){//满足的条件
res.push_back(temp);
return;
}
for (int i=0; i<nums.size(); i++){
if (visit[i])//是否使用过
continue;
visit[i] = true;
temp.push_back(nums[i]);//添加选择
backTrack(nums, temp, visit);
visit[i] = false;
temp.pop_back();//去除选择
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
if (nums.empty()) return {};
vector<bool> visit(nums.size(), false);
backTrack(nums, {}, visit);
return res;
}
};
存在重复元素的排列
class Solution {
vector<vector<int>> res;
void backTrack(vector<int> nums, vector<int> temp, vector<bool>& visit){
if (temp.size() == nums.size()){//满足的条件
res.push_back(temp);
return;
}
for (int i=0; i<nums.size(); i++){
if (visit[i])//是否使用过
continue;
//判断重复元素
if (i>0 && visit[i-1] && nums[i]==nums[i-1])
continue;
visit[i] = true;
temp.push_back(nums[i]);//添加选择
backTrack(nums, temp, visit);
visit[i] = false;
temp.pop_back();//去除选择
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
if (nums.empty()) return {};
vector<bool> visit(nums.size(), false);
sort(nums.begin(), nums.endl());//注意先排序
backTrack(nums, {}, visit);
return res;
}
};
组合总数
candidates = {2,3,5,7} target = 7
这是非常经典的组合问题,按照以下模板。分为元素可重复使用和不可重复使用
class Solution {
public:
vector<vector<int>> res;
void backTrack(int now, int sum, int target, vector<int> temp, vector<int>& candidates){
if(sum > target) return;//剪枝
if(sum == target){//终止条件
res.push_back(temp);
return;
}
//注意这里的now,不是0,因为这是组合问题,不是排列
for(int i=now; i<candidates.size(); i++){
sum += candidates[i];//添加选择
temp.push_back(candidates[i]);
//这里是允许元素重复使用的,如[5,5,7]
backTrack(i, sum, target, temp, candidates);
sum -= candidates[i];
temp.pop_back();//去除选择
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target){
vector<int> temp;
backTrack(0, 0, target, temp, candidates);
return res;
}
};
go 语言版本,注意二维slice的值拷贝问题
func combinationSum(candidates []int, target int) [][]int {
res := make([][]int, 0)
temp := make([]int, 0)
backTrack(0, candidates, target, &res, temp) // 注意res一定是指针类型,函数内部会发生append
return res
}
func backTrack(now int, candidates []int, target int, res *[][]int, temp []int) {
if target < 0 {
return
}
if target == 0 {
track := make([]int, len(temp))
copy(track, temp) // 注意,二维slice添加时,一定要值拷贝
*res = append(*res, track)
return
}
for i := now; i < len(candidates); i++ {
temp = append(temp, candidates[i])
backTrack(i, candidates, target-candidates[i], res, temp)
temp = temp[ : len(temp)-1]
}
}
class Solution {
public:
vector<vector<int>> res;
void backTrack(int now, int sum, int target, vector<int> temp, vector<int>& candidates){
if(sum > target) return;//剪枝
if(sum == target){//终止条件
res.push_back(temp);
return;
}
//注意这里的now,不是0,因为这是组合问题,不是排列
for(int i=now; i<candidates.size(); i++){
sum += candidates[i];//添加选择
temp.push_back(candidates[i]);
//这里是不允许元素重复使用的,如[2,3,5,7]
backTrack(i+1, sum, target, temp, candidates);
sum -= candidates[i];
temp.pop_back();//去除选择
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target){
vector<int> temp;
backTrack(0, 0, target, temp, candidates);
return res;
}
};
存在重复元素的组合
acab
output
a aa aab aabc aac ab abc ac b bc c
#include <bits/stdc++.h>
using namespace std;
vector<string> res;
void backTrack(int now, string str, string temp, vector<bool> visit, int k){
if (temp.length() > k){//剪枝
return;
}
res.push_back(temp);
for (int i=now; i<str.length(); i++){
if (i>0 && !visit[i-1] && str[i]==str[i-1])//剪枝,去除重复元素
continue;
visit[i] = true;
temp += str[i]; //这里进行了修改,那么后面一定有对应的还原回去
backTrack(i+1, str, temp, visit, k);
visit[i] = false;
temp = temp.substr(0, temp.length()-1);//这里还原
}
}
int main(){
string str; int k;
while (cin >> str >> k){
sort(str.begin(), str.end());
vector<bool> visit(str.length(), false);
backTrack(0, str, "", visit, k);
for (int i=0; i<res.size(); i++)
cout << res[i] << ' ';
}
return 0;
}
24游戏
给出4个数,运用加减乘除凑齐24
经典解法,递归,递归从头到尾只跑一遍,中间递归,不同于回溯,回溯从任意地方开始递归。
#include <bits/stdc++.h>
using namespace std;
char sign[4] = {'+', '-', '*', '/'};
bool res = false;
void backTrack(int i, vector<int> arr, vector<bool>& visit, double sum, int count){
if (count == 4 && (int)sum == 24){
res = true;
return;
}
if (i >= arr.size())
return;
if (!visit[i]){
for (int j=0; j<4; j++){
switch (sign[j]){//选择
case '+': sum+=arr[i]*1.0; break;
case '-': sum-=arr[i]*1.0; break;
case '*': sum*=arr[i]*1.0; break;
case '/': if (arr[i]!=0) sum/=arr[i]*1.0; break;
default: break;
}
backTrack(i+1, arr, visit, sum, count+1);//选择
}
}
else
backTrack(i+1, arr, visit, sum, count);
}
int main(){
vector<int> arr; int i;
while (cin >> i){
arr.push_back(i);
}
if (arr.size() != 4){
cout << "input error!" << endl;
return 0;
}
for (int i=0; i<arr.size(); i++){
vector<bool> visit(arr.size(), false); visit[i] = true;
backTrack(0, arr, visit, (double)arr[i], 1);
}
cout << res << endl;
return 0;
}
括号生成 中等
递归,从头到尾,然后中间就是选择
class Solution {
vector<string> res;
void helper(string s, int ln, int rn, int n){
if (ln < 0 || rn < 0) return;
if (s.length() == n && ln == rn) res.push_back(s);
helper(s+'(', ln-1, rn, n);//选择
if (ln < rn)
helper(s+')', ln, rn-1, n);//选择
}
public:
vector<string> generateParenthesis(int n) {
helper("", n, n, 2*n);
return res;
}
};
电话号码组合问题
回溯思路,但是只走一遍,所以注意return是在for里面。
func letterCombinations(digits string) []string {
if len(digits) == 0 {
return []string{}
}
s := []string {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
res := make([]string, 1)
backTrack(0, s, digits, &res) // 注意res一定是指针类型,函数内部会发生append
return res
}
func backTrack(now int, s []string, digits string, res *[]string) {
for i := now; i < len(digits); i++ {
if len((*res)[0]) >= len(digits) { // 不是组合问题,只走一遍循环,所以判断要放for里面
return
}
num := digits[i] - '0'
temp := make([]string, 0)
for m := 0; m < len(*res); m++ {
for n := 0; n < len(s[num]); n++ {
str := (*res)[m] + string(s[num][n])
temp = append(temp, str)
}
}
(*res) = temp
backTrack(i+1, s, digits, res)
}
}
最近做笔试题,很多题基本是回溯方法,但是就是会超时,不是回溯本身的问题,组合排列本身不存在重复,所以方法应该是没问题的,用迭代也是一样的时间复杂度。所以唯一能做的就是剪枝或者完全应该是另外解题思路。