代码随想录算法训练营第二十七天丨 回溯算法part04

news2024/11/21 2:39:12

93.复原IP地址

思路

其实只要意识到这是切割问题,切割问题就可以使用回溯搜索法把所有可能性搜出来,和刚做过的131.分割回文串 (opens new window)十分类似。

切割问题可以抽象为树型结构,如图:

93.复原IP地址

回溯三部曲

  • 递归参数

在131.分割回文串 (opens new window)中提到切割问题类似组合问题。

startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。

本题还需要一个变量pointNum,记录添加逗点的数量。

所以代码如下:

List<String> res = new ArrayList<>();// 记录结果
StringBuilder sb = new StringBuilder();//字符串拼接
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
 boolean isVoid(String s,int start,int end){
  • 递归终止条件

本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。

pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。

然后验证一下第四段是否合法,如果合法就加入到结果集里

代码如下:

if (pointSum == 3){
    // 判断第四段子字符串是否合法,如果合法就放进result中
    if (isVoid(s,startIndex,s.length() - 1)){
        res.add(s);
    }
    return;
}
  • 单层搜索的逻辑

for (int i = startIndex; i < s.size(); i++)循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。

如果合法就在字符串后面加上符号.表示已经分割。

如果不合法就结束本层循环,如图中剪掉的分支:

93.复原IP地址

然后就是递归和回溯的过程:

递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.),同时记录分割符的数量pointNum 要 +1。

回溯的时候,就将刚刚加入的分隔符. 删掉就可以了,pointNum也要-1。

代码如下:

for (int i = startIndex; i < s.length(); i++) {
    //如果不符合规范,直接结束本层循环
    if (!isVoid(s,startIndex,i)){
        break;
    }
    //符合规范
    sb.delete(0,sb.length());
    sb.append(s).insert(i+1,".");
    backtracking(sb.toString(),i+2,pointSum+1);/// 插入逗点之后下一个子串的起始位置为i+2
}

判断子串是否合法

最后就是在写一个判断段位是否是有效段位了。

主要考虑到如下三点:

  • 段位以0为开头的数字不合法
  • 段位里有非正整数字符不合法
  • 段位如果大于255了不合法

代码如下:

 // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
 boolean isVoid(String s,int start,int end){
     if (start > end) {
         return false;
     }
     if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
         return false;
     }
     int num = 0;
     for (int i = start; i <= end; i++) {
         if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
             return false;
         }
         num = num * 10 + (s.charAt(i) - '0');
         if (num > 255) { // 如果⼤于255了不合法
             return false;
         }
     }
     return true;
 }

整体代码如下:

class Solution {
    //结果集
    List<String> res = new ArrayList<>();
    StringBuilder sb = new StringBuilder();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s,0,0);
        return res;
    }
    public void backtracking(String s,int startIndex,int pointSum){
        if (pointSum == 3){
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isVoid(s,startIndex,s.length() - 1)){
                res.add(s);
            }
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            //如果不符合规范,直接结束本层循环
            if (!isVoid(s,startIndex,i)){
                break;
            }
            //符合规范
            sb.delete(0,sb.length());
            sb.append(s).insert(i+1,".");
            backtracking(sb.toString(),i+2,pointSum+1);/// 插入逗点之后下一个子串的起始位置为i+2
        }
    }
    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
    boolean isVoid(String s,int start,int end){
        if (start > end) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }
}

78.子集

思路

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

有同学问了,什么时候for可以从0开始呢?

求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合。

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:

78.子集

从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合

回溯三部曲

  • 递归函数参数

全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)

递归函数参数在上面讲到了,需要startIndex。

代码如下:

List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
void backTracking(int[] nums, int startIndex)

递归终止条件

从图中可以看出:

78.子集

剩余集合为空的时候,就是叶子节点。

那么什么时候剩余集合为空呢?

就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,代码如下:

 if (startIndex > nums.length -1) {
     return;
 }

其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了

  • 单层搜索逻辑

求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树

那么单层递归逻辑代码如下:

for (int i = startIndex; i < nums.length; i++) {
    path.add(nums[i]); // 子集收集元素
    backTracking(nums, i + 1); // 注意从i+1开始,元素不重复取
    path.remove(path.size() - 1);// 回溯
}

根据关于回溯算法,你该了解这些! (opens new window)给出的回溯算法模板:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

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

整体代码如下:

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

    public List<List<Integer>> subsets(int[] nums) {
        Arrays.sort(nums);
        backTracking(nums, 0);
        return res;
    }
    void backTracking(int[] nums, int startIndex) {
        res.add(new ArrayList<>(path));
        if (startIndex > nums.length -1) {
            return;
        }

        for (int i = startIndex; i < nums.length; i++) {
            path.add(nums[i]); // 子集收集元素
            backTracking(nums, i + 1); // 注意从i+1开始,元素不重复取
            path.remove(path.size() - 1);// 回溯
        }
    }
}

90.子集II

思路

其实和上一题整体逻辑一样,但是要注意去重的逻辑,要分清楚树层上去重和树枝上去重的区别。

本题不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backTracking(nums, 0);
        return res;
    }
    void backTracking(int[] nums, int startIndex) {
        res.add(new ArrayList<>(path));
        if (startIndex >= nums.length) {
            return;
        }
        for (int i = startIndex; i < nums.length; i++) {
            if (i > startIndex && nums[i - 1] == nums[i]) {
                continue;
            }
            path.add(nums[i]);
            backTracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

以上为我做题时候的相关思路,自己的语言组织能力较弱,很多都是直接抄卡哥的,有错误望指正。

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

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

相关文章

浏览器中的网络钓鱼防护

网络钓鱼防护是一项功能&#xff0c;可保护用户免受旨在窃取其敏感信息的网络钓鱼攻击&#xff0c;网络钓鱼是网络犯罪分子常用的技术&#xff0c;这是一种社会工程攻击&#xff0c;诱使用户单击指向受感染网页的恶意链接&#xff0c;用户在该网页中感染了恶意软件或其敏感信息…

神经网络硬件加速器-DPU分析

一 DPU概述 DPU是专为卷积神经网络优化的可编程引擎&#xff0c;其使用专用指令集&#xff0c;支持诸多卷积神经网络的有效实现。 1、关键模块 卷积引擎&#xff1a;常规CONV等ALU&#xff1a;DepthwiseConvScheduler&#xff1a;指令调度分发Buffer Group&#xff1a;片上数据…

利用爬虫采集音频信息完整代码示例

以下是一个使用WWW::RobotRules和duoip.cn/get_proxy的Perl下载器程序&#xff1a; #!/usr/bin/perluse strict; use warnings; use WWW::RobotRules; use LWP::UserAgent; use HTTP::Request; use HTTP::Response;# 创建一个UserAgent对象 my $ua LWP::UserAgent->new();#…

鸿蒙状态栏设置

鸿蒙状态栏设置 基于鸿蒙 ArkTS API9&#xff0c;设置状态栏颜色&#xff0c;隐藏显示状态栏。 API参考文档 参考文档 新建项目打开之后发现状态栏是黑色的&#xff0c;页面颜色设置完了也不能影响状态栏颜色&#xff0c;如果是浅色背景&#xff0c;上边有个黑色的头&#…

C# LINQ常用操作方法——提升你的编程效率

导语&#xff1a;C# LINQ&#xff08;Language Integrated Query&#xff09;是一种强大且灵活的查询语言&#xff0c;可以将数据查询、过滤、排序和转换等操作无缝集成到C#代码中。本文将介绍一些常用的LINQ操作方法&#xff0c;帮助熟练掌握LINQ的使用&#xff0c;并进一步提…

【玩转 EdgeOne】边缘安全加速平台EO给自己的技术博客插上“翅膀”

目录 一、边缘安全加速平台 EO简介 二、产品功能 三、应用场景 四、网站加速体验 五、总结 作为一个技术博客爱好者&#xff0c;不知不觉已经在程序员行业将近十年了&#xff0c;写技术博客也有将近七年的时间&#xff0c;其中我也搭建了一个自己的技术博客&#xff0c;因…

iPhone 12电池寿命结果:四款机型都进行了比较

如果你想决定买哪款iPhone 12&#xff0c;电池寿命应该在你的清单上名列前茅。我们已经通过电池测试运行了所有新款iPhone 12&#xff0c;结果喜忧参半。 iPhone 12 mini是其中最小的一款&#xff0c;有一块小电池可供匹配&#xff0c;在5G上运行时间最短。iPhone 12和iPhone …

探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码

一、NIO简介 Java NIO&#xff08;New IO&#xff09;是Java SE 1.4引入的一个新的IO API&#xff0c;它提供了比传统IO更高效、更灵活的IO操作。与传统IO相比&#xff0c;Java NIO的优势在于它支持非阻塞IO和选择器&#xff08;Selector&#xff09;等特性&#xff0c;能够更…

基于下垂控制的孤岛双机并联逆变器环流抑制模型(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

分享一个Redis自带的压测工具:redis-benchmark

前言 今天给xdm分享一个Redis自带的压测工具&#xff1a;redis-benchmark。 介绍 redis-benchmark 是一个用于测试 Redis性能的基准测试工具&#xff0c;可以帮助开发人员评估和比较 Redis 在不同配置或负载情况下的吞吐量和延迟。 使用 redis-benchmark 可以执行多种类型的…

如何将几个模型合并成一个

1、什么时候需要合并模型&#xff1f; 组装和装配&#xff1a;当你需要将多个零件或组件组装成一个整体时&#xff0c;可以合并它们成为一个模型。例如&#xff0c;在制造业中&#xff0c;当需要设计和展示一个完整的机械装置或产品时&#xff0c;可以将各个零部件合并成一个模…

从黑客帝国到元宇宙,“数字身份”之船终将驶向何方?

1999年第一部《黑客帝国》在北美上映&#xff0c;当时正值千禧年初&#xff0c;在这个互联网并未全民普及的年代&#xff0c;《黑客帝国》超前地抛出在蓝、红药丸&#xff08;即虚幻与真实&#xff09;之间作选择的经典问题&#xff0c;表达人类与互联网之间的虚实关系&#xf…

c 语言基础:L1-044 稳赢

大家应该都会玩“锤子剪刀布”的游戏&#xff1a;两人同时给出手势&#xff0c;胜负规则如图所示&#xff1a; 现要求你编写一个稳赢不输的程序&#xff0c;根据对方的出招&#xff0c;给出对应的赢招。但是&#xff01;为了不让对方输得太惨&#xff0c;你需要每隔K次就让一个…

对传感器采样数据的低通滤波

低通滤波(Low-pass filter) 是一种过滤方式&#xff0c;规则为低频信号能正常通过&#xff0c;而超过设定临界值的高频信号则被阻隔、减弱。 一阶低通数字滤波器 滤波系数a越小&#xff0c;滤波结果越平稳&#xff0c;但是灵敏度低&#xff1b;滤波系数a越大&#xff0c;滤波结…

LeetCode算法刷题(python) Day43|09动态规划|343. 整数拆分、96.不同的二叉搜索树

目录 LeetCode 343. 整数拆分LeetCode 96.不同的二叉搜索树 LeetCode 343. 整数拆分 力扣题目链接 正整数n&#xff0c;先拆成两个数i, n-i&#xff0c;拆成多个数可以对n-i进行拆分&#xff0c;然后对这些求最大值。 确定dp数组以及下标的含义&#xff1a;对i进行拆分的最大…

ch552g下载时遇到的可能问题

第一次出现的问题 首先确定&#xff0c;vcc和v33&#xff0c;示例芯片中的是15和16引脚都并联了去耦电容。 烧录一般使用上面的两个软件。第一次出现的问题&#xff0c;当没有打开这两个烧录软件时&#xff0c;连接usb数据线后电脑循环出现识别和断开的情况。当打开软件后就能…

通讯网关软件028——利用CommGate X2Modbus实现Modbus RTU访问PI服务器

本文介绍利用CommGate X2Modbus实现Modbus RTU访问PI数据库。CommGate X2MODBUS是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现上位机通过Modbus RTU来获取PI数据库的数据。 【解决方案…

计算机算法分析与设计(12)---贪心算法(最优装载问题和哈夫曼编码问题)

文章目录 一、最优装载问题1.1 问题表述1.2 代码编写 二、哈夫曼编码2.1 哈夫曼编码概述2.2 前缀码2.3 问题描述2.4 代码思路2.5 代码编写 一、最优装载问题 1.1 问题表述 1. 有一批集装箱要装上一艘载重量为 c c c 的轮船&#xff0c;已知集装箱 i ( 1 ≤ i ≤ n ) i(1≤i≤…

神经网络中的反向传播:综合指南

塔曼纳 一、说明 反向传播是人工神经网络 &#xff08;ANN&#xff09; 中用于训练深度学习模型的流行算法。它是一种监督学习技术&#xff0c;用于调整网络中神经元的权重&#xff0c;以最小化预测输出和实际输出之间的误差。 在神经网络中&#xff0c;反向传播是计算损失函数…

纳米软件科普|半导体常见的测试种类详解

纳米软件科普下半导体几种常见的测试方法种类的详细介绍&#xff0c;有你不了解的吗&#xff1f; 外观检测&#xff1a;这一步骤主要是对半导体外观质量的评估。它包括检查芯片的平整度、颜色、镜面度等&#xff0c;以确保半导体表面无明显缺陷或不规则形状。 电性能测试&…