LeetCode算法之----回溯

news2024/12/23 9:17:46

目录

【一】前言

【二】全排列

【三】电话号码的字母组合

【四】括号生成

【五】组合总和

【六】子集

【七】总结

【一】前言

回溯算法采用试错的思想,尝试分步的来解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

  • 找到一个可能存在的正确的答案;
  • 在尝试了所有可能的分步方法后宣告该问题没有答案。

回溯算法通常与递归算法结合在一起使用,当选择了某个步骤后再递归该操作过程,最后再回溯上一步的选择(也叫做撤销选择),回溯算法里面最经典的就是全排列问题了,以下介绍全排列以及类似的回溯算法题解。

【二】全排列

【题目】:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

【题解】:这道题属于回溯里面的入门级题型,也是必须要掌握的,重点在于如何分步选择数组里面的不同元素并排序,因为相同的元素不同的排序位置都算作一个排列答案,如示例1。如何做到我们在某一分步选择了数组中某个元素后,再撤销对该元素的选择呢?这里我们需要引用一个状态标志数组,需要对之前选择过的元素进行标识以及在回溯时进行状态重置。处理好了这一关键步骤之后,其余的就是递归以及边界条件退出循环和变量的设置了。

class Solution {
    public List<List<Integer>> res = new ArrayList<List<Integer>>();
    public List<List<Integer>> permute(int[] nums) {
        List<Integer> path = new ArrayList<Integer>();
        boolean[] used = new boolean[nums.length];//状态标识数组
        Integer count = 0;//代表排列到了哪层
        dfs(nums,count,used,path);
        return res;
    }
    public void dfs(int[] nums,Integer count ,boolean[] used,List<Integer> path){
        if(count == nums.length){
            res.add(new ArrayList<Integer>(path));
        }else{
            for(int i=0; i<used.length; i++){
                if(!used[i]){
                    path.add(nums[i]);
                    used[i] = true;
                    dfs(nums,count+1,used,path);
                    path.remove(path.size()-1);
                    used[i]= false;
                }
            }
        }
    }
}

【三】电话号码的字母组合

【题目】:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

【题解】:解决这个问题需要处理3个关键点:第一个是找到合适的数据结构来预处理存储电话号码上数字对应的字符组合,由于只考虑给定的2-9个数字对应的字符组合,我们很容易想到用哈希表(键值对)来存储它们的关系。第二个是找到输入数字串中某个数字对应在电话号码按键上的字符组合是哪些,这里就需要关联上第一步的哈希表了。最后一步是找到某个数字的字符串组合后,循环的拼接数字对应的字符串数组,递归和拼接以及回溯操作均在一个循环里进行。当操作的步数满足输入数字的长度时,添加拼接字符到list集合中。

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> combinations = new ArrayList<String>();//新建一个list集合用于存放返回字符串结果集
        if (digits.length() == 0) {
            return combinations;//如果字符串长度为0,则直接返回空list
        }
        Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};//用map集合存放电话号码(2-9)对应的字符数组(a-z)
        backtrack(combinations,phoneMap,digits,0,new StringBuffer());//回溯+递归算法
        return combinations;
    }
    public void backtrack(List<String> combinations,Map<Character, String> phoneMap,String digits,int index,StringBuffer combination){
        if(index == digits.length()){
            combinations.add(combination.toString());//当遍历的字符位置+1等于字符的长度时,说明已经遍历完字符了,返回结果集
        }else{
            char digit = digits.charAt(index);//字符串中某个位置的电话号码数字
            String letters = phoneMap.get(digit);//电话号码数字对应在map集合中包含有哪些字符集合
            int letterCount = letters.length();//该字符集合的长度,用于遍历
            for(int i=0; i<letterCount; i++){
                combination.append(letters.charAt(i));//依次循环遍历存储该字符某个电话号码对应的字符
                backtrack(combinations,phoneMap,digits,index+1,combination);//递归依次调用遍历电话号码字符中的数字并追加到字符集中,作为本次循环遍历的结果
                combination.deleteCharAt(index);//回溯操作,删除开始加入到字符集合中遍历电话号码数字对应的字符
            }
        }
    }
}

【四】括号生成

【题目】:数字n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

【题解】:这题有一个隐含的已知条件,我们需要挖掘出来并用到题解中,那就是拼接生成的有效括号长度为输入数字n与左右2个括号的乘积数(2n)。这个题目分为两个大的步骤考虑,第一个步骤是当拼接的字符串长度小于2n时,继续选择拼接'('或')'字符,以及递归循环和对它们的回溯操作。第二个步骤是当拼接的字符长度等于乘积2n时,需要校验拼接的字符是否满足有效括号组合,如果满足才把该拼接字符加到结果集合list中,否则不添加。

注:结合全排列的思想,先给所有拼接字符进行一个全排列,最后校验符合有效括号组合的字符串并添加到list集合中,只是这里不需要标识和重置状态,而是选择2个括号。

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> list = new ArrayList<String>();
        Integer count = 0;
        StringBuffer str = new StringBuffer("");
        dfs(count,n,list,str);
        return list;
    }
    public void dfs(Integer count,int n,List<String> list,StringBuffer str){
        if(count == 2*n){
            if(checkLetter(str.toString())){
                list.add(str.toString());
                return;
            }
        }
        if(str.length() < 2*n){
            str.append('(');
            dfs(count+1,n,list,str);
            str.deleteCharAt(str.length()-1);
            str.append(')');
            dfs(count+1,n,list,str);
            str.deleteCharAt(str.length()-1);
        }
    }

    public boolean checkLetter(String str){
        char[] letter = str.toCharArray();
        Deque<Character> deque = new LinkedList<Character>();
        for(char ch:letter){
            if(!deque.isEmpty() && ch == ')' && deque.peek() == '('){
                deque.pop();//遍历字符数组,如果匹配到一组有效括号组合就弹出栈顶'('
                continue;
            }
            deque.push(ch);
        }
        return deque.isEmpty()?true:false;
    }
}

【五】组合总和

【题目】:给你一个 无重复元素 的整数数组candidates和一个目标整数target ,找出candidates中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

【题解】:解决这题的关键是,找出无重复元素的整数数组的某一个或某几个,相加的和与目标整数相等,需要注意的是 选择的元素可以无限制重复次数的选择。所以我们可以分为2个步骤来处理,第一个是不选择之前已经选择过的元素,从而直接递归选择数组中其他不同的元素,直到某次选择的元素集合等于目标集合target时,添加到结果集合中。第二个是重复选择之前已经选择过的元素,添加到该次选择元素和与列表,并且递归和回溯操作,边界条件为操作步骤等于数组长度返回,或者某次操作选择元素和等于目标值时,把该次选择的元素集合添加到结果结合list中。

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<Integer> list = new ArrayList<Integer>();
        Integer result = 0;
        Integer index = 0;
        dfs(index,result,list,candidates,target);
        return ans;
    }
    public void dfs(Integer index,Integer result,List<Integer> list,int[] candidates, int target){
        if(index == candidates.length){
            return;
        }
        if(result == target){
            ans.add(new ArrayList<Integer>(list));
            return;
        }
        dfs(index+1,result,list,candidates,target);//不选择之前已经选过的元素,此时index+1
        if(target - result >= 0){//可重复选择,此时index不动
            result += candidates[index];
            list.add(candidates[index]);
            dfs(index,result,list,candidates,target);
            result -= candidates[index];
            list.remove(list.size()-1);
        }
    }
}

【六】子集

【题目】:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

【题解】:这题与全排列可谓是刚好相反,全排列需要标识已经选择过的元素并且需要做状态重置操作,而子集不需要这些步骤,只不过这里需要对原来数组集合的子集做选择和添加,也就是说只要任何子集符合满足属于原数组(包括空集合),就可以添加到结果集合中来。只不过这里的步骤需要注意一下的是,选择了某个元素后递归操作,接下来撤销对该元素的选择后再次递归操作,从而达到再次循环往复遍历数组的效果。

class Solution {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> list = new ArrayList<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(0,nums);
        return res;
    }
    public void dfs(Integer index,int[] nums){
        if(index == nums.length){
            res.add(new ArrayList<Integer>(list));
            return;
        }
        list.add(nums[index]);
        dfs(index+1,nums);
        list.remove(list.size()-1);
        dfs(index+1,nums);
    }
}

【七】总结

回溯算法的核心思想是需要找到在某个步骤选择某个元素并且递归这个操作后,可以回溯上一步的操作,从而达到多个步骤最终能够实现想要的结果值。需要注意的是,要区分什么情况下需要进行状态标识和状态重置,什么情况下不需要状态标识及重置。还有选择某个元素和不选择某个元素的步骤,结合不同的场景点来使用。

我是程序大视界,坚持原创技术博客分享。

注:如果本篇博客有任何纰漏和建议,欢迎私信或评论留言!

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

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

相关文章

helm、k8s dasboard、rancher、kubesphere介绍及使用

文章目录1. helm 安装及使用概述1.1 helm 安装1.1.1 添加仓库1.2 helm 常用命令2. dashboard 部署使用2.1 安装helm repo 源2.2 安装dashboard2.3 查看dashboard 运行状态2.4 创建dashboard-admin.yaml文件2.5 创建登录用户2.6 查看admin-user账户的token2.7 登录dashboard2.8 …

非对称加密实战(二):解决web项目不支持https问题 ,添加证书【附源码】

目录web项目http请求变为https请求解决无法访问https问题重启再次访问https出现链接不安全,但是可以继续访问认证文件加入域名参数生成客户端认证文件证书安装源码地址web项目 http请求变为https请求 http请求 https请求 解决无法访问https问题 需要把 非对称加密实战(一…

【博客576】警惕docker本身iptables规则对网络的影响

警惕docker本身iptables规则对网络的影响 警惕1&#xff1a;k8s环境下&#xff0c;独立拉取docker容器时&#xff0c;进行端口映射会有问题 场景&#xff1a; 在k8s节点由于某种原因&#xff0c;比如&#xff1a;需要拉起一个docker环境来制作镜像&#xff0c;需要拉起一些不…

靶机测试Os-hacknos-3笔记

靶机介绍Difficulty: IntermediateFlag: 2 Flag first user And the second rootLearning: Web Application | Enumeration | Privilege EscalationWeb-site: www.hacknos.comContact-us : rahul_gehlautThis works better with VirtualBox rather than VMware靶机地址https://…

nuPlan: A closed-loop ML-based planning benchmark for autonomous vehicles

Paper name nuPlan: A closed-loop ML-based planning benchmark for autonomous vehicles Paper Reading Note URL: https://arxiv.org/pdf/2106.11810.pdf TL;DR nuPlan 比赛&#xff0c;提出了规控领域新数据集 Introduction 背景 当前自动驾驶规划任务中使用专家系统…

正确实践Jetpack SplashScreen API —— 在所有Android系统上使用总结,内含原理分析

1.前言 文章末尾有演示的APK链接&#xff0c;感兴趣的同学&#xff0c;可以自行下载体验一下 官方Android 12的Splash Screen文档地址 官方Splash Screen兼容库&#xff0c;支持所有版本系统 本篇文章主要围绕下面三个问题来介绍&#xff1a; 我们能从Android 12 SplashScree…

订单数据越来越多,如何优化数据库性能?

“增删改查”都是查找问题&#xff0c;因为你都得先找到数据才能对数据做操作。那存储系统性能问题&#xff0c;其实就是查找快慢问题。 存储系统一次查询所耗时间取决两个因素&#xff1a; 查找的时间复杂度数据总量 查找的时间复杂度取决于&#xff1a; 查找算法存储数据…

基于Java-SpringBoot+vue实现的前后端分离信息管理系统设计和实现

基于Java-SpringBootvue实现的前后端分离信息管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言…

后台执行限制总结

后台限制的发展历程 前台定义 针对后台Service procState < PROCESS_STATE_IMPORTANT_BACKGROUND7 针对后台启动Activity procState < PROCESS_STATE_BOUND_TOP3 针对后台启动FGS/后台启动FGS的while-in-use权限 procState < PROCESS_STATE_BOUND_FOREGROUND_SERVICE…

【Linux】文本编辑器-vim使用

目  录1 vim的基本概念2 vim的基本操作3 vim常用模式命令集3.1 vim正常模式命令集3.2 vim末行模式命令集4 vim的简单配置1 vim的基本概念 vim编辑器与vi编辑器一样都是多模式编辑器&#xff0c;不同的是vim编辑器是vi编辑器的升级版本&#xff0c;vim不仅兼容vi的所有指令&am…

Django by Example·第二章|Enhancing Your Blog with Advanced Features@笔记

Django by Example第二章|Enhancing Your Blog with Advanced Features笔记 这本书的结构确实很不错&#xff0c;如果能够坚持看下去&#xff0c;那么Django框架的各种用法也就掌握的七七八八了。之前写过一篇这本书的第一章&#xff0c;看完第一章就算是入门了&#xff0c;但…

acwing差分

题目&#xff1a;输入一个长度为 n 的整数序列。接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c&#xff0c;表示将序列中 [l,r] 之间的每个数加上 c。请你输出进行完所有操作后的序列。输入格式第一行包含两个整数 n 和 m。第二行包含 n 个整数&#xff0c;表示整…

【C++高阶数据结构】跳表(skiplist)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

第十一章Thymeleaf学习

文章目录什么是Thymeleaf什么是模板引擎Thymeleaf的同行Thymeleaf优势一个实例来认识大概过程导入对应的jar包配置对应的xml文件对应的ViewBaseServlet编写——对应的模板引擎写对应的Servlet类并且继承ViewBaseServlet对应index.html资源——对应的模板Thymeleaf的基础语法th名…

337. 打家劫舍 III

目录题目思路代码题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”…

笔试强训(11)

第一题:二进制插入二进制插入__牛客网 给定32位整数n和m&#xff0c;同时我们指定i和j&#xff0c;将m的二进制位数插入到n的二进制位数的j到i位&#xff0c;我们保证n的j到i位均是等于0的&#xff0c;况且m的二进制位数小于等于i-j1&#xff0c;其中二进制的位数从0开始从低到…

js设计模式(八)-总体感受一下设计模式

前言 首先&#xff0c;不得不说我们是站在巨人的肩膀上写代码&#xff0c;前辈们已经很合理的帮助我们总结出来了23种设计模式&#xff0c;虽然有些已经被语言直接使用Api实现了&#xff0c;感谢走在前沿的攻城狮。 但是真真正正的看一遍所有的设计模式还是很有必要的&#x…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO&#xf…

计算机基础——计算机分类

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会讲解计算机分类应用领域以及发展趋势 一.计算机分类 计算机并非只有日常所…

并行计算 Clion配置使用OpenMP

文章目录配置CMakeList.txt文件OpenMP之HelloWorld数据共享属性shared子句private子句default子句default(shared)default(none)配置CMakeList.txt文件 文件底部加入以下内容&#xff0c;即可支持OpenMP FIND_PACKAGE(OpenMP REQUIRED) if (OPENMP_FOUND)message("OPENM…