今天刷一些简单的回溯和递归的题;
1.LeetCode 78.Subsets 求子集问题:
Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
已知一组数(其中无重复的数),求这组数可以组成的所有子集。结果中不可有无重复的子集。
求解思路:其实这道题也就是求解排列组合的问题。在所有的子集中,生成各个子集, [3],[1],[2],[1,2,3], [1,3],[2,3],[1,2],[]。
即是否选[1],是否选[2],是否选[3]的问题。
如果这道题仅仅是简单的生成[1],[1,2],[1,2,3]三个子集的话,我们可以直接这样做:
#include <stdio.h>
#include <vector>
int main()
{
vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
vector<int> item; //生成各个子集的数组
vector<vector<int>> result; //最终存放结果的数组
for(int i=0;i<nums.size();i++){ // i =0 时, item = [1]
item.push_back(nums[i]); // i=1时,item=[1,2]
result.push_back(item); // i =2时,item=[1,2,3]
}
for(int i=0;i<result.size();i++)
for(int j=0;j<result[i].size();j++){
printf("[%d]",result[i][j]);
}
printf("\n");
return 0;
}
我们也可以使用递归来做:
void generate(int i,vector<int>& nums,vector<int>& item,vector<vector<int>>&result){
if(i>=nums.size()) return; //递归退出条件
item.push_back(i);
result.push_back(item);
generate(i+1,nums,item,result);
}
我们可以利用回溯法生成子集,即对于每个元素,都有试探放入或不放入集合中的两个选择:
选择放入该元素,递归的进行后续元素的选择,完成放入该元素后续所有元素的试探:之后将其拿出,即再进行一次选择不放入该元素,递归的进行后续元素的选择,完成不放入该元素后续所有元素的试探。
本来选择放入,在选择一次不放入的这个过程,称为回溯试探法。
例如:
元素数组:nums=[1,2,3,4,5,…],子集生成数组item[]=[]
对于元素1,
选择放入item,item=[1],继续递归后续处理后续[2,3,4,5,…]元素; item = [1,…]
选择不放入item,item=[], 继续递归处理后续[2,3,4,5,…]元素;item = […]
如图:
实现代码:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
/* std::vector<vector<int>> result;
std::vector<int> item;
result.push_back(item); //push进空集
generate(0,nums,item,result);
return result;
}
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++,nums, item, result); //第一次递归调用
item.pop_back(); //选择不放入
generate(i++,nums, item, result);
}
}
但是在leetcode上超时。我们选择第二种方法:
在c语言中位运算是基础运算符之一有很大的用处,比如求一个数组,[1,3,1,2,5,5,2] ;求出该数组中之出现一次的数
我们可以使用异或(^)来做,可以把两两数依次进行异或。因为两个相同的数,进行异或的话是0,所以最后一个数一定是只出现一次的数。
算法思路:若一个集合有三个元素A,B,C,则三个元素有2^3=8种组成集合的方式,用0-7表示这些集合:
A元素为100 = 4;B元素为010 = 2;C元素为001 =1
如构造某一集合,即使用A,B,C对应的三个整数与该集合对应的整数做&运算,当为真时,将该元素push进入集合。
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
int all_set = 1<<nums.size(); //设置全部集合的最大值+1 1<<n 即为2^n
for(int i=0;i<all_set;i++){ //遍历所有集合
vector<int> item;
for(int j=0;j<nums.size();j++){
if(i&(1<<j))
item.push_back(nums[j]);
}
result.push_back(item);
}
return result;
}
};
通过!
接下来看一个题目的变形:Leetcode 90 求子集2:
已知一组数(其中有重复元素),求这组数可以组成所有子集。结果无重复的子集。
例如: nums[]=[2,1,2,2]
结果为:[[],[1],[1,2],[1,2,2],[1,2,2,2],[2],[2,2],[2,2,2]]
注意:[2,1,2]与[1,2,2]是重复的元素
算法思路:我们可以先对nums数组进行排序,再使用set去重!因为set中的每一个元素都唯一。
例如:[2,1,2,2] ,排序后:[1,2,2,2] 无论如何选择,均只出现[1,2,2]
代码:
#include<vector>
#include<set>
#include <algorithm>
using namespace std;
Class Solution{
public: vector<vector<int>> subsetWithDup(vector<int> & nums){
vector<vector<int>> result;
vector<int> item;
set<vector<int>> res_set;
sort(nums.begin(),nums.end());
result.push_back(item);
generate(0,result,item,nums,res_set);
}
void generate(int i, vector<vector<int>>&result, vector<int>&item, vector<int>&nums,set<vector<int>>&res_set){
if(i>=nums.size()) return;
item.push_back(nums[i]);
if(res_set.find(item)==res_set.end()){ //没找到重复的
result.push_back(item);
res_set.insert(item); //放入无重复数组里面
}
generate(i+1,result,item,nums,res_set);
item.pop_back(); //回溯
generate(i+1,result,item,nums,res_set);
}
}
OK
变形3 leetcode 40组合数之和2
已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子集中的各个元素和为整数target的子集,结果中无重复的子集。
例如:nums[]=[10,1,2,7,6,1,5], target=8
结果为:[[1,7],[1,2,5],[2,6],[1,1,6]]
我们可以思考下:使用回溯法或位运算发,整体时间复杂度O(2^n)。
所以我们可以在搜索回溯过程中进行剪枝操作:
递归调用时,计算已选择元素的和sum,若sum>target,不再进行更深的搜索,直接返回。
例如:
nums[]={10,1,2,7,6,1,5}
target=8;
vector<vector<int>> combinationSum2(vector<int>& candidates,int target){
vector<vector<int>> result;
vector<int> item;
set<vector<int>> res_set;
sort(candidates.begin(),candidates.end());
result.push_back(item);
generate(0,result,item,candidates,res_set,0,target);
}
void generate(int i, vector<vector<int>>&result, vector<int>&item, vector<int>&nums,set<vector<int>>&res_set,int sum,int target){
if(i>=nums.size() || sum>target ) return; //sum的值大于目标值直接返回
item.push_back(nums[i]);
sum+=nums[i]; //累加sum
if(sum==target && res_set.find(item)==res_set.end()){ //没找到重复的 并且sum=target
result.push_back(item);
res_set.insert(item); //放入无重复数组里面
}
generate(i+1,result,item,nums,res_set);
sum-=nums[i]; //回溯后,sum将nums[i]减去并从item删去
item.pop_back(); //回溯
generate(i+1,result,item,nums,res_set);
}
}
最后放上归并排序的算法(o(nlogn)),算法是稳定的。
void merge_sort_two_vec(vector<int> &vec1,vector<int> &vec2,vector<int> &res){
int i=0;
int j=0;
while(i<vec1.size() && j<vec2.size()){
if(vec1[i]<=vec2[j]){
res.push_back(vec1[i]);
i++;
}
else {
res.push_back(vec2[j]);
j++;
}
}
while(i<vec1.size()){
res.push_back(vec1[i]);
i++;
}
while(j<vec2.size()){
res.push_back(vec2[j]);
j++;
}
}
void merge_sort(vector<int>& vec){
if(vec.size()<2) return;
int mid = vec.size()/2;
vector<int> sub1;
vector<int> sub2;
for(int i=0;i<mid;i++)
sub1.push_back(vec[i]);
for(int j=mid;j<vec.size();j++)
sub2.push_back(vec[j]);
merge_sort(sub1);
merge_sort(sub2);
vec.clear();
merge_sort_two_vec(sub1,sub2,vec);
}