递归、回溯法
回溯法又称为试探法,但是当探索到某一步时,发现原先选择达不到目标,就退回一部重新选择,这种走不通就退回重新再走的技术称为回溯法。
有向无环图:
78. 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
转换为是否选择[1],是否选择[2],是否选择[3]
递归树:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> item;
result.push_back(item);
generate(0,nums,item,result);
return result;
}
private:
void generate(int i,vector<int>& nums,vector<int>& item,vector<vector<int> >&result)
{
if(i>=nums.size())
return;
item.push_back(nums[i]);
result.push_back(item);//生成的子集放入结果当中
generate(i+1,nums,item,result);//放入
item.pop_back();
generate(i+1,nums,item,result);//不放入
}
};
void bit_generate(vector<int>& nums,vector<vector<int> >&result)
{
int num=nums.size();
int all_set=1<<num;
for(int i=0;i<all_set;++i)
{
vector<int> item;
for(int j=0;j<num;++j)
{
if(i&(1<<j))
item.push_back(nums[j]);
}
result.push_back(item);
}
}
90. 子集 II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
set<vector<int> > res_set;
vector<vector<int> > result;
bit_generate(nums,res_set,result);
return result;
}
void bit_generate(vector<int>& nums,set<vector<int> >&res_set,vector<vector<int> >& result)
{
int num=nums.size();
int all_set=1<<num;
for(int i=0;i<all_set;++i)
{
vector<int> item;
for(int j=0;j<num;++j)
{
if(i&(1<<j))
item.push_back(nums[j]);
}
sort(item.begin(),item.end());
if(res_set.find(item)==res_set.end())
{
result.push_back(item);
res_set.insert(item);
}
}
}
};
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
剪枝:(在深度搜索中十分常用)
nums[]=[10,1,2,7,6,1,5],target=8
[10]>targt,那么包含[10,…]一定不满足目标
[1,2,7]>target,那么包含[1,2,7,…]的子集一定不满足条件
过多的错误尝试!
此时我们需要剪枝,把一些结点已经等于或者大于目标的之后的分支全部剪除,不再进行回溯。
这个问题应该先排序。
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target){
vector<vector<int> > result;
set<vector<int> > res_set;
vector<int> item;
sort(candidates.begin(),candidates.end());
generate(0,target,candidates,item,result,res_set);
return result;
}
private:
void generate(int i,int target,vector<int>& candidates, vector<int>& item,vector<vector<int> >&result,set<vector<int> >&res_set)
{
if(i>=candidates.size()||accumulate(item.begin(),item.end(),0)>target)
return;
item.push_back(candidates[i]);
sort(item.begin(),item.end());
if(accumulate(item.begin(),item.end(),0)==target&&res_set.find(item)==res_set.end()){
res_set.insert(item);
result.push_back(item);//生成的子集放入结果当中
}
generate(i+1,target,candidates,item,result,res_set);//放入
if(!item.empty())
item.pop_back();
generate(i+1,target,candidates,item,result,res_set);//不放入
}
};
22. 括号生成
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
思考:n组括号,有多少种组合的可能?
n组括号,括号的字符串长度为2*n,字符串中的每个字符有两种选择的可能,“(”和“)”,故有22n种可能。
如n=2组,那么16种可能,满足哪些是合法的?有多少种合法的可能?
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
generate(n,n,"",result);
return result;
}
private:
void generate(int left,int right,string item,vector<string>& result)//left 左括号剩下,右括号剩下
{
if(left==0&&right==0)
{
result.push_back(item);
return;
}
if(left>0)
{
generate(left-1,right,item+'(',result);
}
if(left<right)
{
generate(left,right-1,item+')',result);
}
}
};
51. N皇后 <重要!!!>
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> result;//保存结果的数组
vector<vector<int> > mark;//标记棋盘
vector<string> location;//存储某个摆放结果
//初始化容器数据
for(int i=0;i<n;i++)
{
mark.push_back(vector<int>());
for(int j=0;j<n;j++)
{
mark[i].push_back(0);
}
location.push_back("");
location[i].append(n,'.');
}
generate(0,n,location,result,mark);
return result;
}
private:
//k表示完成了几个皇后的位置(正在放置第k行皇后)
//n表示n皇后问题,n行n列
void generate(int k,int n,vector<string> &location,
vector<vector<string> > &result,vector<vector<int> > &mark)
{
if(k==n)
{
result.push_back(location);
return;
}
for(int i=0;i<n;i++)//按顺序尝试第0-n-1列
{
if(mark[k][i]==0)
{
vector<vector<int> > tmp_mark=mark;//记录回溯前的mark镜像
location[k][i]='Q';//记录皇后的位置
put_down_the_queen(k,i,mark);//放置皇后
generate(k+1,n,location,result,mark);//递归下一行皇后放置
mark=tmp_mark;
location[k][i]='.';//回溯,重置为“.”
}
}
}
void put_down_the_queen(int x,int y,vector<vector<int>> &mark)
{
static const int dx[]={-1,1,0,0,-1,-1,1,1};
static const int dy[]={0,0,-1,1,-1,1,-1,1};
mark[x][y]=1;//放置皇后 进行标记
//标记该皇后的攻击范围
for(int i=1;i<mark.size();i++)
{
for(int j=0;j<8;j++)//8个方向
{
int new_x=x+i*dx[j];//新的位置向8个方向延伸
int new_y=y+i*dy[j];//每个方向最多延伸N-1
if(new_x<mark.size()&&new_x>=0
&&new_y<mark.size()&&new_y>=0)
{
mark[new_x][new_y]=1;
}
}
}
}
};
分治法:
归并两个已排序的数组
注意:当相同时,先把数组1(即前面那个数组)放进去
分治算法之归并排序
void merge_sort_two_vec(vector<int>&sub_vec1,vector<int>&sub_vec1,vector<int>&vec)
{
int i=0,j=0;
while(i<sub_vec1.size()&&j<sub_vec2.size())
{
if(sub_vec1[i]<=sub_vec2[j])
{
vec.push_back(sub_vec1[i]);
i++;
}
else{
vec.push_back(sub_vec2[j]);
j++;
}
}
for(;i<sub_vec1.size();i++)
vec.push_back(sub_vec1[i]);
for(;j<sub_vec2.size();j++)
vec.push_back(sub_vec2[j]);
}
void merge_sort(vector<int> &vec)
{
if(vec.size()<2)
return;
int mid=vec.size()/2;
vector<int> sub_vec1;
vector<int> sub_vec2;
for(int i=0;i<mid;i++)
{
sub_vec1.push_back(vec[i]);
}
for(int i=mid;i<vec.size();i++)
{
sub_vec2.push_back(vec[i]);
}
merge_sort(sub_vec1);
merge_sort(sub_vec2);
vec.clear();
merge_sort_two_vec(sub_vec1,sub_vec2,vec);
}
315. 计算右侧小于当前元素的个数
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例:
输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
思路:
将数组不断分解直到变成2个元素为一组(共n组),然后比较这两个元素,如果第一个小于第二个,则对应的count不计数,且调换两个元素的位置,确保当n=2时,两组的元素的排序都是从小到大。在程序中,以引用方式修改vec,导致最后返回只有一个数组,那么较大的元素先插入vec,即可完成排序。
当n=2时,对于第一个和第二个自己内部,都已经统计好结果,此时比较两个都已排好序的数组(都是从小到大),左边的指针i所指的值若小于右边指针j所指的值,那么左边指针大于0~j-1的值,那么count+=j,直到循环结束,之后i到最后一个元素都大于0~j-1的元素。
在这个循环中,若发现左边的元素小于右边的元素,那么左边的元素指针加1,原因是这个元素在判断条件内的函数体内会完成count的计算,那么计算下一个,假如大于,那么左边i+1(下一个)元素比i(上一个)大,它一定也会大于右边的元素,所以右边元素指向下一个更大的元素比较。
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int> count;
vector<pair<int,int> > vec;
for(int i=0;i<nums.size();i++)
{
vec.push_back(make_pair(nums[i],i));
count.push_back(0);
}
merge_sort(vec,count);
return count;
}
private:
private:
void merge_sort_two_vec(vector<pair<int,int>> &sub_vec1,
vector<pair<int,int>> &sub_vec2,
vector<pair<int,int>> &vec,
vector<int> &count)
{
int i=0,j=0;
while(i<sub_vec1.size()&&j<sub_vec2.size())
{
if(sub_vec1[i].first<=sub_vec2[j].first)
{
count[sub_vec1[i].second]+=j;
vec.push_back(sub_vec1[i]);
i++;
}
else{
vec.push_back(sub_vec2[j]);
j++;
}
}
for(;i<sub_vec1.size();i++){
count[sub_vec1[i].second]+=j;
vec.push_back(sub_vec1[i]);
}
for(;j<sub_vec2.size();j++)
vec.push_back(sub_vec2[j]);
}
void merge_sort(vector<pair<int,int>> &vec,vector<int> &count)
{
if(vec.size()<2)
return;
int mid=vec.size()/2;
vector<pair<int,int>> sub_vec1;
vector<pair<int,int>> sub_vec2;
for(int i=0;i<mid;i++)
{
sub_vec1.push_back(vec[i]);
}
for(int i=mid;i<vec.size();i++)
{
sub_vec2.push_back(vec[i]);
}
merge_sort(sub_vec1,count);
merge_sort(sub_vec2,count);
vec.clear();
merge_sort_two_vec(sub_vec1,sub_vec2,vec,count);
}