算法通过村第十八关-回溯|白银笔记|经典问题

文章目录

  • 前言
  • 组合总和问题
  • 分割回文串
  • 子集问题
  • 排序问题
  • 字母大小写全排列
  • 单词搜索
  • 总结


前言


提示:我不愿再给你写信了。因为我终于感到,我们的全部通信知识一个大大的幻影,我们每个人知识再给自己写信。 --安德烈·纪德

回溯主要解决一些暴力枚举也搞不定的问题,例如组合、分割、子集、排列、棋盘等。这关我们就看看如何接入。

组合总和问题

参考题目地址:39. 组合总和 - 力扣(LeetCode)

在这里插入图片描述
如过不考虑重复的,这个题目和113的那个题目相差无疑,如果可以重复,哪呢是否可以无限制取下去呢?也不会,因为题目也给了说明。每个元素最小为1,因此最多一个target个1.我们画图该怎么做呢,对于序列{2,3,6,7}.target = 7.很明显我们选择1个2,还剩下target = 7 - 2 = 5.然后再选取一个2,还剩下target = target - 2 = 3;之后再选一个2,变成了target = target - 2 = 1,此时最小的为2,不能再选了,就要退出。看看有没有3。ok,有那么第一个结果就是{2,2,3}.

之后我们继续回退到只选1个2的时候,这是2不能选了,从{3,6,7}中选择,如下图所示,没有符合规定的。

依次类推,然后尝试从3和6、7中开始选择。

最终的结果就是{2,2,3}和{2,5}.为了方便,我们可以先对元素做个排序,然后按照上面的过程执行,形成一个树形图:

在这里插入图片描述
这个图横向是针对每个元素做暴力枚举,纵向是递归(向下找)也是纵横问题,实现起来代码也不复杂:

public List<List<Integer>> combinationSum(int[] c, int target) {
      List<List<Integer>> res = new ArrayList();
      List<Integer> path = new ArrayList();
      dfs(c,0,target,path,res);
      return res;
}

public void dfs(int[] c,int u,int target,List<Integer> path,List<List<Integer>> res){
        if(target < 0){
            return;
        }
        if(target == 0){
            res.add(new ArrayList(path));
            return;
        }
        for(int i = u; i < c.length; i++){
            if(c[i] <= target){
                path.add(c[i]);
                dfs(c,i,target - c[i],path,res);
                path.remove(path.size() - 1);
            }   
        }
}

分割回文串

参考题目地址:131. 分割回文串 - 力扣(LeetCode)

在这里插入图片描述
字符串如何判断回文本身就是一道算法题,本题在其之上还要解决一个问题:如何分割?如果暴力切割很麻烦,解决起来也很困难,如果从回溯的角度去思考就很清晰:
在这里插入图片描述
我们说回溯本身仍然回进行枚举的,这里也是一样的。切割线(途中的|)切割到字符串的结尾位置,说明找到了一个切割方法。这里就是先试一试,第一次切割’a’,第二次切割’aa‘,第三次切割’aab’。这对应的就是回溯里面的for循环(也就是横向遍历)

我们还要说回溯(需要进行递归操作)第一次切合’a’,剩下的就是‘ab’.递归就是在将其且下一个回文下来,也就是第二个‘a’,剩下的‘b’再交给递归进行一步切割。这就是纵向方面要干的事,依次类推。

至于回溯操作与前面的一样的道理,不再赘述。通过代码就可以发现,切割问题的回溯搜索的过程和嘴和问题的回溯搜索的过程是差不多的。

class Solution {
     List<List<String>> res = new ArrayList<>();
     Deque<String> path = new LinkedList<>();
    public List<List<String>> partition(String s) {
        backTracking(s,0);
        return res;
    }
        private void backTracking(String s, int startIndex) {
        // 如果起始位置大于s的大小,说明找到了一组分割方案
        if (startIndex >= s.length()) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            // 如果是回文子串,则记录
            if (isPalindrome(s, startIndex, i)) {
                String str = s.substring(startIndex, i + 1);
                path.addLast(str);
            } else {
                continue;
            } 
            // 起始位置后移,保证不重复
            backTracking(s, i + 1);
            path.removeLast();
        }
    }
    public boolean isPalindrome(String str,int start,int end){
        for(int i = start, j = end; i < j; i++,j--){
            if(str.charAt(i) != str.charAt(j)){
                return false;
            }
        }
        return true;
    }
}

子集问题

子集问题也是回溯的经典使用场景。回溯可以画成一种状态树,子集,组合,分割,都可以抽象出来。

但是子集也有它特有的类型:需要找出所有情况。

参考题目:78. 子集 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
画图详解:
在这里插入图片描述
从图中也可以看出,遍历这颗树的时候,就可以记录下所有的节点,也就是得到子集结果。

那么什么时候停下来呢?起始可以不加终止条件,因为startIdex >= nums.size(),for循环就结束了。

记住:求子集问题,不需要任何的剪枝操作!因为子集就是要遍历整颗树。这样实现起来也比较容易。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList();

    public List<List<Integer>> subsets(int[] nums) {
        // 空集也是
        if(nums.length == 0 ){
            res.add(new ArrayList<>());
            return res;
        }
        backTracking(nums,0);
        return res;
    } 
    public void  backTracking(int[] nums,int startIndex){
    	// 记住:加进来,再判断
        res.add(new ArrayList(path));
        if(startIndex >= nums.length){
            return;
        }
        for(int i = startIndex; i < nums.length; i++){
        	// 今典小连招
            path.add(nums[i]);
            backTracking(nums,i+ 1);
            path.remove(path.size() - 1);
        }
    }
}

tips:上面的代码可以通过全局定义res和path,这样简介。这里(也可以转成参数传递的形式)

排序问题

参考题目介绍:46. 全排列 - 力扣(LeetCode)

在这里插入图片描述

排列问题也是经典的问题(每次刷是不是都难)。这个问题和前面的组合等问题的一个区别就是后面的还用再选(可重复),例如:1:开始使用了,但是到了2和3的时候仍然要使用一次。本质上是因为【1,2】和【2,1】从集合的角度看一个问题,单是从排列的角度是两个问题。

元素1在【1,2】中已经使用多了,但是在【2,1】中还要再使用一次,所以就不能使用startIndex了,此时可以使用一个used数组来标记已经选择的元素,完整的过程如图所示:

在这里插入图片描述
开图就知道了,怎么确定终止条件呢?就是到达叶子节点,那么是么时候到达叶子节点呢?

当时收集元素的数组path的大小达到和nums数组一样不就行了,说明就找到了一个全排列,也就是到达叶子节点了。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    // 是否选
    boolean[] used ; 

    public List<List<Integer>> permute(int[] nums) {
        if(nums.length == 0){
            return res;
        }
        used = new boolean[nums.length];
        permuteHepler(nums);
        return res;
    }


    public  void permuteHepler(int[] nums){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return ;
        }
        for(int i = 0; i < nums.length; i++){
        	//  解决01 10 问题
            if(used[i]){
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            permuteHepler(nums);
            //  经典回溯
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }
}

字母大小写全排列

参考题目地址:784. 字母大小写全排列 - 力扣(LeetCode)

在这里插入图片描述

如果本题去掉数组,只告诉你两个字母ab,然你每个字母变化大小写,那么就是ab,Ab,aB,AB四种情况。就和上面的处理一样了。这里有数字的干扰,我们就需要作过滤数字,只处理字母。还有就是添加大小写的转换问题了。
在这里插入图片描述

由于每个字符的大小形式刚好相差32,因此再大小写里面可以换成用c^32来进行转换和恢复。这样代码就简洁多了:

class Solution {
    List<String> res = new ArrayList<>();

    public List<String> letterCasePermutation(String s) {
        dfs(s.toCharArray(),0);
        return res;
    }

    public void dfs(char[] arr, int pos){
        // 过滤数字
        while(pos < arr.length && Character.isDigit(arr[pos])){
            pos++;
        }
		// 终止条件
        if(pos == arr.length){
            res.add(new String(arr));
            return;
        }
		// 转换
        arr[pos]^=32;
        dfs(arr,pos + 1);
        // 撤销
        arr[pos]^=32;
        dfs(arr,pos + 1);

    }
}

单词搜索

参考题目介绍:79. 单词搜索 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述

思路:从上到下,左到右遍历网格,每个坐标递归调用check(i,j,k)函数,i,j表示网格坐标,k表示word的第k个字符,如果能搜索到第k个字符返回true,否则返回false。

check函数的终止条件也很明确:

  1. 如果i,j的位置和字符上的字符位置k不相符,也就是说这个方面的路径有问题,直接返回false
  2. 如果搜索到了字符串的结尾,则找到了网格中的一条路径,这条路径上的字符正好可以组成字符串s,返回true。

两种情况都不满足,就把当前网格加入visited数组,visited表示该位置已经访问过了,然后顺着当前网格的坐标的四个方向继续尝试,如果没有找到k开始的子串,则返回回溯状态visited[i ] [j] = false,继续向后面访问。

分析一波复杂度:时间复杂度为O(ML*3^L),M,N 为网格的长度和宽度,L 为字符串word的长度,第一次电泳check函数的时候,回向4个方向进行测试,其余坐标的节点都是3个方向检车,走过的分支不会反方向回去,左移check函数的复杂度为3^L,而网格上面有MN个坐标,且存在剪枝,最坏的情况下是O(MN * 3 ^L).空间复杂符是O(MN) visited数组的空间是O(MN) ,check递归栈的最大深度在最坏的情况下是O(MN)

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        for(int i = 0; i < board.length; i++){
            for(int j  = 0; j < board[i].length;j++){
                if(dfs(board,words,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean dfs(char [][] board,char[] words,int i,int j, int k){
        // 边界+条件
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != words[k]){
            return false;
        }
        // 终止条件
        if(k ==  words.length - 1){
            return true;
        }
        // 防止重复
        board[i][j] = '\0';
        boolean res = dfs(board,words,i+1,j,k+1) || dfs(board,words,i - 1,j,k+1) || 							dfs(board,words,i,j - 1,k+1)  ||  dfs(board,words,i,j+ 1,k+1);
        // 恢复
        board[i][j] = words[k];
        return res;

    }
}

总结

提示:组合总和问题;分割问题;子集问题;排列问题;搜索问题


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。

在这里插入图片描述

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

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

相关文章

使用Go语言抓取酒店价格数据的技术实现

目录 一、引言 二、准备工作 三、抓取数据 四、数据处理与存储 五、数据分析与可视化 六、结论与展望 一、引言 随着互联网的快速发展&#xff0c;酒店预订已经成为人们出行的重要环节。在选择酒店时&#xff0c;价格是消费者考虑的重要因素之一。因此&#xff0c;抓取酒…

Pytorch tensor 数据类型快速转换三种方法

目录 1 通用,简单&#xff0c;CPU/GPU tensor 数据类型转换 2 tensor.type()方法 CPU tensor 数据类型转换 GPU tensor 数据类型转换 3 tensor.to() 方法,CPU/GPU tensor 数据类型转换 1 通用,简单&#xff0c; CPU/GPU tensor 数据类型转换 tensor.double()&#xff1a;…

使用Keras建立模型并训练等一系列操作方式

由于Keras是一种建立在已有深度学习框架上的二次框架&#xff0c;其使用起来非常方便&#xff0c;其后端实现有两种方法&#xff0c;theano和tensorflow。由于自己平时用tensorflow&#xff0c;所以选择后端用tensorflow的Keras&#xff0c;代码写起来更加方便。 1、建立模型 …

玩转Apipost-Helper:代码编辑器内调试、生成文档

Apipost-Helper是由Apipost推出的IDEA插件&#xff0c;写完接口可以进行快速调试&#xff0c;且支持搜索接口、根据method跳转接口&#xff0c;还支持生成标准的API文档&#xff0c;注意&#xff1a;这些操作都可以在代码编辑器内独立完成&#xff0c;非常好用&#xff01;这里…

SSM之spring注解式缓存redis

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.Redis与SSM的整合 1.1.添加Redis依赖 在Maven中添加Redis的依赖 <redis.version>2.9.0</redis.…

axios请求的问题

本来不想记录&#xff0c;但是实在没有办法&#xff0c;因为总是会出现post请求&#xff0c;后台接收不到数据的情况,还是记录一下如何的解决的比较好。 但是我使用export const addPsiPurOrder data > request.post(/psi/psiPurOrder/add, data); 下面是封装的代码。后台接…

kubernetes (k8s)的使用

一、kubernetes 简介 谷歌2014年开源的管理工具项目&#xff0c;简化微服务的开发和部署。 提供功能&#xff1a;自愈和自动伸缩、调度和发布、调用链监控、配置管理、Metrics监控、日志监控、弹性和容错、API管理、服务安全等。官网&#xff1a;https://kubernetes.io/zh-cn…

算法记录|笔试中遇到的题

栈 394. 字符串解码730.统计不同回文子序列 394. 字符串解码 我自己写的方法 class Solution {public String decodeString(String s) {char[] chs s.toCharArray();LinkedList<Character> stack new LinkedList<>();for(char ch:chs){if(ch]){stack helper(st…

微信管理系统:让企业更轻松地管理客户和员工资源

在日常工作中&#xff0c;我们经常遇到以下问题&#xff1a; ①由于微信号众多&#xff0c;需要频繁地在不同设备之间切换&#xff0c;这严重影响了工作效率。 ②尽管我一直努力回复客户的消息&#xff0c;但有时还是无法做到即时回复&#xff0c;这给客户带来了一些不便。 …

fpga时序相关概念与理解

一、基本概念理解 对于数字系统而言&#xff0c;建立时间&#xff08;setup time&#xff09;和保持时间&#xff08;hold time&#xff09;是数字电路时序的基础。数字电路系统的稳定性&#xff0c;基本取决于时序是否满足建立时间和保持时间。 建立时间Tsu&#xff1a;触发器…

基于BP神经网络+Adaboost的强分类器设计实现公司财务预警

大家好&#xff0c;我是带我去滑雪&#xff01; Adaboost算法的思想是合并多个弱分类器的输出以产生有效分类。其主要步骤是先利用弱学习算法进行迭代运算&#xff0c;每次运算都按照分类结果更新训练数据权重分布&#xff0c;对于分类失败的训练个体赋予较大的权重&#xff0c…

HCIA-单臂路由-VLAN-VLAN间通信-OSPF 小型实验

HCIA-单臂路由-VLAN-VLAN间通信-OSPF 实验拓扑配置步骤第一步 配置二层VLAN第二步 配置VLANIF和IP地址第三步 配置OSPF 配置验证PC1可以ping通PC2 PC3 PC4 实验拓扑 配置步骤 第一步 配置二层VLAN 第二步 配置VLANIF和IP地址 第三步 配置OSPF 第一步 配置二层VLAN SW1 sysna…

Blender vs 3ds Max:谁才是3D软件的未来

在不断发展的3D建模和动画领域&#xff0c;两大软件巨头Blender和3ds Max一直在争夺顶级地位。 随着技术的进步和用户需求的演变&#xff0c;一个重要问题逐渐浮出水面&#xff1a;Blender是否最终会取代3ds Max&#xff1f;本文将深入探讨二者各自的优势和劣势、当前状况&…

SpringMVC使用AOP监听方法推送数据

导入aop的maven依赖 <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.6.12</version> </dependency>创建一个spring的XML文件编写aop配置 <?xml version"1.0" …

pytest+yaml实现接口自动化框架

前言 httprunner 用 yaml 文件实现接口自动化框架很好用&#xff0c;最近在看 pytest 框架&#xff0c;于是参考 httprunner的用例格式&#xff0c;写了一个差不多的 pytest 版的简易框架 项目结构设计 项目结构完全符合 pytest 的项目结构&#xff0c;pytest 是查找 test_.…

【ARM Coresight OpenOCD 系列 1 -- OpenOCD 介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 OpenOCD 介绍1.1.1 OpenOCD 支持的JTAG 适配器1.1.2 OpenOCD 支持的调试设备1.1.3 OpenOCD 支持的 Flash 驱动 1.2 OpenOCD 安装与使用1.2.1 OpenOCD 代码获取及安装1.2.2 OpenOCD 使用1.2.3 OpenOCD 启用 GDB…

互联网金融风控常见知识点

1.怎么做互联网金融风控 首先风险不是都是坏的&#xff0c;风险是有价值的。也就是风险的VaR值(Value at Risk) 对于互联网信贷风控&#xff0c;是要把风险和收益做到更合理的平衡&#xff0c;在控制风险水平的情况下使得收益更高。 所以&#xff0c;做风控的不是一味地追求耕…

【C++进阶】继承

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a; 继承的概念与定义、基类与派生类对象赋值转换、继承中的作用域、派生类的默认成员函数、继承与友元、继承与静态成员、菱形继承与虚继承、继承的总结与反思。 &#x1f43c;本文作者&#xff1…

企业办理CCRC需要多少费用?

近几年&#xff0c;很多企业都在咨询了解CCRC认证&#xff0c;各企业对于办理CCRC资质认证最在意的一个环节就是办理的费用&#xff0c;也有不少企业都在咨询同邦信息科技的小编费用的问题&#xff0c;那今天同邦信息科技的小编就给大家说一下 先来给大家科普一下CCRC认证&…

跨境电商源码独立开发:一次购买,终生使用

随着全球电子商务的快速发展&#xff0c;越来越多的企业开始涉足跨境电商领域。为了在这个竞争激烈的市场中脱颖而出&#xff0c;您需要一个专业的跨境电商解决方案。我们的团队为您提供最优质的源码独立开发服务&#xff0c;让您拥有一个功能强大、安全稳定的跨境电商平台。 一…