491.递增子序列, 46.全排列, 47.全排列 II

简介: **简介:**本文介绍了三道经典的回溯算法题目及其解法,分别是“491. 递增子序列”、“46. 全排列”和“47. 全排列 II”。其中,“491”要求从数组中找出所有不同的递增子序列,需注意去重且不能对原数组排序;“46”是求不含重复数字数组的所有全排列,使用布尔数组记录元素使用状态;“47”则针对含重复数字的数组求不重复的全排列,通过排序与树层去重实现。这些题目涵盖了回溯算法中的常见问题类型,如子集、排列以及去重处理,帮助读者深入理解回溯算法的核心思想与实现细节。代码示例详细展示了如何通过递归与剪枝优化解决问题,是学习回溯算法的经典案例。

题目:491. 递增子序列

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

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

示例 1:

输入:nums = [4,6,7,7]

输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

示例 2:

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

输出:[[4,4]]

提示:

1 <= nums.length <= 15

-100 <= nums[i] <= 100

思考历程与知识点:  

这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。

这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的90.子集II (opens new window)。

就是因为太像了,更要注意差别所在,要不就掉坑里了!

在90.子集II (opens new window)中我们是通过排序,再加一个标记数组来达到去重的目的。

而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。

所以不能使用之前的去重逻辑。

题解:

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

 题目:46. 全排列

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

示例 1:

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

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

示例 2:

输入:nums = [0,1]

输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]

输出:[[1]]

提示:

1 <= nums.length <= 6

-10 <= nums[i] <= 10

nums 中的所有整数 互不相同

思考历程与知识点:  

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。

题解:

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

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

示例 1:

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

输出:

[[1,1,2],

[1,2,1],

[2,1,1]]

示例 2:

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

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

提示:

1 <= nums.length <= 8

-10 <= nums[i] <= 10

题解:

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


相关文章
|
5月前
|
自然语言处理 API 开发工具
端午出游高定:通义灵码+高德 MCP 10 分钟定制出游攻略
本文介绍了如何使用通义灵码编程智能体和高德MCP 2.0制作北京端午3天旅行攻略页面。首先需下载通义灵码AI IDE并获取高德申请的key,通过添加MCP服务、生成travel_tips.html文件完成初步攻略制作。用户可自定义页面风格、固定基础功能页面生成,并扩展MCP服务以满足多样化需求。文章还详细描述了开发专属MCP服务的过程,包括借助通义灵码编写代码、部署服务及调用工具,最终实现个性化旅游攻略生成。此外,提供了相关资料和参考链接,方便读者深入了解和实践。
|
5月前
|
算法
242.有效的字母异位词,349. 两个数组的交集,202题. 快乐数,1. 两数之和
以下是根据您提供的内容编写的一段简介: 在算法学习中,掌握高效解法和数据结构至关重要。本文通过解析四道LeetCode经典题目(242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和),深入探讨了多种优化思路与常用数据结构的应用。例如,使用数组统计字符频率判断异位词,利用`unordered_set`实现集合交集去重,借助`unordered_map`优化两数之和的查找效率,以及通过哈希表检测快乐数循环问题。这些方法不仅提升了时间复杂度,还强化了对`map`、`set`等数据结构的理解,为解决类似问题提供了重要参考。
|
开发者 黑灰产治理
专家博主最新专享福利上线!发文即得积分好礼!
最新专享福利上线!赢取海量积分兑换心仪礼品
725 0
|
5月前
|
JavaScript 前端开发 UED
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发四
本文介绍了Web组件开发与性能优化的相关内容。在Web组件开发部分,涵盖创建组件、设置样式与属性、添加事件和方法以及场景示例,如动态播放视频。性能提升方面,推荐使用数据懒加载、条件渲染替代显隐控制、Column/Row替代Flex、设置List组件宽高及调整cachedCount减少滑动白块等方法,以优化应用性能与用户体验。
236 56
|
5月前
|
存储 机器学习/深度学习 缓存
软考软件评测师——计算机组成与体系结构(分级存储架构)
本内容全面解析了计算机存储系统的四大核心领域:虚拟存储技术、局部性原理、分级存储体系架构及存储器类型。虚拟存储通过软硬件协同扩展内存,支持动态加载与地址转换;局部性原理揭示程序运行特性,指导缓存设计优化;分级存储架构从寄存器到外存逐级扩展,平衡速度、容量与成本;存储器类型按寻址和访问方式分类,并介绍新型存储技术。最后探讨了存储系统未来优化趋势,如异构集成、智能预取和近存储计算等,为突破性能瓶颈提供了新方向。
|
6月前
|
人工智能 自然语言处理 程序员
通义灵码 2.5 版发布上线,支持 Qwen3
示例中展示了通义灵码创建贪食蛇游戏的过程,包括代码优化、Bug修复和功能改进(如游戏结束后提示重新开始)。并通过AI总结了工具的核心能力,如实时续写、自然语言生码、单元测试生成等,帮助开发者高效编码并提升代码质量。
289 10
|
5月前
|
敏捷开发 设计模式 测试技术
软考软件评测师——软件工程之开发模型与方法
本内容主要介绍了软件开发过程中的核心概念及主流模型,包括瀑布模型、原型模型、增量模型、螺旋模型和敏捷开发等。每种模型各有优劣,适用于不同场景:瀑布模型适合需求明确的大型项目;螺旋模型适用于高风险复杂系统;增量模型支持模块化开发;原型模型适合需求模糊的小型项目;敏捷方法则强调灵活响应与持续交付。此外,还通过历年真题解析,深入探讨了各模型的应用场景及其特点,为实际开发提供了理论指导与实践经验。选择合适的开发模型需综合考虑需求明确度、项目规模、团队经验等因素。
|
5月前
|
测试技术
软考软件评测师——可靠性测试测试方法
软件可靠性是指软件在规定条件和时间内完成预定功能的能力,受运行环境、软件规模、内部结构、开发方法及可靠性投入等因素影响。失效概率指软件运行中出现失效的可能性,可靠度为不发生失效的概率,平均无失效时间(MTTF)体现软件可靠程度。案例分析显示,嵌入式软件需满足高可靠性要求,如机载软件的可靠度需达99.99%以上,通过定量指标评估其是否达标。
|
5月前
|
存储 缓存 程序员
软考软件评测师——计算机组成与体系结构(CPU指令系统)
本内容详细解析了计算机中央处理器(CPU)的核心架构及其关键组件的工作原理。首先介绍了CPU的四大核心模块:运算单元、控制单元、寄存器阵列和内部总线,并阐述其在数据处理中的核心职责。接着深入探讨了算术逻辑部件(ALU)的功能与专用寄存器的作用,以及通用寄存器对性能提升的意义。随后分析了控制单元的指令处理流程及特殊寄存器的功能。此外,还解析了寄存器系统的分类与设计特点,并对比了不同内存访问模式的特点与应用场景。最后,通过历年真题巩固相关知识点,帮助理解CPU各组件的协同工作及优化策略。
|
5月前
|
数据采集 自然语言处理 搜索推荐
Python内置函数ord()详解
`ord()` 是 Python 中用于将单个字符转换为对应 Unicode 码点的核心函数,支持 ASCII、多语言字符及特殊符号。其返回值为整数(范围 0-1114111),适用于字符编码验证、数据清洗、自定义排序、基础加解密等场景。使用时需注意参数长度必须为 1,否则会触发 `TypeError`。结合 `chr()` 函数可实现双向转换,进阶技巧包括多字节字符处理、编码范围检测及字符分类验证等。