四、递归、回溯、分治(小象)

目录

78.求子集

递归法

利用位运算法

90.子集II

40.组合总和II

22.括号生成

51.N皇后问题

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

回溯法:用递归来实现回溯法。

78.求子集

递归法

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> result;
        vector<int> item; //存储子集
        result.push_back(item);
        generate(0,item,result,nums);
        return result;
    }
    
    void generate( int i,vector<int> &item,vector<vector<int>> &result,vector<int>& nums){
        if(i >= nums.size()){
            return ;
        }
        item.push_back(nums[i]);
        result.push_back(item);
        generate(i+1,item,result,nums);
        item.pop_back();
        generate(i+1,item,result,nums);
    }
};

利用位运算法

左移1位:等于乘以2

右移1位:等于除以2

要表示2^n  = 1<< n   (1向左移动n位)    

'&' !!!位运算的时候,不需要转换成二进制!直接用十进制就可以了!

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
         vector<vector<int>> reslut;
         int result_num = 1<<nums.size();
        for(int i = 0;  i <result_num ; i++){
            vector<int> item;  //这不是重定义了吗?没太懂为啥没错呢。
            for(int j = 0; j < nums.size();j++){
                if(i&(1<<j)){
                    item.push_back(nums.at(j));
                }
            }
            reslut.push_back(item);
        }
        return reslut;
    }
};

90.子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

思考:可以添加一个set集合 ,里面存放的是vector<int>表示的子集,这样用做检测因为位置原因而存在的子集,不让他们添加到result里面

比如:set<vector<int>> = [[],[1],[1,2]]

原来的nums = [2,1,2,2]

可能会因为选择的位置不同 ,选出来[2,1] [1,2] 但是因为子集的无序性,其实是一个子集。所以用set来查找,但是[2,1] [1,2] 用set也查找不出来,因为他的确是两个不同的,如果我们在之前把nums排序,就不会出现[2,1] [1,2] ,而都是[1,2]了,这样就可以检测出来了。

key : 1、要把nums先进行排序,用sort();2、要设置一个set<vector<int>> res_set 来检测这个子集出现过没有,没有则添加。

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> result;
        vector<int> item;
        //res_set这个set集合只是为了检测当前要传入的子集在不在以前已经出现了
        set<vector<int>> res_set;
        sort(nums.begin(),nums.end());
        res_set.insert(item);
        result.push_back(item);
        generate(0,item,result,nums,res_set);
        return result;
    }
    
    void generate(int i ,vector<int> &item,vector<vector<int>> &result,
                vector<int> & nums, set<vector<int>>& res_set){
            if(i >= nums.size())    return ;
            item.push_back(nums[i]);
        //查找到res_set.end()表示没有查到 这个时候才应该把这个子集添加到result之中
            if(res_set.find(item) == res_set.end()){
                res_set.insert(item);
                result.push_back(item);
            }
            generate(i+1,item,result,nums,res_set);
            item.pop_back(); 
            generate(i+1,item,result,nums,res_set);
        }
};

40.组合总和II

思考:回溯过程中加入剪枝 会大幅度降低算法效率和时间复杂度
sum > target 直接 修剪掉
在深度搜索使用剪枝很常用

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,0,item,result,res_set,candidates,target);
        return result;
    }
    //傻逼了。。。忘记传引用 我说为啥这个还有重复的集合呢!!! set忘记传引用
    void generate(int i ,int sum ,vector<int> &item,  vector<vector<int>> &result,          
            set<vector<int>> &res_set,vector<int>& candidates, int target) {
        if(i >= candidates.size()||sum > target){
            //跳出循环的条件 肯定是和已经大于了目标值 而不是等于
            //还有 是或而不是与。。。只要有一个不满足就可以跳出来了
            return ;
        }
        item.push_back(candidates[i]);
        sum += candidates[i];
        if(sum == target && res_set.find(item) == res_set.end()){
            //要添加的是正好满足条件的集合,而不是在进行循环累加
            //比如 [1,7] 加起来正好等于8才可以添加进去,而不是一个一个的往集合中添加
            //元素在判断<target的意思 
            res_set.insert(item);
            result.push_back(item);
        }
         generate(i+1,sum,item,result,res_set,candidates,target);
         item.pop_back();
        sum -= candidates[i];
         generate(i+1,sum,item,result,res_set,candidates,target);
        
    }
};

22.括号生成

首先回答问题:如果有N组括号,求生成的全部集合?

若有2组括号,全部生成的可能集合总共有1<<4,一共16种。

一开始传入的是" " 空字符串,每次选择"("或者是")",如下图:

#include<iostream>
#include<vector>
#include <string>
using namespace std;

void generate(string item,int n ,vector<string>& Result){
	if(item.size() == 2 * n){
		Result.push_back(item);
		return ;
	}
	generate(item+"(",n,Result);
	generate(item+")",n,Result);
}
void main(){
	 vector<string> result;
	 generate("",2,result);
	 for(int i = 0 ; i < (1<<4);i++){
		 cout<<result[i]<<endl;
	 }
}

key:

1、生成合法的括号对,则"(" 和 ")"成对出现,若N =2 ,则左括号2个,右括号两个。

2、若是合法的括号对,则"("的数量不小于")"的时候才可能添加")".  //更正!!!不是不小于啊!是严格的大于")"的时候才可以添加,举例子当都只0的时候?难道可以先添加"("吗?

3、在递归回溯的过程中不要再调用函数中使用++运算符//(否则如下图 left不变化 但是一直添加。。)

4、见2!调BUG N久。。。我说为什么一直就有问题呢!

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        string item;
        vector<string> result;
        generate(item,n,0,0,result);
        return result;
    }
    void generate( string item,int n ,int left/*此时的左括号数*/ ,int right,vector<string> &result){
        if(left == n&& right ==n ){
            result.push_back(item);
            return ;
        }
        if(left < n ){
             generate(item+"(",n,left+1,right,result);
        }
        if(left > right){
             generate(item+")",n,left,right+1,result);
        }
    }
};

51.N皇后问题

思考: = = !早都在vs上调好了!弄了一晚上!!!因为几个小BUG自己没注意,写到LeetCode上总是出错。

1、首先要写一个函数push_on_board(int x,int y ,vector<vector<int>> & checkerBoard) 来模拟每次把皇后放置到棋盘时候对周围八个方向的改变,棋盘的初始化都是0,表示都可以放置皇后,当放置了之后变为1。有一个关键点,放置的时候,是一圈一圈的展开,从(x,y)这个位置开始扩散,所以总共的层数应该是1到n -->> for(int i = 1;i< n ;i++)

2、输出的是字符串棋盘,所以要用一个vector<string> item来 和数字棋盘一一对应的变换。用vector<vector<result>>来存储最后的结果

3、用递归来回溯比较简单,就按照行来放置皇后,然后按照列遍历,看看这一列能不能放啊之类的。退出的结果就是目前放置的皇后数目已经是n个了。

4、遇到的很头疼的地方!

1、LeetCode显示no function match ...我服了 我找了N久,我这从vs运行的怎么就没有函数匹配了,试了把函数放在类外面,又查了半天也没用。后来别人才给我指出来!我函数的变量类型声明错了!vector<vector<int>> 写成了vector<vector<string>> ,汗!玩成了重载。。。

2、总是显示“ reference binding to null pointer of type 'value_type”,简直了!我想半天这result二维数组也没法赋初值啊!查好久!后来才看到是 vector<vector<int>> checkerBoard(n);我已经给checkerBoard手动分配了内存,然后又用了别人代码中的一句话checkerBoard.push_back(vector<int>()); 最后我的循环次数就只有4次,然后又开辟了4个空间,相当于我这个对象中8个对象,4个为空。。结果就引用指向了空值 = = 。。汗

class Solution {
public:
       vector<vector<string>> solveNQueens(int n) {
       vector<vector<string>> result;
        vector<string> item ;
        vector<vector<int>> checkerBoard(n);
        for(int i =0 ; i< n ;i++){
           // checkerBoard.push_back(vector<int>());
            for(int j = 0 ; j< n ;j++){
                checkerBoard[i].push_back(0);
            }
            item.push_back("");
            item[i].append(n,'.');
        }
        //result.push_back(item);
         generate(0,n,checkerBoard,item,result);
        return result;
    }
    
     void push_on_checkerBoard(int x, int y ,vector<vector<int>>&checkerBoard ){
        const int dx[] = {1,1,0,-1,-1,-1,0,1};
        const int dy[] = {0,-1,-1,-1,0,1,1,1};
          checkerBoard[x][y] = 1;  
        for(int i = 1 ; i <checkerBoard.size();i++){
            for(int j = 0  ; j < 8;j++){
                int newX = x + dx[j] * i;
                int newY = y + dy[j] * i;
                if(newX < checkerBoard.size()&& newX >= 0 && newY < checkerBoard.size()&&newY >= 0 ){
                  checkerBoard[newX][newY] = 1;  
                }
            }
        }
    }
    
    void generate(int k /*完成了几个皇后放置*/, int n , vector<vector<int>> &checkerBoard, vector<string> &item,  vector<vector<string>> &solution){
        if(k==n){
            solution.push_back(item);
            return ;
        }
        for(int i = 0; i< n ;i++){
            if(checkerBoard[k][i] == 0){
                vector<vector<int>> mark = checkerBoard;
                push_on_checkerBoard(k,i,checkerBoard);
                item[k][i] = 'Q';
                generate(k+1,n,checkerBoard,item,solution);
                checkerBoard = mark;
                 item[k][i] = '.';
            }
        }
    }
  
};
 

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

首先是回顾了“归并排序”,一开始看书的时候觉得好难,后来自己实现了就发现还是挺简单的。

基本的操作就两个:

1、两个有序的数组进行合并成为一个有序的数组。

-->两个计数器,同时指向数组的首元素,然后比较,如果vec1[i] <= vec2[j],那么把vec[i]这个元素存储到新开辟的vec之中,同理如果大于就放入vec[j],同时应该把当前的计数器后移一位,判断终止条件就是其中某一个数据已经使用完了,那么直接把另一个数组接到新的vec尾部就可以了。

缺点:“大概就是新开辟的空间吧”~

2、如果是无序的数组,传入进来用归并排序,首先应该使用递归的思想,把传入进来的vec进行验证,如果它的元素小于2,意味着只有1个或者0个元素,就直接求解;否则的话就把vec一分为二,变成sub_vec1,sub_vec2。然后再足够小的规模的时候再使用归并排序。这里要理解递归的含义!

3、题目的难点就在于“排序了之后,原先元素的位置发生了改变,再想对应回去它本来位置的正确逆序数就很苦难。”....

所以采用元素和位置进行绑定,使用pair就可以了,把原始的nums变成vector<pair<int,int>> vec 然后操作count的时候,就直接根据元素的位置去变换,这样就正好对应到了。

5、还有一点需要反思的地方,就是我在分治的时候,把原始的问题变成了两个子问题,就直接去调用递归排序想要解决问题,实际上这里反应了我对递归的不理解,应该是一直调用,等到足够小到一定规模的时候,才使用真正解决问题的手段——归并,而不是直接变成两个子问题就归并!~

    vector<pair<int,int>> sub_vec1;
    sub_vec1.assign(vec.begin(), vec.begin() + mid);
	vector<pair<int, int>> sub_vec2;
    sub_vec2.assign(vec.begin()+ mid, vec.end());
	//sortTheOrder(sub_vec1, sub_vec2, vec);没有真理解递归的意义 
	smallerNumber(sub_vec1, count);
	smallerNumber(sub_vec2, count);
	vec.clear();
	sortTheOrder(sub_vec1, sub_vec2, vec, count);

6.还有个问题就是我一开始用迭代器做可以通过小数据的案例,但是大数据量就没法过,测试的数据集有两个计算错了,真不懂。。我也没有对子vector进行修改啊,为什么它的迭代器来操作就有问题呢。。不太懂。。。放上这个失败的代码。。。看看以后能不能找到解决的方法。

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> counts;
        vector<pair<int,int> > vec;
        for(int i = 0; i <nums.size();i++){
            counts.push_back(0);
            vec.push_back(make_pair(nums[i],i));
        }
        smallerNumber(vec,counts);
        return counts;
    }
    void smallerNumber(vector<pair<int,int>>& vec,vector<int>&count) {
	if (vec.size() < 2) {
		return;
	}
	int mid = vec.size() / 2;
	vector<pair<int,int>> sub_vec1;
    sub_vec1.assign(vec.begin(), vec.begin() + mid);
	vector<pair<int, int>> sub_vec2;
    sub_vec2.assign(vec.begin()+ mid, vec.end());
	//sortTheOrder(sub_vec1, sub_vec2, vec);没有真理解递归的意义 
	smallerNumber(sub_vec1, count);
	smallerNumber(sub_vec2, count);
	vec.clear();
	sortTheOrder(sub_vec1, sub_vec2, vec, count);
}
    void sortTheOrder(vector<pair<int,int>>& sub_vec1,                                         
                      vector<pair<int,int>>& sub_vec2,
					vector<pair<int, int>>& vec, 
                      vector<int> &count) {
	vector<pair<int,int>>::iterator iter1 = sub_vec1.begin();
	vector<pair<int,int>>::iterator iter2 = sub_vec2.begin();
	while (iter1 != sub_vec1.end() && iter2 != sub_vec2.end()) {
		if (*iter1 <= *iter2) {
			vec.push_back(*iter1); //count[i] += j
			count[iter1->second] += iter2 - sub_vec2.begin();
			iter1++;
		}
		else {
			vec.push_back(*iter2);
			iter2++;
		}
	}
	if (iter1 == sub_vec1.end()) {
		vec.insert(vec.end(), iter2, sub_vec2.end());
	}
	if (iter2 == sub_vec2.end()) {
		for (auto i = iter1; i != sub_vec1.end(); i++) {
			//iter1->first = iter1->first +  (int)sub_vec2.size();
			count[iter1->second] += (int)sub_vec2.size();
		}
		vec.insert(vec.end(), iter1, sub_vec1.end());
	}
}
};

7.最后附上正确的代码:

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> counts;
        vector<pair<int,int> > vec;
        for(int i = 0; i <nums.size();i++){
            counts.push_back(0);
            vec.push_back(make_pair(nums[i],i));
        }
        smallerNumber(vec,counts);
        return counts;
    }
    void smallerNumber(vector<pair<int,int>>& vec,vector<int>&count) {
	if (vec.size() < 2) {
		return;
	}
	int mid = vec.size() / 2;
	vector<pair<int,int>> sub_vec1;
    sub_vec1.assign(vec.begin(), vec.begin() + mid);
	vector<pair<int, int>> sub_vec2;
    sub_vec2.assign(vec.begin()+ mid, vec.end());
	//sortTheOrder(sub_vec1, sub_vec2, vec);没有真理解递归的意义 
	smallerNumber(sub_vec1, count);
	smallerNumber(sub_vec2, count);
	vec.clear();
	sortTheOrder(sub_vec1, sub_vec2, vec, count);
}
   void sortTheOrder(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 (*iter1 <= *iter2)*/
		if(sub_vec1[i].first <= sub_vec2[j].first)
		{
			vec.push_back(sub_vec1[i]); //count[i] += j
			count[sub_vec1[i].second] += j;
			i++;
		}
		else {
			vec.push_back(sub_vec2[j]);
			j++;
		}
	}
	while (i != sub_vec1.size()) {
		vec.push_back(sub_vec1[i]);
		count[sub_vec1[i].second] += j;
		i++;
	}
	while (j!= sub_vec2.size()) {
		vec.push_back(sub_vec2[j]);
		j++;
	}
}
};

总结:这几个代码难度感觉都好大。。我弄了好久才弄完 = = 感觉要是自己刷题还是先从medium挨个做吧!不过收获也很大!再接再厉咯!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值