4.算法之递归、回溯与分治

递归、回溯法

回溯法又称为试探法,但是当探索到某一步时,发现原先选择达不到目标,就退回一部重新选择,这种走不通就退回重新再走的技术称为回溯法。

有向无环图:
在这里插入图片描述

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);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值