【算法刷题 | 回溯思想 01】4.11(回溯算法理论、组合、组合总和 ||| )

在这里插入图片描述

文章目录

  • 回溯
  • 1.回溯算法理论基础
    • 1.1什么是回溯法?
    • 1.2回溯法的效率
    • 1.3回溯法解决的问题
    • 1.4如何理解回溯法?
    • 1.5回溯法模板
  • 2.组合
    • 2.1问题
    • 2.2解法一:暴力解法(循环次数不确定)
    • 2.3解法二:回溯
      • 2.3.1回溯思路
        • (1)回溯函数模板返回值以及参数
        • (2)回溯函数终止条件
        • (3)回溯搜索的遍历过程
      • 2.3.2代码实现
      • 2.3.3剪枝操作
  • 3.组合总和 |||
    • 3.1问题
    • 3.2解法:回溯
      • 3.2.1回溯思路
        • (1)函数返回值以及参数
        • (2)终止条件
        • (3)遍历过程
      • 3.2.2代码实现
      • 3.2.3剪枝操作

回溯

1.回溯算法理论基础

1.1什么是回溯法?

  1. 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
  2. 回溯是递归的副产品,只要有递归就会有回溯。

1.2回溯法的效率

  1. 虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法
  2. 因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

1.3回溯法解决的问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

补充:组合是不强调元素顺序的,排列是强调元素顺序

例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

1.4如何理解回溯法?

  1. 回溯法解决的问题都可以抽象为树形结构
  2. 因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度
  3. 递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

1.5回溯法模板

  • 回溯三部曲:

    • 回溯函数模板返回值以及参数(回溯算法中函数返回值一般为void)

    • 回溯函数终止条件:(搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。)

      if (终止条件) {
          存放结果;
          return;
      }
      
    • 回溯搜索的遍历过程(回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度)

      for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
          处理节点;
          backtracking(路径,选择列表); // 递归
          回溯,撤销处理结果
      }
      
      • for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。
      • 大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
  • 回溯算法模板框架:

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

2.组合

2.1问题

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

  • 示例一:
输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
  • 示例二:
输入:n = 1, k = 1
输出:[[1]]

2.2解法一:暴力解法(循环次数不确定)

  • 使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。

    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            cout << i << " " << j << endl;
        }
    }
    
  • 如果n为100,k为50呢,那就50层for循环,循环写不出来

2.3解法二:回溯

2.3.1回溯思路

  • 分解成一层一层的树形结构:

77.组合

  • 使用res存放全部合适的结果;
  • 使用paths存放当前的路径;
(1)回溯函数模板返回值以及参数
  • 回溯函数返回值一般为void;
  • 参数:
    • 题目要求 求范围[1,n]中所有可能的k个数的组合;
    • 参数有 n、k
    • 还要有一个startIndex,代表每次循环从数组第几个数开始遍历
private void backtracking(int n,int k,int startIndex)
(2)回溯函数终止条件
  • 当当前路径 path 的个数达到k时,添加到res中
if(paths.size()==k){
    res.add(paths);
	return;	
}
(3)回溯搜索的遍历过程
  • 从startIndex遍历该层的元素
for(int i=startIndex;i<=n;i++){
	paths.add(i);
    //递归
    backtracking(n,k,i+1);
    //回溯
    paths.remove(paths.size()-1);
}

2.3.2代码实现

	List<List<Integer>> res=new ArrayList<>();
    List<Integer> paths=new ArrayList<>();
    
    public List<List<Integer>> combine(int n, int k) {
        
        backtracking(n,k,1);
        return res;
    }

    private void backtracking(int n,int k,int startIndex){
        if(paths.size()==k){
            res.add(new ArrayList<>(paths));
	        return;	
        }

        for(int i=startIndex;i<=n;i++){
	        paths.add(i);
            //递归
            backtracking(n,k,i+1);
            //回溯
            paths.remove(paths.size()-1);
        }
    }

2.3.3剪枝操作

  • 回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的

  • 遍历代码如下:

    for(int i=startIndex;i<=n;i++){
        
    	paths.add(i);
        //递归
        backtracking(n,k,i+1);
        //回溯
        paths.remove(paths.size()-1);
    }
    
  • 这个遍历的范围是可以剪枝优化的,怎么优化呢?

    • 来举一个例子,n = 4,k = 4 的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。

      77.组合4

    • 如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

  • 优化过程如下:

    • 已经选择的元素个数:path.size();
    • 还需要的元素个数为: k - path.size();
    • 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
  • 为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

  • 举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2

  • 从2开始搜索都是合理的,可以是组合[2, 3, 4]。

for(int i=startIndex;i<= n - (k - paths.size()) + 1;i++){
	paths.add(i);
    //递归
    backtracking(n,k,i+1);
    //回溯
    paths.remove(paths.size()-1);
}

3.组合总和 |||

3.1问题

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

  • 示例一:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

3.2解法:回溯

3.2.1回溯思路

(1)函数返回值以及参数
  • 无返回值
  • 参数:
    • k:总共需要k个数
    • n:总和为n
    • startIndex:从startIndex开始遍历
    • count:当前路径总和
private void backtracking(int k,int n,int startIndex,int count)
(2)终止条件
  • 判断路径个数是否为k,路径总和是否为n,满足则添加到res中并返回
if( paths.size()==k ){
	if(count==n){
    	res.add(new ArrayList<>(paths));   
    }
	return;	
}
(3)遍历过程
  • 从startIndex开始递归(paths加上该节点、count总和加上)
  • 递归后回溯(paths移除最后一个节点、count总和减去该节点值)
for(int i=startIndex;i<=9;i++){
    
	paths.add(i);
    //递归
    backtracking(k,n,i+1,count+=i);
    
    //回溯
    paths.remove(paths.size()-1);
    count-=i;
}

3.2.2代码实现

	List<List<Integer>> res=new ArrayList<>();
    List<Integer> paths=new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracking(k,n,1,0);
        return res;
    }

    private void backtracking(int k,int n,int startIndex,int count){
        if( paths.size()==k ){
            if(count==n){
                res.add(new ArrayList<>(paths));   
            }
	        return;	
        }

        for(int i=startIndex;i<=9;i++){
	        paths.add(i);
            //递归
            backtracking(k,n,i+1,count+=i);
            //回溯
            paths.remove(paths.size()-1);
            count-=i;
        }
    }

3.2.3剪枝操作

		for(int i=startIndex;i<= 9-(k-paths.size())+1 ;i++){
	        paths.add(i);
            //递归
            backtracking(k,n,i+1,count+=i);
            //回溯
            paths.remove(paths.size()-1);
            count-=i;
        }

在这里插入图片描述

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

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

相关文章

如何申请做快团团购帮卖团长?免费试用教程一学就会!

新手小白想要自己做快团团&#xff0c;有两种方法。 1、自己提供货源&#xff0c;自己开团做团长。 这种方法流程非常简单&#xff0c;从微信小程序中找到快团团&#xff0c;点击右上角“一键开团”&#xff0c;然后“创建普通团购”&#xff0c;按照操作设置就可以开团了。 …

【2024MathorCup妈妈杯建模】赛题 B:甲骨文智能识别中原始拓片单字自动分割与识别研究

【2024MathorCup妈妈杯建模】赛题 B&#xff1a;甲骨文智能识别中原始拓片单字自动分割与识别研究 加群可以享受定制等更多服务&#xff0c;或者搜索B站&#xff1a;数模洛凌寺 联络组织企鹅&#xff1a;904117571 问题重述 问题 1&#xff1a;对于附件 1&#xff08;Pre_…

实习记录小程序|基于SSM的实习记录小程序设计与实现(源码+数据库+文档)

知识管理 目录 基于SSM的习记录小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、小程序端&#xff1a; 2、后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕…

Linux:软件包管理器 - yum

Linux&#xff1a;软件包管理器 - yum Linux的软件安装方式源代码安装rpm包安装yum安装 yum三板斧yum listyum installyum remove yum生态yum源 Linux的软件安装方式 源代码安装 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序 源代码安…

ARM内核、内核寄存器及作用、ARM最小系统、半导体器件、存储器、ARM工作方式、ARM寄存器个数、立即数、汇编指令的s/c后缀

我要成为嵌入式高手之4月10日51单片机第五天&#xff01;&#xff01; ———————————————————————————— 单片机最小系统板&#xff1a; 单片机内部的CPU&#xff1a; CPU定义的变量都在ram中、编写的程序代码都在rom中 CPU在次细化&#xff1a;ker…

AMD Tensile 简介与示例

按照知其然&#xff0c;再知其所以然的认知次序进行 1&#xff0c;下载代码 git clone --recursive https://github.com/ROCm/Tensile.git 2&#xff0c;安装 Tensile cd Tensile mkdir build cd build ../Tensile/bin/Tensile ../Tensile/Configs/rocblas_dgemm_nn_asm_full…

MongoDB快照(LVM)业务场景应用实战

MongoDB和LVM快照概述 MongoDB的重要性&#xff1a;MongoDB支持的灵活的文档模型&#xff0c;使其成为处理大量分散数据的理想选择&#xff0c;特别是在需要快速迭代和频繁更改数据结构的应用中。 LVM&#xff08;逻辑卷管理&#xff09;快照技术基本概念&#xff1a;LVM允许…

CY7.5-COOH荧光属性Cyanine7.5花菁染料

CY7.5-COOH&#xff0c;作为一种Cyanine 7.5花菁染料&#xff0c;在荧光属性上表现出色。其独特的化学结构赋予了它荧光性质&#xff0c;使其在生物标记、荧光成像和光学传感等领域具有诸多应用前景。 CY7.5-COOH具有出色的荧光量子产率&#xff0c;这意味着在激发光的照射下&…

C#/.NET/.NET Core拾遗补漏合集(24年4月更新)

前言 在这个快速发展的技术世界中&#xff0c;时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节&#xff0c;以帮助大家更全面地了解这些技术栈的特性和发展方向。 GitHub开源地…

微信小程序搜索排名优化的八个套路

微信小程序的搜索优化排名是一个综合性的工作&#xff0c;需要针对多个维度进行操作。小柚给各位新老客户列出一些关键步骤和策略&#xff0c;帮助老板利用微信小程序的搜索排名规则进行优化。 1.选择合适的关键词 深入研究目标用户的搜索习惯&#xff0c;选择高搜索量、低竞争…

Java List基础篇

目录 前言一、常用List1.1 List1.1.1 特点1.1.2 常用API 1.2 ArrayList1.2.1 特点1.2.2 使用 1.3 LinkedList1.3.1 特点1.3.2 使用 1.4 CopyOnWriteArrayList1.4.1 特点1.4.2 使用 1.5 Arrays.asList()1.5.1 特点1.5.2 使用 二、对比总结 前言 一、常用List 1.1 List List是…

Day93:云上攻防-云服务篇对象存储Bucket桶任意上传域名接管AccessKey泄漏

目录 云服务-对象存储-权限配置不当 权限控制-公共读或公共读写&#xff1a;可完整访问但不显示完整结构目录 权限控制-Bucket授权策略&#xff1a;设置ListObject显示完整结构目录 权限控制-Bucket公共读写权限&#xff1a;公共读写直接PUT文件任意上传 云服务-对象存储-…

手写ArrrayList

需求 自定义的MyArrayList import java.util.Arrays; import java.util.Objects;public class MyArrayList<E> {private Object[] elementData ; // 存储元素的数组private int size; // 记录 的元素个数private static final int DEFAULT_CAPACITY 10; // 默认容量// …

Java高频面试之JVM篇

说一下 Jvm 的主要组成部分&#xff1f;及其作用&#xff1f; 类加载器执行器运行时数据区域本地接口 谈谈对运行时数据区的理解&#xff1f; 堆和栈的区别是什么&#xff1f; 堆中存什么&#xff1f;栈中存什么&#xff1f; 堆总存对象,栈中存局部变量,引用 为什么要把堆…

最新php投票系统源码下载 二维码投票生成器系统 源码开源可二开

在当今数字化时代&#xff0c;投票系统已成为各类活动、选举、调查等场景中不可或缺的工具。为了满足广大用户对于高效、便捷投票系统的需求&#xff0c;分享一款最新版的PHP投票系统源码&#xff0c;带二维码投票生成器功能&#xff0c;为用户提供了更为灵活和多样化的投票体验…

说说你对栈、队列的理解?应用场景?

一、栈 栈&#xff08;stack&#xff09;又名堆栈&#xff0c;它是一种运算受限的线性表&#xff0c;限定仅在表尾进行插入和删除操作的线性表 表尾这一端被称为栈顶&#xff0c;相反地另一端被称为栈底&#xff0c;向栈顶插入元素被称为进栈、入栈、压栈&#xff0c;从栈顶删…

【Python实践应用】使用Python加载栅格数据

下面的代码实现的是加载伊宁市NDVI数据&#xff0c;首先进行相关的python包的导入&#xff0c;然后定义和读取我们需要加载的数据&#xff0c;这里我们使用的NDVI数据是将伊宁23年的NDVI数据合并成为了一张栅格图像&#xff0c;每个波段表示一年的 NDVI&#xff0c;我们这里显示…

备战蓝桥杯---刷杂题2

显然我们直接看前一半&#xff0c;然后我们按照斜行看&#xff0c;我们发现斜行是递增的&#xff0c;而同一行从左向右也是递增的&#xff0c;因此我们可以直接二分&#xff0c;同时我们发现对称轴的数为Ck,2k. 我们从16斜行枚举即可 #include<bits/stdc.h> using name…

探索AI工具导航网站

在现代科技发展迅猛的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为了各行各业中不可或缺的一部分。了解和利用最新的AI工具对于工作、学习和娱乐都具有重大意义。在这篇博客中&#xff0c;我们将探索一些最新的人工智能工具导航网站&#xff0c;以及其中一款名…

liunx系统发布.net core项目

liunx系统发布.net core项目 准备.net6程序运行环境部署nginx&#xff0c;通过一个地址既能访问web api&#xff0c;又能访问web项目有一个客户把web api放到docker中&#xff0c;想通过nginx转发&#xff0c;nginx也支持配置多个程序api接口的其它 liunx系统&#xff1a;cento…