leetcode刷题_四

今天刷一些简单的回溯和递归的题;
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);

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值