代码随想录刷题day24|回溯理论基础组合问题

news2025/1/10 16:42:28

文章目录

  • day24学习内容
  • 一、修剪二叉搜索树
      • 1.1、什么是回溯法
      • 1.2、递归与回溯
      • 1.3、回溯法的效率
      • 1.4、回溯法解决的问题类型
      • 1.5、如何理解回溯法
      • 1.6、回溯算法模板
  • 二、组合问题
    • 2.1、思路
    • 2.2、正确写法-没有剪枝
      • 2.2.1、为什么不能写i < n
      • 2.2.2、为什么不能写startIndex==0
      • 2.2.3、为什么不能写backtracking(n, k, startIndex + 1);
    • 2.3、剪枝优化
      • 2.3.1、什么是剪枝
      • 2.3.2、剪枝优化的代码
      • 2.3.3、为什么是i <= n - (k - path.size()) + 1
  • 总结
    • 1.感想
    • 2.思维导图


day24学习内容

day24主要内容

  • 回溯理论基础
  • 组合问题

声明
本文思路和文字,引用自《代码随想录》

一、修剪二叉搜索树

1.1、什么是回溯法

基本概念:回溯法也称作回溯搜索法,是一种穷举搜索方式。在解决问题过程中,回溯法会试图分步去解决一个问题。每一步都基于当前的解尝试进一步的解决,如果发现当前的步骤不能得到有效的解或者达到了问题的边界,它将退回一步,尝试其他可能的路径。

1.2、递归与回溯

递归是回溯的基础,几乎所有的回溯问题都可以通过递归函数来实现。回溯法实际上是递归的一个副产品,它利用递归来探索并尝试不同的解决方案。
个人理解,回溯就是递归里面套了for循环

1.3、回溯法的效率

性能说明:虽然回溯法在理解上可能较为复杂且看似不易掌握,但它并不是一个高效的算法。其本质是穷举所有可能的解,然后从中选择满足条件的答案。为了提高效率,可以通过剪枝来减少搜索的空间,但这并不能改变其穷举的本质。
为何使用回溯法:对于某些问题,暴力搜索可能是唯一可行的解决方案。在没有更高效的算法可用时,即使是回溯法,也只能尽力而为,通过剪枝等手段来提高搜索效率。

1.4、回溯法解决的问题类型

  • 组合问题:在N个数中按一定规则找出k个数的集合。不强调顺序
  • 切割问题:按一定规则切割一个字符串的不同方式。
  • 子集问题:在一个N个数的集合中找出满足条件的子集。
  • 排列问题:N个数按一定规则全排列的不同方式。强调顺序
  • 棋盘问题:如N皇后问题,解数独等。

1.5、如何理解回溯法

树形结构的抽象:回溯法解决的问题可以被抽象为树形结构。每一次的递归都代表着向下探索树的一个分支,而每一次回溯则是返回到上一层。整个解空间形成了一棵树,其中的每个节点代表了解决问题的一个步骤。

1.6、回溯算法模板

回溯算法模板:详见卡尔模板


void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

二、组合问题

77.原题链接

2.1、思路

  • 和递归很像,可以想象转换成树,在数中进行遍历,只不过在树的字节点里面,有多个数字

2.2、正确写法-没有剪枝

class Solution {

    List<List<Integer>> result = new ArrayList();
    List<Integer> path = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }

    private void backtracking(int n, int k, int startIndex) {
        // 回溯终止条件
        if (path.size() == k) {
            result.add(new ArrayList(path));
            return;
        }

        // 在单层递归里面for循环
        for (int i = startIndex; i <= n; i++) {
            // 处理节点
            path.add(i);
            // 继续递归,注意这里使用i + 1而不是startIndex + 1
            backtracking(n, k, i + 1);
            // 回溯
            path.remove(path.size() - 1);
        }
    }
}

2.2.1、为什么不能写i < n

  • 根据题意,找出从 1 到 n 的所有可能的 k 个数的组合,并且在路径长度等于k时将其添加到结果列表中。
  • 举个例子, combine(4,2),其中 n=4 和 k=2
    • 意味着我们需要找到所有可能的从 1 到 4 中挑选 2 个数的组合。
    • 如果使用 i <= n,循环将遍历 1, 2, 3, 和 4,确保所有可能的组合都被考虑,包括包含 4 的组合(如 [3, 4])。
    • 如果使用 i < n,循环将只遍历 1, 2, 和 3,这意味着任何包含 4 的组合都将被错误地排除在外,导致算法不完整,无法生成所有正确的组合。

2.2.2、为什么不能写startIndex==0

  • 看题目啊大哥,写的这么清楚
  • 在这里插入图片描述

2.2.3、为什么不能写backtracking(n, k, startIndex + 1);

画个树,很好理解的。
在一层循环的入参,一定是当前选择的元素+1

2.3、剪枝优化

2.3.1、什么是剪枝

就是排除已经考虑过的元素

2.3.2、剪枝优化的代码

class Solution {

    List<List<Integer>> result = new ArrayList();
    List<Integer> path = new ArrayList();

    public List<List<Integer>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }

    private void backtracking(int n, int k, int startIndex) {
        // 回溯终止条件
        if (path.size() == k) {
            result.add(new ArrayList(path));
            return;
        }

        // 在单层递归里面for循环
        for (int i = startIndex; i <= n - k + path.size()+1; i++) {
            // 处理节点
            path.add(i);
            // 继续递归,注意这里使用i + 1而不是startIndex + 1
            backtracking(n, k, i + 1);
            // 回溯
            path.remove(path.size() - 1);
        }
    }
}

2.3.3、为什么是i <= n - (k - path.size()) + 1

先说结论:如果剩余可选的元素数量加上当前已选择的元素数量小于所需的元素数量 k,那么就没有必要继续搜索了,因为即使选择了所有剩余的元素,也无法满足组合中应有的元素数量。

推论,为什么是这样

  • n 是总的元素数量。
  • k 是需要选择的元素数量。
  • path.size() 是当前已经选择的元素数量。

我们需要的是,从当前位置 i 开始,至少还有 k - path.size() 个元素可供选择,以确保能够找到足够的元素组成一个有效的组合。即列表中剩余的元素要大于等于剩余需要选择的元素

  • n - i + 1 是从当前位置 i 开始,包括 i 在内,到结束一共有多少个元素可供选择。

为了保证至少还有 k - path.size() 个元素可选,我们需要有:

n - i + 1 >= k - path.size()

这个不等式简化后得到:
i <= n - k + path.size()

这就是剪枝条件 i <= n - (k - path.size()) + 1 的来源。

总结

1.感想

  • 回溯的第一天,剪枝的条件想了好久才想明白、。。

2.思维导图

本文思路引用自代码随想录,感谢代码随想录作者。

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

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

相关文章

phpcms头像上传漏洞引发的故事

目录 关键代码 第一次防御 第一次绕过 第二次防御 第二次绕过 第三次防御 第三次绕过 如何构造一个出错的压缩包 第四次防御 第四次绕过 本篇文章是参考某位大佬与开发人员对于文件包含漏洞的较量记录下的故事&#xff0c;因为要学习文件包含漏洞&#xff0c;就将大佬…

什么是 HTTPS?它是如何解决安全性问题的?

什么是 HTTPS&#xff1f; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种安全的通信协议&#xff0c;用于在计算机网络上安全地传输超文本&#xff08;如网页、图像、视频等&#xff09;和其他数据。它是 HTTP 协议的安全版本&#xff0c;通过使用加…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Grid)

网格容器&#xff0c;由“行”和“列”分割的单元格所组成&#xff0c;通过指定“项目”所在的单元格做出各种各样的布局。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 仅支持GridItem…

定时执行专家:自动截屏功能在电脑监控中的卓越应用

一、软件简介 定时执行专家&#xff0c;作为一款专业级的定时任务执行软件&#xff0c;不仅功能强大&#xff0c;而且操作简便。它支持25种任务类型&#xff0c;其中自动截屏功能尤为出色。通过这一功能&#xff0c;用户可以设定定时自动截取电脑屏幕&#xff0c;从而实现对电…

Java微服务轻松部署服务器

我们在日常开发微服务之后需要再服务器上面部署&#xff0c;那么如何进行部署呢&#xff0c;先把微服务的各个服务和中间件以及对应的端口列举出来&#xff0c;都打包成镜像&#xff0c;以及前端代码部署的nginx&#xff0c;使用docker-compose启动&#xff0c;访问服务器nginx…

系统重构后,对项目定制开发的兼容性问题

公司自实施产品线战略以来&#xff0c;基本推翻了全部旧有业务模块。后续以标准产品二次开发的模式进行项目开发。但在涉及到一些旧有系统二期、三期升级改造过程中。不可避免的需要解决旧有系统的客户定制化开发兼容性问题。也就是旧有系统定制开发的模块不能丢弃。重新开发从…

C++Qt学习——QPushButton、QRadioButton(单选按钮)、QCheckBox(复选按钮)

目录 1、QPushButton 1.1、创建一个新的项目&#xff0c;转到UI界面拖一个Push Button 1.2、Push Button的常用信号主要有四个&#xff0c;分别为 clicked(), pressed(), released(), toggled() 1.2.1、按住Push Button右键转到槽&#xff0c;选择信号函数 1.2.2、在Widget…

国创证券|资源再生概念持续活跃,超越科技两连板,大地海洋等走高

资源再生概念15日盘中再度走强&#xff0c;截至发稿&#xff0c;超越科技涨停斩获两连板&#xff0c;深水海纳涨超14%&#xff0c;大地海洋涨超12%&#xff0c;华新环保涨近9%&#xff0c;天奇股份、格林美、怡球资源等涨超5%。 消息面上&#xff0c;3月13日&#xff0c;国务院…

解决jsp request.getParameter乱码问题(兼容Tomcat 6~8三个版本)

JSP页面写法&#xff1a; <% page contentType"text/html; charsetutf-8" language"java" %> <% page import"java.io.*" %> <%! int getServerVersion(HttpServletRequest request) {ServletContext application request.getS…

前端组件化:构建高效应用的艺术

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

LeetCode每日一题——两数之和

两数之和OJ链接&#xff1a;1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a; 在读懂题目后很多人觉得这种题目很简单&#xff0c;但是不管怎么写&#xff0c;在VS等其他编译器上能跑成功&#xff0c;但是在LeetCode上就是没办法通过。…

Learn OpenGL 08 颜色+基础光照+材质+光照贴图

我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色&#xff0c;而是它所反射的(Reflected)颜色。物体的颜色为物体从一个光源反射各个颜色分量的大小。 创建光照场景 首先需要创建一个光源&#xff0c;因为我们以及有一个立方体数据&#xff0c;我们只需要进行…

【论文阅读笔记】Attention Is All You Need

1.论文介绍 Attention Is All You Need 2017年 NIPS transformer 开山之作 回顾一下经典&#xff0c;学不明白了 Paper Code 2. 摘要 显性序列转导模型基于包括编码器和解码器的复杂递归或卷积神经网络。性能最好的模型还通过注意力机制连接编码器和解码器。我们提出了一个新…

Redis部署方式(三)主从模式

在前面单机版的基础上&#xff0c;41为主&#xff0c;30为从。 一、主从搭建 1、主Redis安装 41机器redis主要配置 requirepass redis#!_41 bind 0.0.0.0 port 6379 daemonize yes 2、从redis安装 30机器redis主要配置 requirepass redis#!_30 bind 0.0.0.0 port 6380 da…

Oracle 部署及基础使用

1. Oracle 简介 Oracle Database&#xff0c;又名 Oracle RDBMS&#xff0c;简称 Oracle Oracle系统&#xff0c;即是以Oracle关系数据库为数据存储和管理作为构架基础&#xff0c;构建出的数据库管理系统。是目前最流行的客户/服务器&#xff08;client/server&#xff09;或…

关于Transfomer的思考

为何诞生 在说transformer是什么&#xff0c;有什么优势之类的之前&#xff0c;先谈一谈它因何而诞生。transformer诞生最重要的原因是早先的语言模型&#xff0c;比如RNN&#xff0c;由于其本身的训练机制导致其并行度不高&#xff0c;特别是遇到一些长句子的情况下。其次&…

面试题手撕篇

参考博客 开始之前&#xff0c;理解递归 手写 浅拷贝 function shallow(target){if(target instanceof Array){return [...resObj]}else{return Object.assign({},target);} }手写深拷贝 const _sampleDeepClone target > {// 补全代码return JSON.parse(JSON.stringify…

深度学习神经网络训练环境配置以及演示

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 目录 1 NVIDIA Dr…

Flink源码解析(1)TM启动

首先在看之前,回顾一下akka模型: Flink通讯模型—Akka与Actor模型-CSDN博客 注:ActorRef就是actor的引用,封装好了actor 下面是jm和tm在通讯上的概念图: RpcGateway 用于定义RPC协议,是客户端和服务端沟通的桥梁。服务端实现了RPC协议,即实现了接口中定义的方法,做具…

云原生(二)、Docker基础

Docker Docker 是一种开源的容器化平台&#xff0c;用于开发、部署和运行应用程序。它允许开发者将应用程序及其所有依赖项打包到一个可移植的容器中&#xff0c;这个容器可以在任何支持 Docker 的环境中运行&#xff0c;无论是开发人员的个人笔记本电脑、测试环境、生产服务器…