代码随想录第29天|491.递增子序列 46.全排列 47.全排列 II

news2024/11/18 13:25:08

目录:

491.递增子序列

46.全排列

47.全排列 II 

491.递增子序列

491. 非递减子序列 - 力扣(LeetCode)

代码随想录 (programmercarl.com)

回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列_哔哩哔哩_bilibili

给你一个整数数组 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

第一反应是把所有的子序列列出来,然后再判断是不是递增子序列。

 回溯三部曲:
1、确定参数:除了path、result和startIndex之外,还需要一个集合来存放一层的值,避免在同一层中出现重复的值从而出现重复的子序列:

// 声明一个结果集合,用于存储所有满足条件的子序列
    List<List<Integer>> result = new ArrayList<>();
    // 声明一个路径集合,用于存储当前正在构建的子序列
    List<Integer> path = new ArrayList<>();
    HashSet<Integer> hs = new HashSet<>();
private void backTracking(int[] nums, int startIndex) {
}

 2、确定终止条件:当path的大小大于1,且path里面的值符合曾序序列的要求时,把path加到result数组里,然后返回:

if (path.size() >= 2)
            result.add(new ArrayList<>(path));

3、确定单层搜索的逻辑:
同一父节点下同层元素中,如果一个数字在同层已经出现过的话,就不能再使用了,因为再次使用的话会出现重复子序列:

 for (int i = startIndex; i < nums.length; i++) {
            // 如果当前路径不为空,并且路径中的最后一个数字大于当前数字,或者当前数字已经在路径中出现过,则跳过当前数字
            if (!path.isEmpty() && path.get(path.size() - 1) > nums[i] || hs.contains(nums[i]))
                continue;
            // 将当前数字加入到路径中
            hs.add(nums[i]);
            path.add(nums[i]);
            // 递归调用,继续寻找以当前数字结尾的子序列
            backTracking(nums, i + 1);
            // 回溯,将当前数字从路径中移除,准备尝试其他可能的数字
            path.remove(path.size() - 1);
    }

综合代码:

// 定义一个名为 Solution 的类
class Solution {
    // 声明一个结果集合,用于存储所有满足条件的子序列
    List<List<Integer>> result = new ArrayList<>();
    // 声明一个路径集合,用于存储当前正在构建的子序列
    List<Integer> path = new ArrayList<>();
    
    // 主方法,入口点
    public List<List<Integer>> findSubsequences(int[] nums) {
        // 调用回溯函数,开始查找所有满足条件的子序列
        backTracking(nums, 0);
        // 返回结果集合
        return result;
    }
    
    // 回溯函数,用于查找所有满足条件的子序列
    private void backTracking(int[] nums, int startIndex) {
        // 如果当前路径的长度大于等于2,则将其加入到结果集合中
        if (path.size() >= 2)
            result.add(new ArrayList<>(path));            
        // 创建一个哈希集合,用于记录当前路径中已经出现过的数字
        HashSet<Integer> hs = new HashSet<>();
        // 遍历数组,从startIndex位置开始
        for (int i = startIndex; i < nums.length; i++) {
            // 如果当前路径不为空,并且路径中的最后一个数字大于当前数字,或者当前数字已经在路径中出现过,则跳过当前数字
            if (!path.isEmpty() && path.get(path.size() - 1) > nums[i] || hs.contains(nums[i]))
                continue;
            // 将当前数字加入到路径中
            hs.add(nums[i]);
            path.add(nums[i]);
            // 递归调用,继续寻找以当前数字结尾的子序列
            backTracking(nums, i + 1);
            // 回溯,将当前数字从路径中移除,准备尝试其他可能的数字
            path.remove(path.size() - 1);
        }
    }
}

这里仍然有一个小疑惑:为什么hs里面的数不需要弹出呢?

46.全排列 

. - 力扣(LeetCode)

代码随想录 (programmercarl.com)

组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列_哔哩哔哩_bilibili

给定一个不含重复数字的数组 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在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素。

List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
boolean[] used; // 用于标记数字是否被使用过

2、确定终止条件:叶子节点终止,即path长度和nums数组的大小一样时终止:

 if (path.size() == nums.length){
            // 将当前路径加入到结果集合中
            result.add(new ArrayList<>(path));
            return;
        }

3、确定单层搜索的逻辑:

// 遍历数组中的每个元素
        for (int i = 0; i < nums.length; i++){
            // 如果该元素已经被使用过,则跳过
            if (used[i]){
                continue;
            }
            // 标记该元素为已使用
            used[i] = true;
            // 将该元素加入到当前路径中
            path.add(nums[i]);
            // 递归调用,继续生成全排列结果
            permuteHelper(nums);
            // 回溯,将当前加入的元素移除
            path.removeLast();
            // 标记该元素为未使用
            used[i] = false;
        }

综合代码:

class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    boolean[] used; // 用于标记数字是否被使用过

    // 主函数,输入数组 nums,返回其全排列结果
    public List<List<Integer>> permute(int[] nums) {
        // 如果数组为空,则直接返回结果集合
        if (nums.length == 0){
            return result;
        }
        // 初始化 used 数组为与 nums 相同长度的布尔数组
        used = new boolean[nums.length];
        // 调用递归函数进行全排列
        permuteHelper(nums);
        // 返回全排列结果
        return result;
    }

    // 辅助递归函数,用于生成全排列结果
    private void permuteHelper(int[] nums){
        // 如果当前路径长度等于数组长度,表示已经得到一个全排列结果
        if (path.size() == nums.length){
            // 将当前路径加入到结果集合中
            result.add(new ArrayList<>(path));
            return;
        }
        // 遍历数组中的每个元素
        for (int i = 0; i < nums.length; i++){
            // 如果该元素已经被使用过,则跳过
            if (used[i]){
                continue;
            }
            // 标记该元素为已使用
            used[i] = true;
            // 将该元素加入到当前路径中
            path.add(nums[i]);
            // 递归调用,继续生成全排列结果
            permuteHelper(nums);
            // 回溯,将当前加入的元素移除
            path.removeLast();
            // 标记该元素为未使用
            used[i] = false;
        }
    }
}

47.全排列 II 

47. 全排列 II - 力扣(LeetCode)

代码随想录 (programmercarl.com)

回溯算法求解全排列,如何去重?| LeetCode:47.全排列 II_哔哩哔哩_bilibili

给定一个可包含重复数字的序列 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

如图,还是要考虑一个去重的逻辑。

1、确定参数:

 // 存放结果
    List<List<Integer>> result = new ArrayList<>();
    // 暂存结果
    List<Integer> path = new ArrayList<>();
  // 标记每个数字是否被使用过
  boolean[] used = new boolean[nums.length];

2、确定终止条件:

// 如果当前路径长度等于数组长度,说明已经找到一个排列
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path)); // 将当前路径加入结果集
            return; // 结束当前递归
        }

3、确定单层搜索逻辑:used[i-1]为false的时候,才保证了是树层上的数值不能相同,而不是树枝上。

// 遍历所有数字
        for (int i = 0; i < nums.length; i++) {
            // 如果当前数字已经被使用过,直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            // 如果当前数字未被使用过,开始处理
            if (used[i] == false) {
                used[i] = true; // 标记当前数字被使用过
                path.add(nums[i]); // 将当前数字加入路径
                backTrack(nums, used); // 递归处理下一层
                path.remove(path.size() - 1); // 回溯,移除当前数字
                used[i] = false; // 恢复当前数字的未使用状态
            }
        }

综合代码:

class Solution {
    // 存放结果
    List<List<Integer>> result = new ArrayList<>();
    // 暂存结果
    List<Integer> path = new ArrayList<>();

    // 主函数,入口
    public List<List<Integer>> permuteUnique(int[] nums) {
        // 标记每个数字是否被使用过
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false); // 初始化为未使用状态
        Arrays.sort(nums); // 对输入数组排序,确保相同数字相邻
        backTrack(nums, used); // 调用回溯函数
        return result; // 返回最终结果
    }

    // 回溯函数,用于搜索所有排列组合
    private void backTrack(int[] nums, boolean[] used) {
        // 如果当前路径长度等于数组长度,说明已经找到一个排列
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path)); // 将当前路径加入结果集
            return; // 结束当前递归
        }
        // 遍历所有数字
        for (int i = 0; i < nums.length; i++) {
            // 如果当前数字已经被使用过,直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            // 如果当前数字未被使用过,开始处理
            if (used[i] == false) {
                used[i] = true; // 标记当前数字被使用过
                path.add(nums[i]); // 将当前数字加入路径
                backTrack(nums, used); // 递归处理下一层
                path.remove(path.size() - 1); // 回溯,移除当前数字
                used[i] = false; // 恢复当前数字的未使用状态
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1567508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言——常用库函数的使用及模拟实现

C语言编译系统为使用者提供了很多的预定义库函数。用户在编写程序时&#xff0c;可以直接调用这些库函数。这里选择了一些常用的库函数&#xff0c;简单介绍各个函数的使用方法&#xff1a; 字符串函数 字符串函数函数分为长度不受限制的字符串函数&#xff0c;和长度受限制的…

图的应用解析

01&#xff0e;任何一个无向连通图的最小生成树(B )。 A.有一棵或多棵 B.只有一棵 C.一定有多棵 D.可能不存在 02.用Prim算法和Kruskal算法构造图的最小生成树&#xff0c…

每日一题————P5725 【深基4.习8】求三角形

题目&#xff1a; 题目乍一看非常的简单&#xff0c;属于初学者都会的问题——————————但是实际上呢&#xff0c;有一些小小的坑在里面。 就是三角形的打印。 平常我们在写代码的时候&#xff0c;遇到打印三角形的题&#xff0c;一般简简单单两个for循环搞定 #inclu…

Element-Plus日期选择组件封装农历日期

背景 在使用element-plus开发项目过程中&#xff0c;需要填入人员的生卒日期&#xff0c;经观察&#xff0c;对于大部分人来说&#xff0c;这类日期通常是农历日期&#xff0c;然而我们在系统建设过程中&#xff0c;对于日期字段&#xff0c;约定成俗的都会使用公历日期&#…

机器学习的模型校准

背景知识 之前一直没了解过模型校准是什么东西&#xff0c;最近上班业务需要看了一下&#xff1a; 模型校准是指对分类模型进行修正以提高其概率预测的准确性。在分类模型中&#xff0c;预测结果通常以类别标签形式呈现&#xff08;例如&#xff0c;0或1&#xff09;&#xf…

day03-Docker

1.初识 Docker 1.1.什么是 Docker 1.1.1.应用部署的环境问题 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会碰到一些问题&#xff1a; 依赖关系复杂&#xff0c;容易出现兼容性问题开发、测试、生产环境有差异 例如一个项目中&#xff0c;部署时需要依…

拯救者Legion R9000X 2021(82HN)原装出厂Win10系统镜像ISO下载

lenovo联想拯救者笔记本R9000X 2021款原厂Windows10系统安装包&#xff0c;恢复出厂开箱状态预装OEM系统 链接&#xff1a;https://pan.baidu.com/s/1tx_ghh6k0Y9vXBz-7FEQng?pwd7mih 提取码&#xff1a;7mih 原装出厂系统自带所有驱动、出厂主题壁纸、系统属性联机支持标…

C++核心编程——4.2(2)对象的初始化和清理

4.2.5 深拷贝与浅拷贝 浅拷贝&#xff1a;编译器提供的简单的赋值拷贝操作 深拷贝&#xff1a;在堆区重新申请空间&#xff0c;进行拷贝操作 示例&#xff1a; class Person { public://无参&#xff08;默认&#xff09;构造函数Person() {cout << "无参构造函数…

并发编程之线程池的应用以及一些小细节的详细解析

线程池在实际中的使用 实际开发中&#xff0c;最常用主要还是利用ThreadPoolExecutor自定义线程池&#xff0c;可以给出一些关键的参数来自定义。 在下面的代码中可以看到&#xff0c;该线程池的最大并行线程数是5&#xff0c;线程等候区&#xff08;阻塞队列)是3&#xff0c;即…

基于Python的微博旅游情感分析、微博舆论可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

JAVAEE之Cookie/Session

1.Cookie HTTP 协议自身是属于 "无状态" 协议. "无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系. 但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的. 例如登陆网站成功后, 第二…

绩效考核存在合理性、公平性、客观性吗?

目录 一、绩效考核流于形式&#xff1a;没有实际考核过 二、考核结果的确定: 主管一人说了算 三、考核结果&#xff1a; 与绩效奖金挂钩吗&#xff1f; 四、考核的滥用&#xff1a;成为公司排挤迫使员工离职的手段 五、公司说&#xff1a; 让你滚蛋&#xff0c;谁还会发你奖…

[HackMyVM]靶场Boxing

难度:Medium kali:192.168.56.104 靶机:192.168.56.143 端口扫描 ┌──(root㉿kali2)-[~/Desktop] └─# nmap 192.168.56.143 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-03 19:25 CST Nmap scan report for staging-env.boxing.hmv (192.168.56.143) Host …

智慧展览馆:基于AI智能识别技术的视频智慧监管解决方案

一、建设背景 随着科技的不断进步和社会安全需求的日益增长&#xff0c;展览馆作为展示文化、艺术和科技成果的重要场所&#xff0c;其安全监控系统的智能化升级已成为当务之急。为此&#xff0c;旭帆科技&#xff08;TSINGSEE青犀&#xff09;基于视频智能分析技术推出了展览馆…

路径规划——曲线拟合详解(一):多项式轨迹与QP优化(minimum-snap算法核心部分)

前言 历经一个多星期时间&#xff0c;我们在路径规划——搜索算法部分讲解了7种常见的路径搜索算法&#xff0c;每一种算法的链接放在下面了&#xff0c;有需要的朋友点击跳转即可&#xff1a; 路径规划——搜索算法详解&#xff08;一&#xff09;&#xff1a;Dijkstra算法详…

redis之穿透、击穿、雪崩

目录 缓存雪崩 问题描述 缓存雪崩 问题描述 key 对应的数据存在&#xff0c;但在 redis 中过期&#xff0c;此时若有大量并发请求过来&#xff0c;这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存&#xff0c;这个时候大并发的请求可能会瞬间把后端数据库压垮。…

GPTfinger赋能指纹识别技术,德施曼赢下这一局

生成式AI这场战局里&#xff0c;德施曼跑赢同行&#xff0c;成为行业里第一个引入GPT技术的智能锁厂商。 4月2日&#xff0c;德施曼智能锁在北京举办了新品发布会之前的“创新技术预沟通会”&#xff0c;向到场媒体揭晓了最新技术突破之一——GPTfinger。 「智哪儿」认为&…

关闭PyCharm中因双击Shift而跳出的搜索框

有时候老是多次按到shift而跳出一个搜索框&#xff0c;本来在编写代码&#xff0c;怎么突然就开始搜索了&#xff0c;非常的烦人。 其实这个搜索框叫做“随处搜索”。 关闭步骤 1、打开PyCharm的设置。 2、在设置-高级设置中勾选-禁用双击修改键快捷键即可。

4.3学习总结

[HNCTF 2022 WEEK2]Canyource&#xff08;无参数&#xff09; 通过这题又接触了一种无参数RCE的方法&#xff0c;前面学习的getallheaders只有在apache环境下才能使用&#xff0c;具有一定的局限性 这里是利用php函数来构造读取flag的方法 localeconv() – 函数返回一个包含本…

Ribbon有哪些负载均衡策略

负载均衡类都实现了IRule接口。 RandomRule&#xff1a;随机的选用一个实例 RoundRobinRule&#xff1a;轮询的使用实例 RetryRule&#xff1a;在轮询的基础上加了一个错误重试机制&#xff0c;在deadline时间内会不断的重试 WeightResponeTimeRule&#xff1a;根据权重去做…