代码随想录算法训练营第二十五天(回溯 四)

昨天偷懒了,今天补打卡

力扣题部分:

491.递增子序列

题目链接:. - 力扣(LeetCode)

题面:

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

思路:

这道题第一个坑点是nums = [4,6,7,7]时[4,7,7]也算一个子序列,也就是说,子序列可以取非连续的数组元素,没注意到这个可能代码全部都得重新考虑了。

这又是子集,又是去重,是不是不由自主的想起了刚刚讲过两遍(今天除了本题下面还有第三遍)的去重方法,但是很遗憾,方法要对原数组排序,**而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以本题看起来和别的题目差不了多少,但是一细想会发现陷阱真的蛮多的。**那具体怎么解决这里的去重呢?我们还是用回溯三部曲说明去重的思路和部分代码好了。

回溯三部曲

递归函数参数

cpp 复制代码
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex)

这环节倒没啥不一样的,两个全局变量,因为也算是求子集,所有startIndex也是必要的。

终止条件

本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归。

但本题收集结果有所不同,题目要求递增子序列大小至少为2,所以代码如下:

注意这里不要加return,因为要取树上的所有节点。

cpp 复制代码
if (path.size() > 1) {
    result.push_back(path);
}

关于树形结构,具体的图在下面可以看到。

在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了

cpp 复制代码
unordered_set<int> uset; // 使用set来对本层元素进行去重
for (int i = startIndex; i < nums.size(); i++) {
    if ((!path.empty() && nums[i] < path.back())
            || uset.find(nums[i]) != uset.end()) {
            continue;
    }
    uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
    path.push_back(nums[i]);
    backtracking(nums, i + 1);
    path.pop_back();
}

代码通过每层内创建一个**unordered_set<int> uset** 记录已经用过的元素,因为是每层重新创建,和之前回溯老套路不一样的是不需要对**unordered_set<int> uset** 使用类似pop的操作,因为unordered_set<int> uset是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int> nums, int startIndex)
    {
        if(path.size() > 1) result.push_back(path);
        unordered_set<int>set;
        for(int i = startIndex; i < nums.size(); i ++)
        {
            if((path.size() && path[path.size() - 1] > nums[i]) || set.find(nums[i]) != set.end())
                continue;
            set.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums,i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums,0);
        return result;
    }
};

46.全排列

题目链接:. - 力扣(LeetCode)

题面:

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

思路:

在前面的回溯学习中,我们接触了组合,子集分割 三种回溯可以解决的问题类型,这是我一刷代码随想录涉及到的最后一种类型了:排列。

相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。

所以正如我们在之前所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?

因为一些问题能暴力搜出来就已经很不错了!

下面是回溯三部曲

递归函数参数

两个全局变量还是一样的,一个用来记录符合条件的一个排列(rightnums),另一个负责记录所有的排列(results)。

不一样的是为了保证不会重复选取数字,加入了一个bool数组used记录。

递归终止条件

如果每个元素都只拿一次,很显然当记录排列的rightnums数组和nums长度相同的时候,我们的排列就可以放进results里了

单层搜索的逻辑

每次i从0开始选取的前提是这个元素之前没被选过,一经选取use[i]就要变成true,其他的就没什么了,和之前的for循环基本一致。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int>rightnums;
    vector<vector<int>>results;
    void find(vector<int>nums, vector<bool> used)
    {
        if(rightnums.size() == nums.size())
        {
            results.push_back(rightnums);
            return;
        }
        for(int i = 0; i < nums.size(); i ++)
        {
            if(used[i] == false)
            {
                rightnums.push_back(nums[i]);
                used[i] = true;
                find(nums, used);
                rightnums.pop_back();
                used[i] = false;
            }
           
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        find(nums, used);
        return results;
    }
};

47.全排列 II

题目链接:. - 力扣(LeetCode)

题面:

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

思路:

和上一道题唯一的区别就是去重 的问题**,但是好消息是全排列对数组顺序没有要求,也就是说我们可以通过排序让重复的元素放在一起....**想起来没?这么熟悉的去重操作,相信如果你看过之前的回溯二,回溯三的内容的话,应该知道该怎么去重了吧?一个比较抽象的去重逻辑,这已经是第三次出现了,我还是把这个逻辑的解释的文章传送门放这里,看不懂下面这行代码的可以去我原来的文章里看看这么解释的。

传送门(第二道题目 组合总和|| ):代码随想录算法训练营第二十三天(回溯 二)-CSDN博客

if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;

相信明白了去重之后下面的的代码不难打出:

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> results;
    vector<int>rightsum;
    void find(vector<int> nums, vector<bool> used)
    {
        if(nums.size() == rightsum.size())
        {
            results.push_back(rightsum);
            return;
        }
        for(int i = 0; i < nums.size(); i ++)
        {
            if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
            if(used[i] == false)
            {
                used[i] = true;
                rightsum.push_back(nums[i]);
                find(nums, used);
                used[i] = false;
                rightsum.pop_back();
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<bool> used(nums.size(),false);
        find(nums, used);
        return results;
    }
};
相关推荐
软工菜鸡26 分钟前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生28 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
发霉的闲鱼29 分钟前
MFC 重写了listControl类(类名为A),并把双击事件的处理函数定义在A中,主窗口如何接收表格是否被双击
c++·mfc
小c君tt32 分钟前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
xiaoxiao涛39 分钟前
协程6 --- HOOK
c++·协程
AI视觉网奇1 小时前
sklearn 安装使用笔记
人工智能·算法·sklearn
JingHongB1 小时前
代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础
算法·深度优先·图论
weixin_432702261 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
小冉在学习1 小时前
day52 图论章节刷题Part04(110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长 )
算法·深度优先·图论
Repeat7151 小时前
图论基础--孤岛系列
算法·深度优先·广度优先·图论基础