回溯算法 —— 子集问题

news2024/11/22 22:19:14

如果说组合问题可以说是思考如何使用回溯算法收割叶子节点的结果、

那么子集问题就是思考如何使用回溯算法收割每一个节点的结果

回溯算法的解题三部曲:1.确定传入的参数 2.确定终止条件 3.确定单层遍历逻辑

​​​​​​78. 子集

本题就是经典的子集问题了,那么使用回溯算法的解题三部曲来解决问题

1.确定传入的参数 本题只需要传入题目给定的数组以及下一层遍历起始位置即可

    public void subsets(int[] nums,int startIndex)

 2.确定终止条件

子集问题中我们其实不需要终止条件,因为我们需要收割每一个节点的结果,但是我们也可以写上终止条件比如

        if(startIndex >= nums.length){
            return;
        }

3.确定单层遍历逻辑

由于我们需要把每一个节点的结果都放进结果集内,所以我们只需要在每一层递归里面都将上一层的结果放进结果集内即可

        ans.add(new ArrayList(path));

        for(int i = startIndex;i < nums.length;i++){
            path.add(nums[i]);
            subsets(nums,i+1);
            path.removeLast();
        }

最后贴上我们完整代码

class Solution {
    List<List<Integer>> ans = new ArrayList();
    LinkedList<Integer> path = new LinkedList();

    public List<List<Integer>> subsets(int[] nums) {
        subsets(nums,0);
        return ans;
    }

    public void subsets(int[] nums,int startIndex){
        ans.add(new ArrayList(path));
        
        if(startIndex >= nums.length){
            return;
        }

        for(int i = startIndex;i < nums.length;i++){
            path.add(nums[i]);
            subsets(nums,i+1);
            path.removeLast();
        }
    }
}

90. 子集 II

本题就是上一题的基础上增加了一点点难度 我们应该如何完成去重操作?

这个问题其实在组合问题我们已经解决了:

先将数组进行排序好然后前一个数字与当前数字重复时,我们就跳过当前循环,执行下一次循环

原因在于前一个数字所构成的组合与当前数字构成的组合是一样的,所以我们可以跳过当前循环避免重复操作

            if(i > startIndex && nums[i] == nums[i-1]){
                continue;
            }

这里需要注意的是 我们的 i 是  i > startIndex 而不是 i > 0 

如果我们将 写成 i > 0 那么就会出现这种情况

很明显如果写成 i > 0 那么就一定会漏掉很多结果,原因在于 写成 i > 0就会导致树枝去重,在 1 这个树枝上都会完成去重操作,但是如果写成 i > startIndex 我们就能保证只在树层去重而不是树枝去重

在这里我画了简单一个图来帮助大家理解什么叫做树枝和树层

最后贴上我们的完整代码

class Solution {
    List<List<Integer>> ans = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        subsetsWithDupHelper(nums,0);
        return ans;
    }

    public void subsetsWithDupHelper(int[] nums,int startIndex){
        ans.add(new ArrayList(path));
        
        if(startIndex >= nums.length){
            return;
        }

        for(int i = startIndex;i < nums.length;i++){
            if(i > startIndex && nums[i] == nums[i-1]){
                continue;
            }
            path.add(nums[i]);
            subsetsWithDupHelper(nums,i+1);
            path.removeLast();
        }
    }
}

491. 递增子序列

这题乍一看好像和刚才的子集II问题是一样的,同样是去重与收集子集

但是我们要注意题目的要求是递增子序列

我们在子集II问题中首先对数组进行了排序 然后再进行一系列操作,但是如果这一题也使用排序,那么不就打乱了题目原本的顺序了吗?数组内的数字永远都是递增的,所以上一题的解法在这里不适用

那么怎么才能解决这题呢?

实际上在组合问题里面我们提到过相关的解法,可以使用一个数组来标记哪一些数字使用过,从而达到不使用排序也可以完成去重效果

依旧使用我们的回溯三部曲:

1.确定参数

    public void findSubsequencesHelper(int[] nums,int startIndex)

2.确定终止条件

        if(path.size() > 1){
            result.add(new ArrayList(path));
        }

3.确定单层遍历的逻辑

我在这里使用的Set这个数据结构来帮助我完成树层去重的操作,大家也可以使用数组的形式进行树层去重

        HashSet<Integer> set = new HashSet();
        for(int i = startIndex;i < nums.length;i++){
            if(!path.isEmpty() && path.get(path.size() - 1) > nums[i] 
                    || set.contains(nums[i])){
                continue;
            }
            set.add(nums[i]);
            path.add(nums[i]);
            findSubsequencesHelper(nums,i+1);
            path.removeLast();
        }

注意我这里在进行是否是递增子序列的判断时使用的是链表的最后一位数字与当前数字进行比较

为什么呢?我这里举一个例子

[1,2,3,4,5,6,7,8,9,10,1,1,1,1,1]

当出现这种例子时,如果使用的是

            if(i > 0 && nums[i] >= nums[i-1] 
                    || set.contains(nums[i])){
                continue;
            }

那么最后输出的一个结果肯定包括

 [1,2,3,4,5,6,7,8,9,10,1,1,1,1]

原因就在于我们是在数组里面进行比较的,不能保证我们的子序列一定是递增的,但是如果我们使用链表的最后一位数字与当前数字进行比较那么就没有这个问题了


46. 全排列

这里实际上还是子集问题,唯一不同的是我们每层循环的开始位置都是0,同时我们需要使用一个used数组来标记哪些数字使用过

简单画一个树状图(关键分支)

递归三部曲:

1.确定参数:由于我们每层循环的开始位置都是0,所以不需要传入下一层的起始位置了

public void permuteHelper(int[] nums)

2.确定终止条件

当我们的path的长度与数组的长度相等时说明此时就完成全排列

        if(path.size() == nums.length){
            ans.add(new ArrayList(path));
        }

 3.确定单层遍历条件

        for(int i = 0;i < nums.length;i++){
            if(used[i] == true){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            permuteHelper(nums);
            used[i] = false;
            path.removeLast();
        }

当我们的used数组是true,说明这个数字已经被使用过了,那么就跳过本次循环,进入下一次循环,当回溯到本层时,我们就需要把这个used再调整成未被使用的状态

最后贴上完整代码

class Solution {

    List<List<Integer>> ans = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    boolean[] used;

    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new boolean[nums.length];
        Arrays.sort(nums);
        permuteUniqueHelper(nums,0);
        return ans;
    }

    public void permuteUniqueHelper(int[] nums,int startIndex){
        if(path.size() == nums.length){
            ans.add(new ArrayList(path));
        }

        for(int i = 0;i < nums.length;i++){
            //used[i-1] == false表示树层去重
            if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true){
                continue;
            }
            if(used[i] == true){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            permuteUniqueHelper(nums,i+1);
            used[i] = false;
            path.removeLast();
        }
    }
}

47. 全排列 II

本题就是在上一题进行了一点拓展,如何进行去重操作之前的题目也讲过了,这里就不重复赘述了

但本题的关键在于如何进行去重的逻辑

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

我这里使用的是 used[i - 1] == false 来进行树层去重的操作,当nums[i-1] == false时说明nums[i-1]已经完成了全排列,所以我们跳过当前循环进入到下一次循环

当然我们也可以使用 used[i - 1] == true 来完成去重操作,只不过如果使用 used[i - 1] == true 那么就是在树枝上完成去重了,这里同样画一幅图来说明

最后贴上我们的完整代码

class Solution {

    List<List<Integer>> ans = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    boolean[] used;

    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new boolean[nums.length];
        Arrays.sort(nums);
        permuteUniqueHelper(nums,0);
        return ans;
    }

    public void permuteUniqueHelper(int[] nums,int startIndex){
        if(path.size() == nums.length){
            ans.add(new ArrayList(path));
        }

        for(int i = 0;i < nums.length;i++){
            if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false){
                continue;
            }
            if(used[i] == true){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            permuteUniqueHelper(nums,i+1);
            used[i] = false;
            path.removeLast();
        }
    }
}

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

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

相关文章

【SpringMvc 丨跨域】

Spring MVC 支持跨域处理&#xff08;CORS&#xff09;。 CORS 简介处理CORS 过滤器CrossOrigin注解java配置xml配置 主页传送门&#xff1a;&#x1f4c0; 传送 简介 跨域是指在浏览器的同源策略下&#xff0c;不能执行其他网站的脚本。它是由浏览器的安全限制造成的&#xf…

C++程序入门(helloworld.cpp编写)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

辅助驾驶功能开发-控制篇(01)-基于PID的横向控制算法

1 文档概述 本文档主要描述Lateral Control(横向控制)设计的功能要求、性能要求、算法推导。 2 功能要求 横向控制(Lateral Control)系统根据上层运动规划输出的期望路径、曲率等信息进行跟踪控制,以减少跟踪误差,同时保证车辆行驶平稳性和舒适性。 3 性能要求 控制系统应…

类和对象【基础概念】

全文目录 类的定义定义方式 类的访问限定符封装&#xff08;面向对象的三大特性之一&#xff09; 类对象模型类对象的存储方式类对象的大小计算 this指针this指针的特性**this指针可以为空吗&#xff1f;** 类的定义 在C中&#xff0c;C语言中的结构体struct中除了定义变量外还…

深度学习中softmax激活函数的用法

在深度学习中&#xff0c;“softmax” 是一种常用的激活函数&#xff0c;它主要用于多类别分类任务中的输出层。“Softmax” 是 “soft maximum” 的缩写&#xff0c;它通过将输入的实数向量转换为概率分布&#xff0c;用于表示相应类别的概率。

Tomcat多实例部署和动静分离

一、多实例部署&#xff1a; 多实例&#xff1a;多实例就是在一台服务器上同时开启多个不同的服务端口&#xff0c;同时运行多个服务进程&#xff0c;这些服务进程通过不同的socket监听不同的服务端口来提供服务。 1.前期准备&#xff1a; 1.关闭防火墙&#xff1a;systemctl …

Postman —— 配置环境变量

PostMan是一套比较方便的接口测试工具&#xff0c;但我们在使用过程中&#xff0c;可能会出现创建了API请求&#xff0c;但API的URL会随着服务器IP地址的变化而改变。 这样的情况下&#xff0c;如果每一个API都重新修改URL的话那将是非常的麻烦&#xff0c;所以PostMan中也提供…

【宝藏系列】几款好用的 Spring Boot 内置工具类

【宝藏系列】几款好用的 Spring Boot 内置工具类 文章目录 【宝藏系列】几款好用的 Spring Boot 内置工具类断言对象字符串集合文件资源IO 流反射AOP&#x1f349;文末推荐【深入浅出Java虚拟机】 断言 断言是一个逻辑判断&#xff0c;用于检查不应该发生的情况 Assert 关键字…

ARM接口编程—IIC总线(exynos 4412平台)

IIC总线简介 IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线 主要用于近距离、低速的芯片之间的通信&#xff1b;IIC总线有两根双向的信号线一根数据线SDA用于收发数据&#xff0c;一根时钟线SCL用于通信双方时钟的同步&#xff1b;IIC总线硬件结构简单&#xff…

Vim9用netrw快速打开文件

Vim9有一个自带的文件浏览器——netrw&#xff0c;它的功能很强大。今天我们给它添加两个快捷命令&#xff1a; Ctrl回车键&#xff0c;一次打开多个文件Alt回车键&#xff0c;打开文件后自动关闭目录窗口 一、修改vimrc文件&#xff1a; 操作路径&#xff1a;编辑》启动设置…

Python判断多个文件夹的文件夹名是否包含“分公司”或“营销中心”怎么处理?(方法二)...

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 先帝称之曰能&#xff0c;是以众议举宠为督。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python最强王者群【哎呦喂 是豆子&#xff5e;】问了一…

SOME/IP

介绍 SOME/IP是一种汽车中间件解决方案&#xff0c;可用于控制消息。它从一开始就被设计为完美地适应不同尺寸和不同操作系统的设备。这包括小型设备&#xff0c;如相机、AUTOSAR 设备&#xff0c;以及头戴设备或远程通信设备。它还确保SOME/IP支持信息娱乐域以及车辆中其他域…

AmazonS3Exception: The specified key does not exist

使用S3近端包下载文件时&#xff0c;遇到这个问题&#xff0c;感觉像是设置的桶名称或者文件名没有找到&#xff0c;最后发现是桶名最后多了一个”/“&#xff0c;S3是根据桶名称文件名寻址&#xff0c;中间不需要添加/ 报错桶名&#xff1a;https://img-cdn.abc.com/eb3e9d5d…

机器学习(12)---梯度下降(含手写公式、推导过程和手写例题)

梯度下降 一、概述1.1 梯度下降的用途1.2 梯度下降公式 二、公式讲解2.1 推导过程2.2 例题 一、概述 1.1 梯度下降的用途 1. 使用线性回归的平方误差成本函数时&#xff0c;成本函数不会也永远不会有多个局部最小值。因为它是凸函数&#xff0c;只有单一的全局最小值。通俗地说…

工业机器人仿真参考

最近有一些朋友看到我做的关于Unity3d仿真机器人的项目&#xff0c;本次我在平台做以分享&#xff0c;希望的朋友或者有需要在此基础做开发的可以参考下。 开发工具&#xff1a; 下位机&#xff1a;Unity3D 上位机&#xff1a;Visual Studio 机械臂模型&#xff1a;TH6-QKM…

java word文档 转 html文件

用java将word转为html文档 1、简介2、添加依赖3、代码示例 1、简介 最近&#xff0c;因项目需要&#xff0c;需要对word文档进行解析拆分&#xff0c;感觉直接解析word有点麻烦&#xff0c;于是想到&#xff0c;先将word转为html文档&#xff0c;然后用jsoup解析html文件更方便…

Debian 12快速安装图解

文章目录 Debian 12安装图解创建虚拟机安装系统登录并用光盘离线安装sudo、curl解决Linux下sudo更改文件权限报错保存快照debain添加在线源(配置清华源)参考 Debian 12安装图解 Debian选择CD安装非常慢&#xff0c;本次安装选择DVD离线安装。 下载 https://www.debian.org/CD…

【遥感变化检测综述】—《多时相遥感影像的变化检测研究现状与展望》

作者&#xff1a;张 祖 勋&#xff0c;姜 慧 伟&#xff0c;庞 世 燕&#xff0c;胡 翔 云 论文连接&#xff1a;多时相遥感影像的变化检测研究现状与展望 — 张祖勋 1、内容概述 本文主要从几何和语义两个角度对变化检测方法进行了分析和归纳总结&#xff0c;重点分析了几何信…

SpringMVC之CRUD(增删改查)

SpringMVC之CRUD(增删改查) 数据库 # 创建表CREATE TABLE Student (sid INT PRIMARY KEY,sname VARCHAR(50),sage INT,spic VARCHAR(255));给student表插入数据 INSERT INTO Student (sid, sname, sage, spic) VALUES (1, John Do, 25, path/to/image1.jpg), (2, Jane Smith, …

宋浩概率论笔记(六)样本与统计量

参数估计的入门章节&#xff0c;为后面的参数估计与假设检验铺垫基础&#xff0c;难点在于背诵公式&#xff0c;此外对于统计量的理解一定要清晰——本质是多个随机变量复合而成的函数~