代码随想录算法训练营第二十九天|491. 递增子序列、46. 全排列、47. 全排列 II

代码随想录刷题03.10

回溯算法5

LeetCode题目

491. 递增子序列

解题思路

1.去重的两种主要思路:

1)值查找:

a)使用unordered_setused;

b)每使用一个数,则将该元素存入容器中used.insert(nums[i]);

c)当used.find(nums[i])!=used.end(),说明改值在当前for循环层已被使用过;

2)值标记:

a)使用数组或动态数组(标记好大小,以方便查找)vectorjudge(201,false)或者int used[201]={0};

b)每使用一个数,则将对应位置上的值改为true或1:judge[nums[i]+100]=true或者int[nums[i]+100]=1;

c)当快速查找定位到该位置时,若对应的值为true或1,则说明改值在当前循环中已被使用过judge[nums[i]+100]==true或者int[nums[i]+100]==1;

**注意:**无论是使用unordered_set还是数组,都将容器定义在递归函数内,且容器不向下传递,这样在每一层内,容器都是新的,也就直接避免了树枝去重的误操作。

2.在搜索中去掉部分不要的节点时(比如去重),就在for循环下进行控制:当使用break时,表示跳出当前小层的for循环,当使用continue时,表示在当前for循环层进入下一个元素。

3.记好回溯遍历中N叉树的数据流过程。

代码过程
class Solution {
public:
    vector<int>path;
    vector<vector<int>>result;
    void backtracking(vector<int>& nums,int index){
        if(path.size()>1)result.push_back(path);
        if(index==nums.size())return;
        unordered_set<int>used;
        //vector<bool>judge(201,false);
        for(int i=index;i<nums.size();i++)
        {
            if((!path.empty()&&nums[i]<path.back())||used.find(nums[i])!=used.end())continue;
            path.push_back(nums[i]);
            //judge[nums[i]+100]=true;
            used.insert(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        int index=0;
        backtracking(nums,index);
        return result;
    }
};

LeetCode题目

46. 全排列

解题思路

1.在回溯N叉树图的数据流向中,同一层的数据传递是既向下递归也会借助for循环(同一层数据公用)传递到同层的其他节点内!!!因此,要及时回溯!!!

2.本题依然可以使用上节中所总结的去重的两种方式来做;
然而,本题有一个独特的操作地方:针对元素自身重复取值的去重操作。使用used容器来记录哪个位置(本题也可以使用和上一节一样的利用值来记录,因为题目说了给定的元素里面不会重复,但最好采用元素在数组中的位置来进行标记!)的元素在上一层已经被取过了!
利用位置来标记去重:
a)定义一个数组used,数组大小为题目给定数组内元素数量个数;
b)(在回溯的情况下)若使用了某个位置上的元素,则标记该位置的数组的值为true:used[i]=true;
c)根据回溯与递归关系,上一层的used数组内容会被传递到下一层,在下一层从头开始遍历时,若发现used[i]==true,说明这个i位置处的元素在上层已被用过,本层不因使用,直接跳过continue即可。

3.分析数据流向图时,从节点的尾状态出发分析每一节点的数据状态;
分析N叉树图来思考解题策略时,从节点的首状态出发分析每一节点的数据状态。
N叉树图的预期应和代码写好后数据流向图分析相一致!!!
在这里插入图片描述
在这里插入图片描述

代码过程
class Solution {
public:
    vector<int>path;
    vector<vector<int>>result;
    vector<bool>used=vector<bool>(21,false);
    void backtracking(vector<int>& nums){
        if(path.size()==nums.size()){
            result.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++)
        {
            if(used[nums[i]+10]==true)continue;
            path.push_back(nums[i]);
            used[nums[i]+10]=true;
            backtracking(nums);
            path.pop_back();
            used[nums[i]+10]=false;
        }
        return;
    }
    vector<vector<int>> permute(vector<int>& nums) {
        backtracking(nums);
        return result;
    }
};

LeetCode题目

47. 全排列 II

解题思路

1.去重操作总结:

1)树层去重(横向)与树枝去重(纵向):

A.数层去重:节点内定义标记数组;

B.树枝去重:定义全局的标记数组,并做好回溯

C.两个方向任意去重:将题目给定数组重排序+若前后选择的元素相等则跳过(这种方案需要借助外部的纵向的标记全局数组来确立到底横向(树层)去重还是纵向(继承节点向下,即树枝)去重。本质上其实还是横向的值去重,但是结合纵向标记数组后,可以表现出使沿横向还是沿纵向去重。

2)自身去重(位置去重)与值去重:

A.自身去重:指元素自身不应该重复使用——可以使用和题目给定数组元素数量等大的used(全局回溯)数组,用used[i]来标记位置处元素的使用信息;

B.值去重:指相同数值的元素应进行去重——可以使用unordered_set容器进行值查找或使用数组进行真假判断;

3)值查找判断与真假判断:针对值去重的情况进一步细分

A.值查找判断:unordered_set容器,使用set.find(nums[i])!=set.end()来判断;

B.真假判断:(动态)数组容器,固定数组容器大小,利用judge(nums[i])==true来判断。

2.本题思路:综合树层去重与树枝去重,树层去重需使用值去重,树枝去重需使用自身去重。值去重中进一步选择值查找方式。

代码过程
class Solution {
public:
    vector<int>path;
    vector<vector<int>>result;
    vector<bool>used=vector<bool>(8,false);
    void backtracking(vector<int>& nums){
        unordered_set<int>judge;
        if(path.size()==nums.size()){
            result.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(judge.find(nums[i])!=judge.end()||used[i]==true)continue;
            path.push_back(nums[i]);
            judge.insert(nums[i]);
            used[i]=true;
            backtracking(nums);
            path.pop_back();
            used[i]=false;
        }
        return;
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        backtracking(nums);
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值