代码随想录刷题day23丨39. 组合总和,40.组合总和II, 131.分割回文串

news2024/12/28 6:05:16

代码随想录刷题day23丨39. 组合总和,40.组合总和II, 131.分割回文串

1.题目

1.1组合总和

  • 题目链接:39. 组合总和 - 力扣(LeetCode)

    在这里插入图片描述

  • 视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili

  • 文档讲解:https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html

  • 解题思路:回溯

    • 本题搜索的过程抽象成树形结构如下:

      在这里插入图片描述

    • 注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!

    • 回溯三部曲

      • 递归函数参数

        List<Integer> path = new ArrayList<>();
        List<List<Integer>> result = new ArrayList<>();
        void backtracking(int[] candidates,int target,int sum,int startIndex)
        
      • 递归终止条件

        if(sum > target){
            return;
        }
        if(sum == target){
            result.add(new ArrayList<>(path));
            return;
        }
        
      • 单层搜索的逻辑

        for(int i = startIndex;i < candidates.length;i++){
            path.add(candidates[i]);
            sum += candidates[i];
            backtracking(candidates,target,sum,i);
            sum -= candidates[i];
            path.remove(path.size() -1);
        }
        
  • 代码:

    class Solution {
        List<Integer> path = new ArrayList<>();
        List<List<Integer>> result = new ArrayList<>();
        public List<List<Integer>> combinationSum(int[] candidates, int target) {
            backtracking(candidates,target,0,0);
            return result;
        }
    
        void backtracking(int[] candidates,int target,int sum,int startIndex){
            if(sum > target){
                return;
            }
            if(sum == target){
                result.add(new ArrayList<>(path));
                return;
            }
            for(int i = startIndex;i < candidates.length;i++){
                path.add(candidates[i]);
                sum += candidates[i];
                backtracking(candidates,target,sum,i);
                sum -= candidates[i];
                path.remove(path.size() -1);
            }
        }
    }
    
    • 剪枝优化

      class Solution {
          List<Integer> path = new ArrayList<>();
          List<List<Integer>> result = new ArrayList<>();
          public List<List<Integer>> combinationSum(int[] candidates, int target) {
              Arrays.sort(candidates); // 先进行排序
              backtracking(candidates,target,0,0);
              return result;
          }
      
          void backtracking(int[] candidates,int target,int sum,int startIndex){
              if(sum > target){
                  return;
              }
              if(sum == target){
                  result.add(new ArrayList<>(path));
                  return;
              }
              for(int i = startIndex;i < candidates.length;i++){
                  // 如果 sum + candidates[i] > target 就终止遍历
                  if (sum + candidates[i] > target) break;
                  path.add(candidates[i]);
                  sum += candidates[i];
                  backtracking(candidates,target,sum,i);
                  sum -= candidates[i];
                  path.remove(path.size() -1);
              }
          }
      }
      
  • 总结:

    • 在求和问题中,排序之后加剪枝是常见的套路!

1.2组合总和II

  • 题目链接:40. 组合总和 II - 力扣(LeetCode)

    在这里插入图片描述

  • 视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili

  • 文档讲解:https://programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html

  • 解题思路:回溯

    • 集合(数组candidates)有重复元素,但还不能有重复的组合

    • 我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

    • 强调一下,树层去重的话,需要对数组排序!

    • 选择过程树形结构如图所示:

      在这里插入图片描述

  • 代码:

    class Solution {
        List<Integer> path = new ArrayList<>();
        List<List<Integer>> result = new ArrayList<>();
    
        public List<List<Integer>> combinationSum2(int[] candidates, int target) {
            Arrays.sort(candidates);  // 先排序
            backtracking(candidates, target, 0, 0);
            return result;
        }
    
        void backtracking(int[] candidates, int target, int sum, int startIndex) {
            if (sum > target) {
                return;
            }
            if (sum == target) {
                result.add(new ArrayList<>(path));
                return;
            }
            for (int i = startIndex; i < candidates.length; i++) {
                // 跳过相同的元素,避免产生重复组合
                if (i > startIndex && candidates[i] == candidates[i - 1]) {
                    continue;
                }
    
                // 如果当前元素加上sum大于目标值,则不继续遍历
                if (sum + candidates[i] > target) {
                    break;
                }
                path.add(candidates[i]);
                sum += candidates[i];
                backtracking(candidates, target, sum, i+1);
                sum -= candidates[i];
                path.remove(path.size() - 1);
            }
        }
    }
    
  • 总结:

    • 直接用startIndex来去重

1.3分割回文串

  • 题目链接:131. 分割回文串 - 力扣(LeetCode)

    在这里插入图片描述

  • 视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibili

  • 文档讲解:https://programmercarl.com/0131.%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.html

  • 解题思路:回溯

    • 本题涉及到两个关键问题:

      • 切割问题,有不同的切割方式
      • 判断回文
    • 回文串是向前和向后读都相同的字符串。

    • 我们来分析一下切割,其实切割问题类似组合问题

      • 例如对于字符串abcdef:
        • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
        • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。
    • 所以切割问题,也可以抽象为一棵树形结构,如图:

      在这里插入图片描述

    • 递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。

    • 此时可以发现,切割问题的回溯搜索的过程和组合问题的回溯搜索的过程是差不多的。

    • 回溯三部曲

      • 递归函数参数

        List<String> path = new ArrayList<>();
        List<List<String>> result = new ArrayList<>();
        void backtracking(String s,int startIndex)
        
      • 递归函数终止条件

        if(startIndex == s.length()){ // 递归终止条件:当索引遍历到字符串末尾时
            result.add(new ArrayList<>(path));// 将当前的分割路径 `path` 加入到 `result` 中
            return;
        }
        
      • 单层搜索的逻辑

        StringBuilder sb = new StringBuilder();
        for(int i = startIndex;i < s.length();i++){
            sb.append(s.charAt(i));// 将从 startIndex 开始的字符逐个加入 StringBuilder
            if(check(sb)){ // 如果当前的子串是回文串
                path.add(sb.toString());// 将该回文子串加入当前路径
                backtracking(s,i + 1);// 递归从下一个字符开始
                path.remove(path.size() -1);// 回溯,移除刚刚加入的回文子串
            }
        }
        
  • 代码:

    class Solution {
        List<String> path = new ArrayList<>();
        List<List<String>> result = new ArrayList<>();
    
        public List<List<String>> partition(String s) {
            backtracking(s,0);// 开始进行回溯,从索引0开始
            return result;// 返回最终的所有分割结果
        }
    
        void backtracking(String s,int startIndex){
            if(startIndex == s.length()){ // 递归终止条件:当索引遍历到字符串末尾时
                result.add(new ArrayList<>(path));// 将当前的分割路径 `path` 加入到 `result` 中
                return;
            }
            StringBuilder sb = new StringBuilder();
            for(int i = startIndex;i < s.length();i++){
                sb.append(s.charAt(i));// 将从 startIndex 开始的字符逐个加入 StringBuilder
                if(check(sb)){ // 如果当前的子串是回文串
                    path.add(sb.toString());// 将该回文子串加入当前路径
                    backtracking(s,i + 1);// 递归从下一个字符开始
                    path.remove(path.size() -1);// 回溯,移除刚刚加入的回文子串
                }
            }
        }
    
        //检查是否是回文
        private boolean check(StringBuilder sb){
            for(int i = 0;i < sb.length()/2;i++){
                if(sb.charAt(i) != sb.charAt(sb.length() - 1 - i)){ // 检查首尾字符是否相等
                    return false;
                }
            }
            return true;
        }
    }
    
  • 总结:

    • 切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。
    • 那么在代码里什么是切割线呢?
      • 在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。
    • 在递归循环中如何截取子串呢?
      • 在for循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。
      • 首先判断这个子串是不是回文,如果是回文,就加入在path中,path用来记录切割过的回文子串。
      • 注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1
    • 如何判断一个字符串是否是回文?
      • 可以使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。
    • 本题有如下几个难点:
      • 切割问题可以抽象为组合问题
      • 如何模拟那些切割线
      • 切割问题中递归如何终止
      • 在递归循环中如何截取子串
      • 如何判断回文

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

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

相关文章

计算机网络基础笔记(二)

计算机网络基础笔记&#xff08;二&#xff09; OSI网络模型 osi&#xff08;开放系统互联–Open System Interconnect&#xff09;模型是一种通信协议的框架&#xff0c;作用是在不同计算机系统之间互联。该模型间通信分为七个层次&#xff0c;每个层次负责特定的功能&#…

设计模式-单例模式工厂模式

3.1 单例模式 1.概念 用类来实现单例。由于某种需要&#xff0c;要保证一个类在程序的生命周期中只有一个实例&#xff0c;并且提供该实例的全局访问方法。 2.结构三要素 1)私有的静态对象属性private static instance&#xff0c;它的类型就是当前类的对象&#xff0c;静态…

Windows JDK安装详细教程

一、关于JDK 1.1 简介 Java是一种广泛使用的计算机编程语言&#xff0c;拥有跨平台、面向对象、泛型编程的特性&#xff0c;广泛应用于企业级Web应用开发和移动应用开发。 JDK&#xff08;Java Development Kit&#xff09;是用于开发 Java 应用程序的工具包。它由以下几个主要…

Python 学习笔记(二)

类 构造方法 魔术方法 类的私有成员 继承 语法 class 类(父类1&#xff0c;父类2&#xff0c;...)&#xff1a; 类内容体 对父类的复写 注解 多态

J.U.C Review - 常见的通信工具类解析

文章目录 概述1. Semaphore2. Exchanger3. CountDownLatch4. CyclicBarrier5. Phaser 原理 & Code1. Semaphore2. Exchanger3. CountDownLatch4. CyclicBarrier5. Phaser 概述 Java 的 java.util.concurrent 包提供了许多实用的工具类&#xff0c;用于简化并发编程。这些工…

计算机网络练级第一级————认识网络

网络搁哪&#xff1f; 网络大家应该都很熟悉了&#xff0c;但网络具体是怎么构成的&#xff0c;怎么用的&#xff1f;长话短说 我认为网络就是计算机里的快递业务&#xff0c;从一台计算机中发出&#xff0c;网络接收到数据后&#xff0c;就要把这个数据安全快速完整地发到目…

【idea】设置文件模板

搜索 File and Code Templates 。 添加模板。 在任意文件目录下右键&#xff0c;new->找到添加的模板。 参考链接&#xff1a; IDEA创建模板文件_edit file templates-CSDN博客

科研绘图系列:R语言宏基因组堆积图(stacked barplot)

介绍 宏基因组堆积条形图是一种数据可视化工具,用于展示宏基因组数据中不同分类群(如微生物群落中的物种或菌株)的相对丰度。宏基因组学(Metagenomics)是一种研究环境样本中所有生物的遗传物质(DNA和RNA)的科学,不依赖于培养,可以直接从环境样本中提取DNA进行测序。 …

Windows server 2016 .net framwork 安装

解决方法: 1、windows server 2016默认是不安装.netframework3.5的,可以在添加删除程序中单独添加。但是有时候系统安装文件不在的时候,找不到安装程序就不能安装成功。 这时候单独下载dotnetfx35直接安装是安装不上的,需要用以下方法进行。 2、单独安装: 通过 NetFx3.c…

无人机之动力系统篇

无人机的动力系统是其飞行中最为核心的部分&#xff0c;它决定了无人机的飞行性能和稳定性。一个完整的无人机动力系统通常由多个关键组件组成&#xff0c;这些组件协同工作&#xff0c;为无人机提供动力。以下是对无人机动力系统的详细解析&#xff1a; 一、主要组成部分 电…

大模型训练框架LLaMAFactory覆盖预训练指令微调强化学习评估全流程

1. 项目背景 开源大模型如LLaMA&#xff0c;Qwen&#xff0c;Baichuan等主要都是使用通用数据进行训练而来&#xff0c;其对于不同下游的使用场景和垂直领域的效果有待进一步提升&#xff0c;衍生出了微调训练相关的需求&#xff0c;包含预训练&#xff08;pt&#xff09;&…

3174. 清除数字(Java)

3174. 清除数字(Java) 1.Java 的 StringBuilder 初用。 2. StringBuffer和StringBuilder方法类似&#xff0c;Buffer支持同步访问&#xff0c;线程安全&#xff0c;速度比较慢&#xff0c;所以Buffer用的少&#xff0c;Builder用的多。 class Solution {public String clearD…

C语言深度剖析--不定期更新的第二弹

好久不见&#xff0c;甚是想念。书接上回&#xff0c;继续前进&#xff01; 关键字static-最名不副实的关键字 对extern声明的小小补充 当我要对一个函数进行声明的时候可不可以像如下情况&#xff1a; extern int v_gal100;对这个变量进行了赋值&#xff0c;这是不可以的&a…

相互作用先验下的 3D 分子生成扩散模型 - IPDiff 评测

IPDiff 是一个基于蛋白质-配体相互作用先验引导的扩散模型&#xff0c;首次把配体-靶标蛋白相互作用引入到扩散模型的扩散和采样过程中&#xff0c;用于蛋白质&#xff08;口袋&#xff09;特异性的三维分子生成。 本文将对 IPDiff 实际的分子生成能力进行评测。 一、背景介绍 …

动态规划的解题思想

1. 从斐波那契数列说起 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c; &#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0, F(2) 1 F&#xff08;n&#xff09; F&…

【C++】C++ STL探索:Vector使用与背后底层逻辑

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现 在string类文章中提及了STL容器间的接口是大差不差的&#xff0c;本篇将直…

虚幻5|不同骨骼受到不同伤害|小知识(2)

1.蓝图创建一个结构&#xff0c;B_BoneDamage 结构里添加一个浮点变量&#xff0c;表示伤害倍数 2.当我们创建了一个结构&#xff0c;就需要创建一个数据表格&#xff0c;数据表格可以选择对应的结构 不同骨骼不同倍数伤害&#xff0c;骨骼要对应骨骼网格体的名称 3.把我们br…

C#继承_里氏替换原则

知识点一&#xff1a;基本概念 知识点二&#xff1a;基本实现 #region 知识点二基本实现class GameObject{}class Player : GameObject{public void PlayerAtk(){Console.WriteLine("玩家攻击");}}class Monster : GameObject{public void PlayerAtk(){Console.Writ…

NLP从零开始------文本中阶处理之序列到序列模型(完整版)

1. 序列到序列模型简介 序列到序列( sequence to sequence, seq2seq) 是指输入和输出各为一个序列(如一句话) 的任务。本节将输入序列称作源序列&#xff0c;输出序列称作目标序列。序列到序列有非常多的重要应用&#xff0c; 其中最有名的是机器翻译( machine translation), 机…

消息中间件都有哪些

RabbitMQ&#xff1a;这可是一个开源的消息代理软件&#xff0c;也叫消息中间件。它支持多种消息传递协议&#xff0c;可以轻松地在分布式系统中进行可靠的消息传递。 Kafka&#xff1a;Apache Kafka是一个分布式流处理平台&#xff0c;它主要用于处理实时数据流。Kafka的设计初…