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

文章目录

  • 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 + 1backtracking(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 + 1backtracking(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.hqwc.cn/news/541451.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

helm部署hadoop

&#xff08;作者&#xff1a;陈玓玏&#xff09; 参考helm仓库的文档&#xff1a;https://artifacthub.io/packages/helm/apache-hadoop-helm/hadoop helm helm repo add pfisterer-hadoop https://pfisterer.github.io/apache-hadoop-helm/ helm install hadoop pfistere…

LeetCode230题:二叉搜索树中第K小的元素(python3)

代码思路&#xff1a;二叉搜索树中序遍历为递增序列。 class Solution:def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:def dfs(root):if not root:returndfs(root.left)if self.k 0:returnself.k - 1if self.k 0:self.res root.valdfs(root.right)se…

Gitee 实战配置

一、Gitee 注册帐号 官网:https://gitee.com点击注册按钮。填写姓名。填写手机号。填写密码。点击立即注册按钮二、安装GIT获取公钥 1.官网下载git下载地址:https://git-scm.com/download/win 2.安装git,双击运行程序,然后一直下一步,直至完成。 3.安装完成后,在 CMD 命令…

蓝桥杯 - 大石头的搬运工 C++ 前缀和 算法 附Java python

题目 思路和解题方法 这段代码的目标是计算给定点集的最小总移动成本&#xff0c;使得所有点都在同一直线上。它通过计算每个点左边和右边的移动成本&#xff0c;然后在所有可能的分割点中选择最小成本。具体步骤如下&#xff1a; 读取输入的点集&#xff0c;每个点表示为 (y, …

[AIGC] Spring Boot中的切面编程和实例演示

切面编程&#xff08;Aspect Oriented Programming&#xff0c;AOP&#xff09;是Spring框架的关键功能之一。通过AOP&#xff0c;我们可以将代码下沉到多个模块中&#xff0c;有助于解决业务逻辑和非业务逻辑耦合的问题。本文将详细介绍Spring Boot中的切面编程&#xff0c;并…

【常规】使用bat批量修改文件名

文章目录 一、目标二、主要思路三、步骤&#xff08;一&#xff09;进入目标文件夹&#xff08;二&#xff09;获取所有目标文件名称1、创建文本文档&#xff08;txt格式&#xff09;2、写代码3、生成bat文件4、执行bat文件&#xff0c;获取原文件名列表&#xff1a; &#xff…

redis如何保证缓存一致性

方式一&#xff1a;先更新数据库&#xff0c;再更新缓存场景 当有两个线程A、B&#xff0c;同时对一条数据进行操作&#xff0c;一开始数据库和redis的数据都为tony&#xff0c;当线程A去修改数据库&#xff0c;将tong改为allen&#xff0c;然后线程A在修改缓存中的数据&#x…

实验一:关联规则 (见U盘)

实验名称 关联规则 实验时间 3月 14 日星期 四 第3.4节 实验目的 利用 Python 对关联规则算法进行调用。能够使用 Python 调用关联规则算法。首先使用apriori ,fpgrowth 或者 fpmax 函数来找出频繁项集&#xff0c;然后使用 association_rules …

基础算法-分治算法-学习

现象&#xff1a; 基础算法-分治算法-学习 分而治之&#xff0c;将复杂问题分成小问题&#xff0c;小问题直接求解&#xff0c;最后合并得到最终结果&#xff0c; 和递归思想有点相近&#xff0c;也是区分小问题自己解决&#xff0c;所以在分治算法很多以递归的方式实现 每个…

【Leetcode每日一题】 递归 - 反转链表(难度⭐)(35)

1. 题目解析 题目链接&#xff1a;206. 反转链表 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、递归函数的核心任务 递归函数的主要职责是接受一个链表的头指针&#xff0c;并返回该链表逆序后的新头结点。递归…

【研发日记】Matlab/Simulink技能解锁(五)——Simulink布线技巧

前言 见《【研发日记】Matlab/Simulink技能解锁(一)——在Simulink编辑窗口Debug》 见《【研发日记】Matlab/Simulink技能解锁(二)——在Function编辑窗口Debug》 见《【研发日记】Matlab/Simulink技能解锁(三)——在Stateflow编辑窗口Debug》 见《【研发日记】Matlab/Simulink…

可变形卷积v4 |更快更强,效果远超DCNv3

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 一、论文摘要 我们介绍了可变形卷积v4 (DCNv4)&#xff0c;这是一种高效的算子&#xff0c;专为广泛的视觉应用而设计。DCNv4通过两个关键增强解决了…