代码随想录算法训练营DAY28(记录)|C++回溯算法Part.5|491.递增子序列、46.全排列、47.全排列II

embedded/2025/2/22 16:06:23/

文章目录

  • 491.递增子序列
    • 思路
    • 伪代码
    • CPP代码
    • 优化代码
  • 46.全排列
    • 思路
    • 伪代码
    • CPP代码
  • 47.全排列II
    • CPP代码

491.递增子序列

力扣题目链接

文章链接:491.递增子序列

视频连接:回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列

状态:今天没时间了,随便记录一下!

思路

本题与90.子集II非常类似。

也就是本题的递增子序列有要求子集,然后还要去重,但是:本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑!

这里用[4, 7, 6, 7]进行对比。

伪代码

  • 递归函数参数:求子序列,很明显一个元素不能重复使用,所以需要startIndex,调整下一层递归的起始位置。

    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex)
    
  • 终止条件:本题类似求子集问题,也要遍历树形结构找每一个结点,所以和78.子集类似,要遍历树形结构找每一个结点。其实可以不加终止条件,startIndex每次都会加1,并不会无限递归。本题收集结果有所不同,题目要求递增子序列大小至少为2,我们不要加return这样就可以取数上的所有结点啦:

if (path.size() > 1) {result.push_back(path);// 注意这里不要加return,因为要取树上的所有节点
}
  • 单层搜索逻辑

同一父结点下的同层上使用过的元素就不能再使用了!这里我们使用set来进行去重。

使用set只记录本层元素是否重复使用,新的一层uset都会被重新定义(清空)。

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();
}

CPP代码

// 版本一
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {if (path.size() > 1) {result.push_back(path);// 注意这里不要加return,要取树上的节点}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();}}
public:vector<vector<int>> findSubsequences(vector<int>& nums) {result.clear();path.clear();backtracking(nums, 0);return result;}
};

优化代码

这里数值范围较小,我们使用数组来做哈希可以对代码进行优化:

程序运行的时候对unordered_set 频繁的insertunordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。

// 版本二
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {if (path.size() > 1) {result.push_back(path);}int used[201] = {0}; // 这里使用数组来进行去重操作,题目说数值范围[-100, 100]for (int i = startIndex; i < nums.size(); i++) {if ((!path.empty() && nums[i] < path.back())|| used[nums[i] + 100] == 1) {continue;}used[nums[i] + 100] = 1; // 记录这个元素在本层用过了,本层后面不能再用了path.push_back(nums[i]);backtracking(nums, i + 1);path.pop_back();}}
public:vector<vector<int>> findSubsequences(vector<int>& nums) {result.clear();path.clear();backtracking(nums, 0);return result;}
};

46.全排列

46.全排列
文章链接:46.全排列

视频连接:组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列

状态:今天没时间了,随便记录一下!

思路

排列与组合(包括分割和子集都近似于组合问题,所以我们总是处理前对其进行排序)相比,排列是有顺序的。总而言之排列是强调元素顺序的

全排列问题的树形结构如图:

从树形结构可以看出,我们每次都是从头开始,即使元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

这里我们会使用used数组,记录此时path里都有哪些元素使用啦,一个排列里一个元素只能使用一次。

伪代码

  • 递归函数参数:正如上文中所讲的,我们需要一个used数组
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used)
  • 递归终止条件:

    • 上图中可以看出,叶子结点收获结果,那么如何判断到了叶子结点呢,本题的判断很简单,path数组的大小达到和nums数组一样大的时候,就说明找到了全排列
    // 此时说明找到了一组
    if (path.size() == nums.size()) {result.push_back(path);return;
    }
    
  • 单层搜索的逻辑:

    • 排列问题和之前写的组合、切割、子集问题最大的不同就是不用startindex来指明遍历的位置了,因为我们每次都要从头开始搜索,正如上文描述的那样。这里我们使用used。
    for (int i = 0; i < nums.size(); i++) {if (used[i] == true) continue; // path里已经收录的元素,直接跳过used[i] = true;path.push_back(nums[i]);backtracking(nums, used);path.pop_back();used[i] = false;
    }
    

CPP代码

class Solution {
public:vector<vector<int>> result;vector<int> path;void backtracking (vector<int>& nums, vector<bool>& used) {// 此时说明找到了一组if (path.size() == nums.size()) {result.push_back(path);return;}for (int i = 0; i < nums.size(); i++) {if (used[i] == true) continue; // path里已经收录的元素,直接跳过used[i] = true;path.push_back(nums[i]);backtracking(nums, used);path.pop_back();used[i] = false;}}vector<vector<int>> permute(vector<int>& nums) {result.clear();path.clear();vector<bool> used(nums.size(), false);backtracking(nums, used);return result;}
};

47.全排列II

力扣题目链接

文章链接:47.全排列II

视频连接:回溯算法求解全排列,如何去重?| LeetCode:47.全排列 II

状态:今天没时间了,随便记录一下!

这题很有意思,这里给定的是一个包含了重复数字的序列,要返回所有不重复的全排列。很明显,需要去重

那么我们这里要判断了,是树层去重呢,还是树枝去重?

![在这里插入图片描述]()
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {continue;
}

我们在40.组合总和II、90.子集II中已经详细论述过去重逻辑的写法,这里直接给出代码

CPP代码

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking (vector<int>& nums, vector<bool>& used) {// 此时说明找到了一组if (path.size() == nums.size()) {result.push_back(path);return;}for (int i = 0; i < nums.size(); i++) {// used[i - 1] == true,说明同一树枝nums[i - 1]使用过// used[i - 1] == false,说明同一树层nums[i - 1]使用过// 如果同一树层nums[i - 1]使用过则直接跳过if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {continue;}if (used[i] == false) {used[i] = true;path.push_back(nums[i]);backtracking(nums, used);path.pop_back();used[i] = false;}}}
public:vector<vector<int>> permuteUnique(vector<int>& nums) {result.clear();path.clear();sort(nums.begin(), nums.end()); // 排序vector<bool> used(nums.size(), false);backtracking(nums, used);return result;}
};// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素

http://www.ppmy.cn/embedded/7585.html

相关文章

ssm053毕业论文管理系统+vue

学院教务处制 摘 要 高校规模越来越大&#xff0c;学生越来越多&#xff0c;每年都有大批的大学生完成学业。毕业之前&#xff0c;各大高校设立毕业论文来对学生进行考核&#xff0c;传统毕业论文管理方式效率低下&#xff0c;为了提高效率&#xff0c;特开发了本毕业论文管…

BCLinux8U6系统部署oceanbase分布式数据库社区版之三、分布式数据库部署

本文是在完成步骤一、准备 OBD 中控机&#xff0c;步骤二3台数据库服务器准备后&#xff0c;正式开始oceanbase分布式数据库安装。 前序步骤&#xff1a;BCLinux8U6系统部署oceanbase分布式数据库社区版之一、准备 OBD 中控机 BCLinux8U6系统部署oceanbase分布式数据库社区版…

Gradle 进阶学习 之 Task

1、项目的生命周期 Gradle 项目的生命周期分为三大阶段: Initialization -> Configuration -> Execution. 每个阶段都有自己的职责。 想象一下&#xff0c;你正在制作一个大型的乐高城堡。在这个过程中&#xff0c;你需要做三件事&#xff1a; 初始化阶段&#xff1a;…

Vitis HLS 学习笔记--HLS优化指令示例-目录

目录 1. 示例集合概述 2. 内容分析 2.1 array_partition 2.2 bind_op_storage 2.3 burst_rw 2.4 critical_path 2.5 custom_datatype 2.6 dataflow_stream 2.7 dataflow_stream_array 2.8 dependence_inter 2.9 gmem_2banks 2.10 kernel_chain 2.11 lmem_2rw 2.1…

最新UI发卡盗U,支持多语言,更新UI界面,支持多个主流钱包,附带系统搭建教程

环境&#xff1a;Linux系统 进入宝塔安装环境&#xff1a;Nginx 1.22.1 MySQL 8.0 php7.4 phpMyAdmin 5.2 按照说明去安装环境&#xff0c;如果没有找到MySQL8.0版本去"软件商店"搜索Mysql切换至8.0 1.上传开源源码 2.上传数据库文件 3.上传猴导入数据库文件 4.修…

STM32

1.总结串口的发送和接收功能使用到的函数 串口数据发送数据的函数&#xff1a;HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout) 串口数据接收数据的函数&#xff1a;HAL_StatusTypeDef HAL_UART_Re…

Go下载安装及切换不同版本的方法

一、下载安装 Go下载地址 Go提供了Windows、MacOS(ARM64) 和 MacOS(x86-64)、Linux版本&#xff0c;也可以下载源码自己编译安装。 Linux && MacOS 下载压缩包 解压到指定目录&#xff0c;如&#xff1a;/usr/local rm -rf /usr/local/go && tar -C /usr…

提升用户体验的UUID设计策略

本文翻译自 The UX of UUIDs&#xff0c;作者&#xff1a;Andreas Thomas&#xff0c; 略有删改。 唯一标识符在从用户身份验证到资源管理的所有应用程序中起着至关重要的作用。虽然使用标准UUID将满足您的所有安全问题&#xff0c;但我们可以为用户改进很多。 这篇文章讨论了…