递归、回溯、分治

1-a 求子集1 LeetCode 78

题目:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
方法1:回溯法(O(2^n))
在这里插入图片描述

class Solution{
public: 
	vector<vector<int>> subsets1(vector<int>& nums){
		vector<vector<int>> result;//存储最终结果
		vector<int> item;//回溯时产生的临时数组
		result.push_back(item);//将空集push进result数组
		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]);//将nums中第i个数字push进item中
		result.push_back(item);//将item中的内容push进result中
		generate(i+1, nums, item,result);//递归调用寻找子集的函数
		item.pop_back();//将前个数字pop出,再调用递归函数,这样对于nums中的数字而言每个数字都进行过选择或者不选择的组合
		generate(i+1, nums, item,result);
	}
};

方法2:位运算法
在这里插入图片描述
简单的说就是将要判断的整数数组看成一串可以为(0或者1)的二进制数,计算不同位置为0为1的组合则是该位置数字出现或者不出现的组合

vector<vector<int>> subsets2(vector<int>& nums){
		vector<vector<int>> res;//存储最终结果
		int all_set = 1<<nums.size();//all_set设置为2^n n为nums的大小代表所有可能出现的情况
		for(int i=0;i<all_set;i++){//遍历所有集合
			vector<int> item;//子集
			for(int j=0;j<nums.size();j++){
				if(i&(1<<j)){//数字i代表的集合,将各个元素存进item中
					//1<<j即为nums数组的第i个元素
					item.push_back(nums[j]);
				}
			}
			res.push_back(item);
		}
		return res;
	}

1-b 求子集2 LeetCode 90

题目:
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
思路:
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> res;//存储结果
		vector<int> item;//存储子集
		set<vector<int>> re_res;//临时结果
		sort(nums.begin(),nums.end());//将nums从小到大排序
		res.push_back(item);//将空集push进res
		generate1(0,nums,res,item,re_res);//寻找子集
		return res;
    }
    void generate1(int i, vector<int>& nums, vector<vector<int>> &res, vector<int> &item, set<vector<int>> &re_res){
		if(i>=nums.size()){//跳出条件
			return;
		}
		item.push_back(nums[i]);//将第i个数字push进item中
		if(re_res.find(item)==re_res.end()){
			//判断临时结果中有无item现在的子集,如果有则重复不再存进最终结果中,否则则将item存进res中,并将其插入临时结果记录
			res.push_back(item);
			re_res.insert(item);
		}
		generate1(i+1,nums,res,item,re_res);//调用递归函数
		item.pop_back();//将当前数字pop再递归,这样对于nums中的数字而言每个数字都进行过选择或者不选择的组合
		generate1(i+1,nums,res,item,re_res);
	}
};

1-c 组合总和 LeetCode

题目:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
思路:
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;//存储结果
		vector<int> item;//存储子集
		set<vector<int>> re_res;//临时结果
		sort(candidates.begin(), candidates.end());//将candidates排序
		//res.push_back(item)   不用再push空集了!!
		generate2(0,candidates,res,item,re_res,0,target);//寻找子集
		return res;//返回结果
    }
    void generate2(int i, vector<int>& candidates, vector<vector<int>> &res,
		             vector<int> &item,set<vector<int>> &re_res,int sum, int target){
		if(i >= candidates.size()|| sum > target){//跳出递归条件
			return;			
	    }
		item.push_back(candidates[i]);//将candidates中的第i个数存进item
		sum+=candidates[i];//sum加上item中存储的数
		if(sum == target && re_res.find(item)==re_res.end()){
			//当item中的数之和小于并且没有重复时,才能将item存进res中,并将item中的数放进re_res中记录
			res.push_back(item);
			re_res.insert(item);
		}
		generate2(i+1,candidates,res,item,re_res,sum,target);//调用递归
		item.pop_back();//item中最后一个数字pop后再递归
		sum-=candidates[i];//pop后不要忘了sum也要减掉这个数
		generate2(i+1,candidates,res,item,re_res,sum,target);
	}
};

2. 括号生成

题目:
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
思路:
在这里插入图片描述

#include <iostream>
#include <vector>
#include <string>
#include <stdlib.h>

using namespace std;

class Solution{
public:
	vector<string> generateParenthesis(int n){
		vector<string> res;//存储结果
		generate("",n,n,res);//生成括号组合
		return res;
	}
private:
	void generate(string item, int left, int right,vector<string> &res){//如果定义成了普通变量,res的值会随着此函数执行结束而被清除
		if(left == 0 && right == 0){//当生成完n对括号递归结束
			res.push_back(item);//将item中存储的括号对push进res
			return;
		}
		if(left>0){//先生成左括号并且左括号最多为n个
			generate(item+'(',left-1,right,res);
		}
		if(left < right){//后生成右括号并且生成的右括号的个数不能大于左括号的个数
			generate(item+')',left,right-1,res);
		}
	}
};

void main()
{

	Solution solve;
	vector<string> result;
	result = solve.generateParenthesis(3);
	for(int i=0;i<result.size();i++){
		cout<<result[i]<<'\n';
	}
	system("pause");
}

3. N皇后 LeetCode 51

题目:
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
在这里插入图片描述
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],

["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
解析:
皇后的攻击范围
在这里插入图片描述
回溯法
在这里插入图片描述

#include <iostream>
#include <vector>
#include <string>
#include <stdlib.h>

using namespace std;

class Solution{
public:
	vector<vector<string>> solveNQueens(int n){
		vector<vector<string>> result;//用来存储结果
		vector<vector<int>> mark;//mark是用来存储是否可以摆放皇后的二维数组
		vector<string> location;//location用来存储递归得到的皇后的摆放位置
		for(int i=0; i < n; i++){//初始化mark和location
			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:
	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};//设置方向数组,代表8个方向
		static const int dy[] ={0,0,-1,1,1,-1,1,-1};
		mark[x][y]=1;
		for(int i=0; i < mark.size();i++){
			//扫描mark的八个方向并将其置为1,这些都是皇后可以攻击的范围,故不能再放置其他皇后
			for(int j=0; j<8;j++){
				int new_x = x + i*dx[j];
				int new_y = y + i*dy[j];
				if(new_x>=0 && new_x < mark.size() && new_y>=0 && new_y < mark.size() ){
					mark[new_x][new_y]=1;
				}
			}
		}
	}

	void generate(int k, int n, vector<string> &location, vector<vector<string>> &result, vector<vector<int>> &mark){
		//k代表第几行
		if(k==n){//如果k等于n则说明每行的皇后放置完成,将放置结果push进result返回
			result.push_back(location);
			return;
		}
		for(int i=0; i<n;i++){//依次寻找可以放皇后的位置
			if(mark[k][i] == 0){//如果mark[k][i]为0则该位置可以放置
				vector<vector<int>> temp_mark = mark;//申请临时数组存放当前mark
				put_down_the_queen(k,i,mark);//在[k][i]处放置皇后
				location[k][i] = 'Q'; //在location相应位置设为Q
				generate(k+1, n, location, result, mark);//递归寻找下一行可以放置皇后的位置
				mark = temp_mark;//让mark重新回到回溯前的状态
				location[k][i] = '.';//location同理
			}
		}
	}
};

void main(){
	vector<vector<string>> result;
	Solution solve;
	result = solve.solveNQueens(4);
	for(int i=0; i<result.size();i++){
		cout<<"i="<<i<<'\n';
		for(int j=0;j<result[i].size();j++){
			cout<<result[i][j]<<'\n';
		}
		cout<<'\n';
	}
	system("pause");
}

4. 计算右侧小于当前元素的个数 LeetCode315

题目:
给定一个整数数组 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 个更小的元素.
思路:
在这里插入图片描述

#include <iostream>
#include <vector>
#include <stdlib.h>

using namespace std;

class Solution{
public:
	vector<int> countSmaller(vector<int>& nums){
		vector<pair<int, int>> vec;
		vector<int> count;
		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:
	void merge_sort_two_vec(vector<pair<int, int>>  &sub_vec1,  //数组1       将数组1,2 排序合并
		                    vector<pair<int, int>>  &sub_vec2,  //数组2
							vector<pair<int, int>>  &vec,
							vector<int> &count){ //合并后的数组
		int i=0;
		int j=0;
		while(i < sub_vec1.size() && j < sub_vec2.size()){//比较两个数组的大小,较小的先push进vec
			if(sub_vec1[i].first <= sub_vec2[j].first){  
				count[sub_vec1[i].second] +=j;  //在i位置后面push了多少数进来,就说明有多少数小于i位置的数字
				vec.push_back(sub_vec1[i]);
				i++;
			}
			else{
				vec.push_back(sub_vec2[j]);
				j++;
			}
		}
		for(;i < sub_vec1.size();i++){//将数组1或数组2中剩余元素push进vec中
			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;//拆分的数组1
		vector<pair<int, int>>  sub_vec2;//拆分的数组2
		//进行拆分
		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();//将vec现有内容清空
		merge_sort_two_vec(sub_vec1, sub_vec2, vec, count);//对子数组排序
	}
};

void main(){
	Solution solve;
	vector<int> nums;
	nums.push_back(5);
	nums.push_back(2);
	nums.push_back(1);
	nums.push_back(1);
	vector<int> result = solve.countSmaller(nums);
	cout<<"[";
	for(int i=0;i < nums.size();i++){
		cout<<result[i]<<',';
	}
	cout<<"]"<<endl;
	system("pause");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值