算法训练营day29

一、组合

参考链接77. 组合 - 力扣(LeetCode)

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

public class Solution {

    public List<List<Integer>> combine  (int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private void dfs(int n, int k, int index, Deque<Integer> path, List<List<Integer>> res) {
    //如果当前path的大小和 k相等,则将结果保存到ArrayList当中
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 剪枝优化 i <= n - (k - path.size()) + 1 背吧,归纳法
        for (int i = index; i <= n - (k - path.size()) + 1; i++) {
            path.addLast(i);
            dfs(n, k, i + 1, path, res);
            path.removeLast();
        }
    }
}
二、递增子序列

核心理解:set去重

set在dfs的实现的方式是 new HashSet<>(),而不是在函数外定义的set,每一次递归都会生成新的Set,

class Solution {
    // 定义全局变量保存结果
    List<List<Integer>> res = new ArrayList<>();
    
    public List<List<Integer>> findSubsequences(int[] nums) {
        // idx 初始化为 -1,开始 dfs 搜索。
        dfs(nums, -1, new ArrayList<>());
        return res;
    }

    private void dfs(int[] nums, int idx, List<Integer> curList) {
        // 只要当前的递增序列长度大于 1,就加入到结果 res 中,然后继续搜索递增序列的下一个值。
        if (curList.size() > 1) {
            res.add(new ArrayList<>(curList));
        }

        // 在 [idx + 1, nums.length - 1] 范围内遍历搜索递增序列的下一个值。
        // 借助 set 对 [idx + 1, nums.length - 1] 范围内的数去重。
//注意 set是 数中每一层的 变量,到达下一层时又new了HashSet
        Set<Integer> set = new HashSet<>();
        for (int i = idx + 1; i < nums.length; i++) {
            // 1. 如果 set 中已经有与 nums[i] 相同的值了,说明加上 nums[i] 后的所有可能的递增序列之前已经被搜过一遍了,因此停止继续搜索。
            if (set.contains(nums[i])) { 
                continue;
            }
            set.add(nums[i]);
            // 2. 如果 nums[i] >= nums[idx] 的话,说明出现了新的递增序列,因此继续 dfs 搜索(因为 curList 在这里是复用的,因此别忘了 remove 哦)
            if (idx == -1 || nums[i] >= nums[idx]) {
                curList.add(nums[i]);
                dfs(nums, i, curList);
                curList.remove(curList.size() - 1);
            }
        }
    }
}
  1. 比如 4 7 3 2 1 7 … 这个数组,当curList遍历到 4 7 的时候,进入递归,一直遍历直到 出现 第二个7,这时候出现了 4 7 7 序列添加到结果中
  2. 回溯到 4 7 ,在当前层的for循环进行遍历,再次遍历到 第二个 7,判断set中有7,通过1我们知道 4 7 7 开头的数组组合我们在递归中已经将其实现,所以这一枝我们进行剪枝
  3. 回归set的作用,处理数组中出现相同值时 出现重复结果(也就是去重)

参考链接46. 全排列 - 力扣(LeetCode)

三、全排列
import java.util.ArrayList;
import java.util.List;


public class Solution {

    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        // 使用一个动态数组保存所有可能的全排列
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }
	//使用一个数组表示某个数是否已经被选过,选过为true,否则为false
        boolean[] used = new boolean[len];
        List<Integer> path = new ArrayList<>();
        dfs(nums, len, 0, path, used, res);
        return res;
    }
//nums 当前数组,len 数组长度,depth表示深度,path 当前走过的路径,used 某个数字是否被选中, res 结果数组
    private void dfs(int[] nums, int len, int depth,
                     List<Integer> path, boolean[] used,
                     List<List<Integer>> res) {
// 如果遍历到 深度 和 长度 相等表示所有数字都已经被选择过了
        if (depth == len) {
            res.add(path);
            return;
        }

        // 在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。 ****这段是重点核心代码,需要背
        for (int i = 0; i < len; i++) {
            if (!used[i]) {
                path.add(nums[i]);
                used[i] = true;

                dfs(nums, len, depth + 1, path, used, res);
                // 注意:下面这两行代码发生 「回溯」,回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
                used[i] = false;
                path.remove(path.size() - 1);
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        Solution solution = new Solution();
        List<List<Integer>> lists = solution.permute(nums);
        System.out.println(lists);
    }
}
四、全排列2

使用存在重复数字的数组来生成新的全排列数组,要求去重

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

public class Solution {

    public List<List<Integer>> permuteUnique(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }

        // 排序(升序或者降序都可以),排序是剪枝的前提
        Arrays.sort(nums);

        boolean[] used = new boolean[len];
        // 使用 Deque 是 Java 官方 Stack 类的建议
        Deque<Integer> path = new ArrayDeque<>(len);
        dfs(nums, len, 0, used, path, res);
        return res;
    }

    private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
        if (depth == len) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < len; ++i) {
            if (used[i]) {
                continue;
            }

            // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
            // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择,被撤销选择为false
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }

            path.addLast(nums[i]);
            used[i] = true;

            dfs(nums, len, depth + 1, used, path, res);
            // 回溯部分的代码,和 dfs 之前的代码是对称的
            used[i] = false;
            path.removeLast();
        }
    }
}

A 1分24秒到2分00秒:讲了为什么要先排序 因为结果是要去重的,而结果都是列表形式的,那列表怎么判重呢?就是排序之后一个一个的比较,所以那还不如先排序之后再计算,在计算的过程中判断是否有重复,免得对每个结果再做一次排序去重操作。再一个,排序之后相同的元素一定重复吗?不一定,不同结果经过排序之后也可能相同

B 为什么" nums[i-1] 的used状态是被选择的,那么说明当前的nums[i] 是 nums[i-1]的下一层路径。"

原因:因为往下层递归的话,一直再往下层走,dfs还未return,也就是说used还没有被回溯为未选择状态,所以同一条分支上,nums[i-1] 的used状态一定是被选择的。

为什么“ 如果 nums[i-1] 的状态是没被选择的,那么说明当前 的nums[i] 是nums[i-1] 同层路径。”

原因:递归到叶节点,开始往上回溯了,回溯到某一层时把used[i-1]回溯为未选择状态,然后for循环i++横向移动,自然这时再判断used[i-1]就一定是未选择状态。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DevOps】Jenkins 集成Docker

目录 1. 安装 Docker 和 Jenkins 2. 在 Jenkins 中安装 Docker 插件 3. 配置 Docker 连接 4. 创建 Jenkins Pipeline 5. 示例 Pipeline 脚本 6. 运行 Jenkins Job 7. 扩展功能 8、docker配置测试连接的时候报错处理 将 Docker 与 Jenkins 集成可以实现持续集成和持续交…

Java学习第05天-编程思维与编程能力

文章目录 综合应用案例&#xff1a;找素数数组元素的复制数字加密模拟双色球 综合应用 涉及的知识点&#xff1a; 变量、数组运算符&#xff1a;基本运算符、关系运算符、逻辑运算符流程控制&#xff1a;if、switch、for、while、死循环、循环嵌套跳转关键字&#xff1a;break、…

初识C语言——第十一天

操作符&#xff1a; 1. 算数操作符&#xff1a; - * / % 2. 移位操作符&#xff1a; >> &#xff08;右移&#xff09; << &#xff08;左移&#xff09; 移动的是二进制位 例如&#xff1a; int ba<<1; 3. 位操作符&#xff1a; & 按位与 | 按位…

数仓开发:DIM层数据处理

一、了解DIM层 这个就是数仓开发的分层架构 我们现在是在DIM层&#xff0c;从ods表中数据进行加工处理&#xff0c;导入到dwd层&#xff0c;但是记住我们依然是在DIM层&#xff0c;而非是上面的ODS和DWD层。 二、处理维度表数据 ①先确认hive的配置 -- 开启动态分区方案 -- …

Unity技术学习:RenderMesh、RenderMeshInstanced

叠甲&#xff1a;本人比较菜&#xff0c;如果哪里不对或者有认知不到的地方&#xff0c;欢迎锐评&#xff08;不玻璃心&#xff09;&#xff01; 导师留了个任务&#xff0c;渲染大量的、移动的物体。 当时找了几个解决方案&#xff1a; 静态批处理&#xff1a; 这东西只对静…

Springboot+Vue项目-基于Java+MySQL的校园二手书交易平台系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Netty 网络编程深入学习【一】:ByteBuffer 源码解析

ByteBuffer源码阅读 ByteBuffer是一个用于处理字节数据的缓冲区类。它是Java NIO 包的一部分&#xff0c;提供了一种高效的方式来处理原始字节数据。 ByteBuffer 可以用来读取、写入、修改和操作字节数据&#xff0c;它是一种直接操作字节的方式&#xff0c;比起传统的 InputSt…

如何高速下载,百度 阿里 天翼 等网盘内的内容

如何高速下载&#xff0c;百度 阿里 天翼 等网盘内的内容&#x1f3c5; 前言教程下期更新预报&#x1f3c5; 前言 近段时间经常给大家分享各种视频教程&#xff0c;由于分享的资料是用迅雷网盘存的&#xff0c;但是绝大部分用户都是使用的某度&#xff0c;阿某的这些网盘&…

AI工具大揭秘:如何改变我们的工作和生活

文章目录 &#x1f4d1;前言一、常用AI工具&#xff1a;便利与高效的结合1.1 语音助手1.2 智能推荐系统1.3 自然语言处理工具 二、创新AI应用&#xff1a;不断突破与发展2.1 医疗诊断AI2.2 智能家居2.3 无人驾驶技术 三、AI工具在人们生活中的应用和影响3.1 生活方式的变化3.2 …

旅游系列之:庐山美景

旅游系列之&#xff1a;庐山美景 一、路线二、住宿二、庐山美景 一、路线 庐山北门乘坐大巴上山&#xff0c;住在上山的酒店东线大巴游览三叠泉&#xff0c;不需要乘坐缆车&#xff0c;步行上下三叠泉即可&#xff0c;线路很短 二、住宿 长江宾馆庐山分部 二、庐山美景

Ubuntu 20.04安装桌面XFCE

1.安装Xfce软件包 $ sudo apt update $ sudo apt install xfce42.选择gdm3和lightdm 我这里选择的是lightdm LightDM&#xff0c;即&#xff1a;Light Display Manager&#xff0c;是一个全新的、轻量的Linux桌面的桌面显示管理器&#xff0c;而传统的Ubuntu用的是GNOME桌面…

HR面试测评,招聘行政部门主管的人才测评方案

把合适的人放入到合适的岗位中&#xff0c;可以实现双赢&#xff08;企业效益和个人成就&#xff09;&#xff0c;人力资源管理者HR又该如何去发掘行政主管岗位的人才&#xff1f; 行政部门主管属于管理层岗位&#xff0c;在企业发展中有重要的作用&#xff0c;可以协助企业…

Docker私有镜像仓库搭建 带图形化界面的

搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址&#xff1a;https://hub.docker.com/_/registry 先配置私服的信任地址: # 打开要修改的文件 vi /etc/docker/daemon.json # 添加内容&#xff1a; "insecure-registries":["http://192.…

FastAPI - Pydantic相关应用

参考链接&#xff1a;Pydantic官方文档 文章目录 定义数据模型创建模型实例数据验证数据转换模型转换模型更新模型配置辅助类Fieldvalidator Pydantic 是一个 Python 库&#xff0c;主要用于数据验证和管理。数据验证是指检查数据是否符合预定的规则和格式&#xff0c;比如检查…

xss注入漏洞解析(下)

DOM型XSS 概念 DOM全称Document Object Model&#xff0c;使用DOM可以使程序和脚本能够动态访问和更新文档的内容、结 构及样式。DOM型XSS其实是一种特殊类型的反射型XSS&#xff0c;它是基于DOM文档对象模型的一种漏洞。 HTML的标签都是节点&#xff0c;而这些节点组成了DOM的…

TeXCount failed. Please refer to LaTeX Utilities Output for details.

写LaTeX的时候总是报这个错、看了下网上也没有什么好的解决方法、就是单词计数器无法使用 我的解决方法: 看下驱动器是否还在&#xff0c;不在的话重新安装一下、不要把驱动器给删除了

开关门机关

根物体创建动画 子物体录制动画 ctrl6&#xff1a;调用动画窗口 添加关键帧&#xff1a;输入添加关键帧到第几帧&#xff0c;然后点击录制&#xff0c;最后在该物体的面板上修改其位置等&#xff0c;记得添加完要结束录制 搞个父物体是为了让动画的可移植性变高 设置触发器方…

基于OpenCv的图像金字塔

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

人工智能大模型应用指南

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

【Flask 系统教程 2】路由的使用

Flask 是一个轻量级的 Python Web 框架&#xff0c;其简洁的设计使得构建 Web 应用变得轻而易举。其中&#xff0c;路由是 Flask 中至关重要的一部分&#xff0c;它定义了 URL 与视图函数之间的映射关系&#xff0c;决定了用户请求的处理方式。在本文中&#xff0c;我们将深入探…
最新文章