java算法day21

java算法day21

  • 77 组合
  • 216 组合总和Ⅲ
  • 17 电话号码的数字组合
  • 39 组合总和

这个阶段所要解决的问题都是有关回溯算法。
所以说解法基本上都是围绕回溯算法模板来完成。回溯算法可以这样总结,树形结构,横向遍历,纵向递归。递归出口收货结果并终止。本层递归处理完之后,对path恢复现场。

77 组合

上来将问题抽象为属性结构。就可以看到回溯的思路。
未优化思路:
在这里插入图片描述
按照这样的思路:
每一层所要做的就是遍历,取结果加入path路径之后就递归下层,收集下一个元素。可以这么理解,每层收集一个元素,所以说一定是要递归下去,加入元素是在不同的递归层,当path符合之后才进行收割结果。

class Solution {//全局变量,用于存结果List<List<Integer>> result= new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();//主方法public List<List<Integer>> combine(int n, int k) {backtracking(n,k,1);return result;}//回溯算法public void backtracking(int n,int k,int startIndex){//递归出口,path符合条件,收割结果,返回上一层if (path.size() == k){result.add(new ArrayList<>(path));return;}//这里是本层的逻辑,对于本层来说,横向是做遍历,目的是开不同的分支//这里是从startIndex开始散开每个分支。//从模板而言,每一层都在做横向遍历,开枝散叶。for (int i =startIndex;i<=n;i++){//收集本层节点path.add(i);//递归下一层backtracking(n,k,i+1);//回来之后把上一层收集的元素,做弹出,就完成了恢复现场。path.removeLast();}}
}

优化逻辑:
也就是可以从已收集的元素个数,来判断出,如果索引走到某个位置之后,后面就不可能收集到结果。所以后面的路可以说是白走。所以这里就是把白走的路给剪掉。
在这里插入图片描述

举个例子
n = 4 k = 3
那么这里就很清楚,第一层,最多索引走到i = 2,这样能取到2,3,4这样的组合。后面的3,4就没必要去走。
根据这样的规律可以进行这个边界的计算:
比如从一开始,path还没收集元素
path.size():已经收集到的元素个数。现在为0
k-path.size()还缺少的元素个数 现在为3
现在看最多走到哪。
n-(k-path.size()) = 1。这显然不是最边界,还要+1。
所以目前i的极限取值就是i=n-(k-path.size())+1。对于本层i而言,最多取到这个位置,一旦超了,后面的路可以说是白走。

因此这个剪枝的特点是通过循环条件来完成的。

可以看到就改了循环条件

class Solution {List<List<Integer>> result = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public List<List<Integer>> combine(int n, int k) {combineHelper(n, k, 1);return result;}/*** 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex* @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。*/private void combineHelper(int n, int k, int startIndex){//终止条件if (path.size() == k){result.add(new ArrayList<>(path));return;}for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){path.add(i);combineHelper(n, k, i + 1);path.removeLast();}}
}

216 组合总和Ⅲ

这题和组合没太大差别,可以理解为就在以往收集结果的时候多加了一个判断条件,判断path中的元素值总和是否等于target。等于了才收集。

所以说由于是组合问题,所以起手还是抽象树形结构。横向遍历,纵向递归。
在这里插入图片描述
可以看到,就是在收集结果的时候多加了一个条件判断。

本题还有一些要注意的点,也就是把n锁死了,最多只到9,所以说写循环条件剪枝的时候n直接写9。
所以剪枝依然是同样的逻辑。而且还多了一个剪枝条件,一旦还没走到底,sum已经大于targetSum了,那后面没必要走,也返回。
在这里插入图片描述


接下来看代码:
剪枝版本:

class Solution {
//全局变量,用于存结果。List<List<Integer>> result = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();
//主函数public List<List<Integer>> combinationSum3(int k, int n) {backTracking(n, k, 1, 0);return result;}
//回溯private void backTracking(int targetSum, int k, int startIndex, int sum) {// 减枝1//值大于targetSum,后面没必要走,直接返回,不用担心负数,因为题目给的1-9if (sum > targetSum) {return;}//剪枝2。path满足大小,收割结果,然后返回,这里已经到叶子节点了。if (path.size() == k) {if (sum == targetSum) result.add(new ArrayList<>(path));return;}// 减枝 9 - (k - path.size()) + 1//还是先前的剪枝规则//横向遍历。for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {//收集path元素path.add(i);//累加节点值。sum += i;//递归下一层,i不能取当前,i要+1backTracking(targetSum, k, i + 1, sum);//回溯path.removeLast();//回溯,回溯的时候不仅把下层已收集的元素弹出,sum也要减去。sum -= i;}}
}

17 电话号码的数字组合

在这里插入图片描述
从图中应该可以感受出来,单个数字代表的元素,代表了横向遍历的长度。
输入的字符串长度,代表了纵向递归的深度。

class Solution {//设置全局列表存储最后的结果List<String> list = new ArrayList<>();//主函数,做特判。public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) {return list;}//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""//题目要求的映射,直接通过下标进行关联。String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};//开始迭代处理backTracking(digits, numString, 0);//返回结果return list;}//每次迭代获取一个字符串,所以会涉及大量的字符串拼接,所以这里选择更为高效的 StringBuilder//又是一个全局变量。StringBuilder temp = new StringBuilder();//比如digits如果为"23",num 为0,则str表示2对应的 abcpublic void backTracking(String digits, String[] numString, int num) {//遍历全部一次记录一次得到的字符串//收割结果做剪枝,一旦收集到的元素等于输入的字符串的长度,就收集返回。if (num == digits.length()) {list.add(temp.toString());return;}//str 表示当前num对应的字符串,获取该数字映射的字符串。String str = numString[digits.charAt(num) - '0'];//取得了字符串,那么开始做横向遍历。对于1对应的abc而言,开始取a,取b,取c。从题目的意思,再根据这个树,就是这样的含义。for (int i = 0; i < str.length(); i++) {//取第一个元素加入temp中。temp.append(str.charAt(i));//递归,处理下一层,注意前面我强调的,进入下一层就是到另一个数字代表的字符串了。所以要num+1。标识处理digits的下一个元素。backTracking(digits, numString, num + 1);//剔除末尾的继续尝试,//这里就是回溯,把下层的元素弹出,恢复现场。temp.deleteCharAt(temp.length() - 1);}}
}

39 组合总和

这题和前面又有点不一样了,他不限制path.size()要满足多少了。
最主要就是符合targetSum。因此之前用的剪枝就不能套用一模一样的,需要改。

由题意,还是把树模型给抽象出来,题目中还说了,数组中的元素各不相同。而且同一个元素可以取多次,但注意,虽然一个元素可以取多次,但这并不意味着和排列一样。比如2,3 target = 7。如果你按排列的思想去想,那么这个题2,2,3和3,2,2有两个答案,但是题目说了,这是一个答案,个数是不能重复的,因此就算元素可以重复取,但是要求结果集中,元素组合不能完全相同。所以这本质还是组合问题,因此为了防止上述情况出现,那么我的树结构依然是这样,取过的元素就会枚举出该元素的所有可能,后面就不能再取了。

在这个过程中也可以看到剪枝,就是sum>=targetSum,那么就可以提前return了。从这个图来看,仍然是组合的思想。
在这里插入图片描述
那么有没有什么方法可以优化?回答是有的。

那么就是排序。因为这个题目你会发现,排序之后会发现这样的好处:
或许还是感觉不出排序有什么好处。这里来进行对比
比如在[2,5,3,1]
先前这种递归,都是在递归下一层之后,通过if条件来进行判断,是否提前剪枝,这是发生在下一层。发生在下一层有什么坏处?那就是实际你多递归一层,那么就会多一层递归调用栈。那么如果我们想,能不能在上一层解决这样的递归。提前在上一层检查了,就不进入下一层了。

所以这里提出了排序的方法。
排序之后我们直接在本层做判断

看这个例子:
现在如果排序了是1,2,3,5
上来就先做判断,要不要去下一层了,而且这里还有一个精髓if (sum + candidates[i] > target) break;这里可以一并砍掉横向遍历没必要的循环。因为是排序过了的,后面不可能的序列不可能再存在解。

也许你会这样想,对于没排序过的,也做了这样的剪枝,会发生什么。
在2,5,3,1中。target=4。这样显然会在5之后会把后面的横向遍历3,1给砍掉。漏结果了。

for (int i = idx; i < candidates.length; i++) {// 如果 sum + candidates[i] > target 就终止遍历if (sum + candidates[i] > target) break;path.add(candidates[i]);backtracking(res, path, candidates, target, sum + candidates[i], i);path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素}

在这里插入图片描述

不排序+剪枝
也是减少递归调用栈。我个人推荐写这种。这种只是优化了函数调用栈。
由于不是排序,所以大的元素后面,也是有可能存在小的元素,因此存在解。所以顶多只能跳过这个大的。不去扩展他的下层。

class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();// Arrays.sort(candidates); // 先进行排序backtracking(res, new ArrayList<>(), candidates, target, 0, 0);return res;}public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {// 找到了数字和为 target 的组合if (sum == target) {res.add(new ArrayList<>(path));return;}//注意递归下一层index还是i,因为元素是可以重复取得。for (int i = idx; i < candidates.length; i++) {// 如果 sum + candidates[i] > target 就终止遍历//直接在本层就做了剪枝,不去判断下一层。if (sum + candidates[i] > target) continue;path.add(candidates[i]);backtracking(res, path, candidates, target, sum + candidates[i], i);path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素}}
}

排序+剪枝
排序之后可以帮我们少走很多路。
大于后的数字直接就砍掉了。

class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();Arrays.sort(candidates); // 先进行排序backtracking(res, new ArrayList<>(), candidates, target, 0, 0);return res;}public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {// 找到了数字和为 target 的组合if (sum == target) {res.add(new ArrayList<>(path));return;}for (int i = idx; i < candidates.length; i++) {// 如果 sum + candidates[i] > target 就终止遍历if (sum + candidates[i] > target) break;path.add(candidates[i]);backtracking(res, path, candidates, target, sum + candidates[i], i);path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素}}
}

我个人感觉我喜欢前一种方法,因为我并不喜欢在面试的场景下写排序。因为要么你手写一个排序,要么你就用Arrays.sort。我感觉不太好。所以面试优先第一种思想。

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

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

相关文章

(十九)原生js案例之h5地里位置信息与高德地图的初使用

h5 地里位置信息 1. 获取当前位置信息 window.onload function () {const oBtn document.querySelector("#btn");const oBox document.querySelector("#box");oBtn.onclick function () {window.navigator.geolocation.getCurrentPosition(function (…

c++----模版进阶

c----模版初阶&#xff1a;http://t.csdnimg.cn/PiYoD 一.非类型模版参数 模板参数除了可以是类型&#xff0c;还可以是常量。 例如&#xff1a; 这样就可以在类中使用这个n常量。 -------------------------------------------------------------------------------------…

鸿蒙OS物联网创新应用实训解决方案

摘要&#xff1a; 随着物联网技术的飞速发展&#xff0c;各种智能设备和传感器正在以前所未有的速度融入我们的日常生活。华为推出的鸿蒙操作系统&#xff08;HarmonyOS&#xff09;作为一款面向全场景、多设备、无缝连接的分布式操作系统&#xff0c;为物联网领域带来了全新的…

机器学习 | 回归算法原理——最小二乘法

Hi&#xff0c;大家好&#xff0c;我是半亩花海。很早便想学习并总结一本很喜欢的机器学习图书——立石贤吾的《白话机器学习的数学》&#xff0c;可谓通俗易懂&#xff0c;清晰形象。那就在此分享并作为学习笔记来记录我的学习过程吧&#xff01;本章的回归算法原理基于《基于…

【时序约束】读懂用好Timing_report

一、静态时序分析&#xff1a; 静态时序分析&#xff08;Static Timing Analysis&#xff09;简称 STA&#xff0c;采用穷尽的分析方法来提取出整个电路存在的所有时序路径&#xff0c;计算信号在这些路径上的传播延时&#xff0c;检查信号的建立和保持时间是否满足时序要求&a…

centos系统mysql主从复制(一主一从)

文章目录 mysql80主从复制&#xff08;一主一从&#xff09;一、环境二、服务器master1操作1.开启二进制日志2. 创建复制用户3. 服务器 slave1操作4. 在主数据库中添加数据 mysql80主从复制&#xff08;一主一从&#xff09; 一、环境 准备两台服务器&#xff0c;都进行以下操…

linux系统安装python3和pip

一、安装python 1、安装依赖环境 yum install gcc -y yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum install zlib zlib-devel openssl -y yum install openssl…

Qt源码交叉编译带openssl的Qt版本

一.背景 近期项目由于对接的后台服务是https的&#xff0c;之前交叉编译的Qt是不带openssl的&#xff0c;为了能支持https&#xff0c;必须要重新编译Qt。 二.环境 环境准备&#xff1a; Ubuntu版本 &#xff1a;18.04&#xff1b; openssl 版本&#xff1a;1.1.1.g&#xff1b…

vscode 搭建 golang 开发环境

介绍 在 vscode 搭建 go 的开发环境需要区分两个方向&#xff1a; go 1.19.0 及其更高版本go 1.19.0 之前的版本 为什么这么分&#xff0c;因为 vscode-go 插件自带的工具安装脚本全部都是装最新版的各类工具&#xff0c;这些工具中有部分要求 go 1.19.0 以上才能安装成功。…

手写RPC-令牌桶限流算法实现,以及常见限流算法

为什么需要服务限流、降级 分布式架构下&#xff0c;不同服务之间频繁调用&#xff0c;对于某个具体的服务而言&#xff0c;可能会面临高并发场景。在这样的情况下&#xff0c;提供服务的每个服务节点就都可能由于访问量过大而引起一系列问题&#xff0c;比如业务处理耗时过长、…

SpringBoot把nacos配置注入时数据注入时出现莫名错误

一、错误详情 我在nacos的配置a是003457 但是注入的数据是1839 二、解决方法 通过加号可以解决这个问题: 数据正确了&#xff1a;

【BUG】已解决:SyntaxError: invalid syntax

SyntaxError: invalid syntax 目录 SyntaxError: invalid syntax 【常见模块错误】 【解决方案】 常见原因及解决方法 解决步骤 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职…

Ubuntu22.04离线安装nginx

下载安装包 nginx nginx下载地址&#xff0c;选stable的即可&#xff0c;传到服务器上面&#xff0c;记住上传路径 提示: 下面的openssl&#xff0c;zlib,pcre也可以不下载也可以&#xff0c;我这里是考虑到完全离线下载的情况openssl 这个是https需要弄得&#xff0c;如果生产…

【JavaScript】箭头函数

具体讲解 之前写 this 的指向时就提到过箭头函数&#xff0c;但是由于其比较复杂&#xff0c;还是单独开一篇来讲箭头函数。 箭头函数&#xff0c;箭头函数不能作为构造函数&#xff0c;没有原型 prototype&#xff0c;不能 new。 在箭头函数中&#xff0c;this 关键字指向的是…

MMROTATE的混淆矩阵confusion matrix生成

mmdetection中加入了混淆矩阵生成并可视化的功能&#xff0c;具体的代码在tools/analysis_tools/confusion_matrix.py。 mmrotate由于主流遥感数据集中的DOTA数据集标注格式问题&#xff0c;做了一些修改&#xff0c;所以我们如果是做遥感图像检测的Dota数据集的混淆矩阵&…

C:图案打印

引言 本篇文章讲了一些常见的图形编程题&#xff0c;并总结了一些规律。 1、打印空心正方形 1.1 代码展示&#xff1a; #include<stdio.h> int main() {int a 0;//边长初始化scanf("%d", &a);//输入边长的值{int i 0;for (i 0; i < a; i)//控制行…

数据结构C++——优先队列

文章目录 一、定义二、ADT三、优先队列的描述3.1 线性表3.2 堆3.2.1 最大堆的ADT3.2.2 最大堆的插入3.2.3 最大堆的删除3.2.4 最大堆的初始化3.3 左高树 LT3.3.1 高度优先左高树HBLT3.3.2 重量优先左高树WBLT3.3.3 最大HBLT的插入3.3.4 最大HBLT的删除3.3.5 合并两棵最大HBLT3.…

京东商品详情API返回值:商品ID与标题解析

京东商品详情API是京东电商平台提供的一个接口&#xff0c;用于获取商品的详细信息&#xff0c;包括商品ID、商品标题、价格、库存等。然而&#xff0c;需要注意的是&#xff0c;直接访问和使用京东的商品详情API通常需要符合京东的开放平台规则&#xff0c;并可能需要注册成为…

OpenCV 卷积操作 均值,高斯,中值滤波 图片降噪

文章目录 卷积概念卷积的作用1. 图像平滑与去噪2. 边缘检测3. 特征提取4. 图像增强 常见的三种滤波均值滤波均值滤波的步骤优点和缺点使用示例 高斯滤波示例代码 中值滤波中值滤波的基本原理数学表达式中值滤波的步骤示例优点和缺点使用示例 三种滤波 图片降噪 Python实现 卷积…

redis高可用之主从复制、哨兵以及Cluster集群

目录 一、Redis主从复制 1&#xff09;主从复制的作用 2&#xff09;主从复制流程 3&#xff09;搭建Redis主从复制 1、部署redis服务器 2、修改Redis配置文件&#xff08;所有节点操作&#xff09; 3、验证主从复制结果 二、哨兵模式 1&#xff09;哨兵的作用 2&…