1863. 找出所有子集的异或总和再求和
之前我们就做了到关于找集合子集的问题,但我们不需要记录路径上的数,求路径上数的异或和就可以。
class Solution {
int path;
int sum=0;
public:
int subsetXORSum(vector<int>& nums) {
dfs(nums,0);
return sum;
}
void dfs(vector<int>&nums,int pos)
{
sum+=path;
for(int i=pos;i<nums.size();i++)
{
path^=nums[i];
dfs(nums,i+1);
path^=nums[i];//恢复现场 A^A==0
}
}
};
47. 全排列 II
之前做的全排列的题是不含重复的数字,但这道题有重复的数字且返回不重复的全排列。
选数时要满足两个条件:
1.在一条路径上同一个数只能选一次(不是值相同的数),和全排列1一样用check[]标记该数是否用过。
2.在同一个节点的所有分支中,相同值的元素只能选一次。
eg.[1,1,1,2] 看似可以选4个数,但为了不重复第一个数只能选1_ _ _ 2_ _ _。
当我们选了第一个1,第二个1第三个1就不能选了,因为以第2 3个1开头的数和第一个1都是值一样的。
所以我们选数时1.该数没有被用过 2.如果该数和前一个数相同也不能选,但看图中左下分支
3.如果和前一个数相同,但前一个数已经被用过了 (可以理解为和其它数不在同一个分支上) 还是可以选的。(因为要判断和前一个数是否相同 所以先sort()排序)
4.i==0时 i-1会越界 只要i==0且该数每被用过就可以选
反过来我们也可以从不合法的分支入手,1.用过直接返回2.没用过 首先不是第一个数(因为没有i-1)该数和前一个相等 且前一个数没有被用过。
class Solution {
vector<vector<int>> re;
vector<int> path;
bool check[9];
int n;
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(),nums.end());
n=nums.size();
dfs(nums);
return re;
}
void dfs(vector<int>& nums)
{
if(path.size()==n)
{
re.push_back(path);
return;
}
for(int i=0;i<n;i++)
{
//只关心合法分支
if(check[i]==false&&(i==0||nums[i]!=nums[i-1]||check[i-1]==true))
{
path.push_back(nums[i]);
check[i]=true;
dfs(nums);
//回溯
path.pop_back();
check[i]=false;
}
}
}
};
17. 电话号码的字母组合
1.先建立数字和字符串的映射关系。可以用字符串数组代替哈希表,数组前两个设为空。
2.传参数pos表示要找数字下标,用数字根据映射关系找到对应的字符串,遍历字符串进行递归。
3.处理特殊情况 digits=""没有数字直接返回空,进行dfs会push""字符串
class Solution {
vector<string> re;
vector<string> hash={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
string path;
public:
vector<string> letterCombinations(string digits) {
if(digits=="") return re;
dfs(digits,0);
return re;
}
void dfs(string digits,int pos)
{
if(path.size()==digits.size())
{
re.push_back(path);
return;
}
//pos表示递归到第几层 有几个数字就有几层
for(auto&ch:hash[digits[pos]-'0'])
{
path+=ch;
dfs(digits,pos+1);
//回溯
path.pop_back();
}
}
};
22. 括号生成
递归返回的路径字符数肯定是n*2的,我们直接递归枚举出所有的情况,再进行剪枝剪去不能组成括号的情况:1.左括号数不能大于n 2.在递归过程中右括号的数不能大于左括号的数
递归终止条件:path==n*2 或者右括号数==n
class Solution {
vector<string> re;
string path;
public:
vector<string> generateParenthesis(int n) {
dfs(n,0,0);
return re;
}
void dfs(int n,int l,int r)
{
if(r>l||l>n) return;//剪枝
if(path.size()==n*2)
{
re.push_back(path);
return;
}
path+="(";;
dfs(n,l+1,r);
path.pop_back();//恢复现场
path+=")";
dfs(n,l,r+1);
path.pop_back();
}
};
77. 组合
因为不能出现重复的eg.[1,2] [2,1],所以在递归时要进行剪枝 eg.2_ _ _选数时不能选自身 也不能选前面的数,只能选后面的数。
dfs函数参数pos传下一个要遍历的数
class Solution {
vector<vector<int>> re;
vector<int> path;
int n,k;
public:
vector<vector<int>> combine(int _n, int _k) {
n=_n+1,k=_k;
dfs(1);
return re;
}
void dfs(int pos)
{
if(path.size()==k)
{
re.push_back(path);
return;
}
for(int i=pos;i<n;i++)
{
path.push_back(i);
dfs(i+1);//注意是i+1 不是pos+1
//回溯
path.pop_back();
}
}
};
i代表了当前递归层选择的数字,因此我们应该从i+1开始递归,确保下一次选择当前数字的后面。
494. 目标和
这道题和我们之前做的求子集时,用的方法一相同。对一个数选和不选,到这道题就是加和减。暴力枚举每种情况的加和减,统计符合条件的数。
1.传参数,pos指对第几个元素进行+/-
2.定义int path记录最终的和。(可以定义成全局的,也可以传参 定义成局部的,这样就不用恢复现场)
3.到最后一个数 path==target re++ 返回
1.path局部
class Solution {
int count=0;
int target;
public:
int findTargetSumWays(vector<int>& nums, int _target) {
target=_target;
dfs(nums,0,0);
return count;
}
void dfs(vector<int>& nums,int pos,int path)
{
if(pos==nums.size())
{
if(path==target) count++;
return;
}
//1.+
dfs(nums,pos+1,path+nums[pos]);
//2.-
dfs(nums,pos+1,path-nums[pos]);
}
};
dfs(nums,pos+1,path+=nums[pos]); 不能写成+=,不能改变原本局部变量path的值
2.path全局
class Solution {
int count=0;
int path=0;
int target;
public:
int findTargetSumWays(vector<int>& nums, int _target) {
target=_target;
dfs(nums,0);
return count;
}
void dfs(vector<int>& nums,int pos)
{
if(pos==nums.size())
{
if(path==target) count++;
return;
}
//1.+
path+=nums[pos];
dfs(nums,pos+1);
path-=nums[pos];
//2.-
path-=nums[pos];
dfs(nums,pos+1);
path+=nums[pos];
}
};
39. 组合总和
数组每个元素不同,但可以重复选。
[2,3,5],第一个数选了2 后面还可以选2/3/5。
但第一个数选了3(数组中第二个元素),后面还可以选2吗?不能选,会出现重复情况。eg.[2,3,3] [3,2,3]。所以当我们选完一个数后,选下一个数时,只能选该数和其后面的数。
pos记录选到数组的第几个数
class Solution {
vector<vector<int>> re;
vector<int> path;
int target;
public:
vector<vector<int>> combinationSum(vector<int>& nums, int _target) {
target = _target;
dfs(nums, 0, 0);
return re;
}
void dfs(vector<int>& nums, int sum, int pos) {
if (sum == target)
{
re.push_back(path);
return;
}
//超过目标值||pos越界
if (sum > target || pos == nums.size())
return;
for (int i = pos; i < nums.size(); i++)
{
path.push_back(nums[i]);
dfs(nums, sum + nums[i], i);
path.pop_back();
}
}
};
注意可以选重复的数,所以要包含自身 dfs(nums,sum+nums[i],i) 不是i+1,i+1是只选该数后面的情况。
解法二:
我们可以先对选数组第一个数,从0开始累加直到大于目标值。0 2 4 6 8 再从小于目标值的情况中继续选数组第二个元素,同理也是不断累加直到大于目标值。
注意:在累积的过程中是不进行回溯的,在原基础上再加1次。等向上层返回时再进行回溯。
class Solution {
vector<vector<int>> re;
vector<int> path;
int target;
public:
vector<vector<int>> combinationSum(vector<int>& nums, int _target) {
target = _target;
dfs(nums, 0, 0);
return re;
}
void dfs(vector<int>& nums, int sum, int pos) {
if (sum == target)
{
re.push_back(path);
return;
}
if (sum > target || pos == nums.size())
return;
//将所有<=target情况push进去 <情况进入下一层累加 =记录并返回
for(int k=0;sum+k*nums[pos]<=target;k++)
{
//k!=0进行push
if(k) path.push_back(nums[pos]);
dfs(nums,sum+k*nums[pos],pos+1);
}
//push几次就回溯几次 因为k==0 没有进行push所以这种情况不能进行pop 从k==1情况算起
for(int k=1;sum+k*nums[pos]<=target;k++)
{
path.pop_back();
}
}
};
784. 字母大小写全排列
通过改变原字符串中的字母大小写,变成不同的字符串,问一共有多少种情况?
这个字符串里面有数字,我们可以用数组记录每个字母在字符串的下标,通过遍历数组间接遍历字符串中的字母。
遍历字符串字母,暴力枚举所有可能的情况。
pos记录要改变字母位置,改变完后选下一个要选改数后面的,选前面的会出现重复情况,传i+1。(i当前遍历的位置)
class Solution {
vector<string> re;
string path;
vector<int> sub;
public:
vector<string> letterCasePermutation(string s) {
path=s;
for(int i=0;i<s.size();i++) if(islower(s[i])||isupper(s[i])) sub.push_back(i);
dfs(0);
return re;
}
char toggle_case(char c)
{
if(isupper(c)) return tolower(c); //c+32变小写
else if(islower(c)) return toupper(c); //c-32变大写
else return c;
}
void dfs(int pos)
{
re.push_back(path);
for(int i=pos;i<sub.size();i++)
{
path[sub[i]]=toggle_case(path[sub[i]]);//改变大小写
dfs(i+1);
path[sub[i]]=toggle_case(path[sub[i]]);//回溯
}
return;
}
};