【代码随想录训练营】【Day28】第七章|回溯算法|93.复原IP地址|78.子集|90.子集II

news2024/11/6 7:14:39

复原IP地址

题目详细:LeetCode.93

这道题与上一道练习题分割回文字符串十分详细,一样是涉及到分割字符串、判断字符串、递归与回溯的问题,所以这道题要解决的难点在于:

  • 如何分割IP地址字符串
  • 如何判断分割的IP地址是否合法
  • 递归的结束条件要如何设置

首先解决“如何判断分割的IP地址是否合法”,我们要知道一个合格的IP的地址的要求,在这道题中的要求比较简单,只要满足:

  • IP地址中号段可以为0,但不存在以0开头的号段
  • IP地址长度为4个字节,每一个号段的长度为1个字节,即其大小空间为28 = 256,每一个号段用十进制表示的范围为0~255

其次是“如何分割IP地址字符串”,这里的分割思路于分割回文字符串非常相似:

  • 在分割的过程中,对每一次分割的号段字符串都进行合法性验证
    • 如果当前号段合法,在其后续插入分割符 ‘.’ ,然后进入递归,继续分割下一个号段
    • 如果当前号段不合法,说明在当前位置进行分割的话,已经是一个不合法的IP地址了,所以无需继续分割,直接break跳出当前循环。

最后是“递归的结束条件要如何设置”,观察IP地址的特点可知:

  • 一个IP地址最多存在3个分割符 ‘.’

那么我们就可以递归参数中设置一个变量来记录分割符的数量,如果数量==3时,即说明该IP地址分割完毕。

不过需要注意,我们在之前的分割的过程中,是先判断字段合法之后再添加分割符,所以当分割符数量==3之后,还需要对最后的号段进行合法性验证,以此来判断待添加的IP地址是否真的合法;无论是否合法都要return,因为这是递归的结束条件。

Java解法(递归,回溯):

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

    public boolean isVaild(String s, int begin, int end){
        if (begin >= end || (end - begin) > 3) {
            return false;
        }
        // 0开头的数字不合法
        if (s.charAt(begin) == '0' && begin != end - 1) { 
                return false;
        }
        int num = 0;
        // 遇到非数字字符不合法
        for (int i = begin; i < end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { 
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            // 如果大于255了不合法
            if (num > 255) { 
                return false;
            }
        }
        return true;
    }

    public void backtracking(StringBuffer sb, int startIndex, int pointNums){
        if(pointNums == 3){
            if(isVaild(sb.toString(), startIndex, sb.length()))
                ans.add(sb.toString());
            return;
        }

        for(int i = startIndex; i < sb.length(); i++){
            if(isVaild(sb.toString(), startIndex, i + 1)){
                sb.insert(i + 1, '.');
                pointNums++;
                backtracking(sb, i + 2, pointNums);
                sb.deleteCharAt(i + 1);
                pointNums--;
            }else continue;
        }
    }

    public List<String> restoreIpAddresses(String s) {
        if(!(s.length() > 12 || s.length() < 4)){
            backtracking(new StringBuffer(s), 0, 0);
        }
        return this.ans;
    }
}

子集

题目详细:LeetCode.78

经过前面那些一知半解的练习之后,到了这道题,我终于领悟到了如何去打开回溯的解题思路,当遇到回溯问题时,我们立刻将问题转化都树形结构,开始画图(必须画图来理解,除非你空间想象力真的很好,我一开始凭空想了很久,但很容易乱,决定画图之后,发现一切都十分明辽了):

在这里插入图片描述
画得非常草稿化,从前面回溯的理论基础和回溯解题模版可知:

  • 回溯问题其实就是一个收集树形结构节点结果或路径结果的问题
  • 循环的次数决定了树的宽度,相当于对树进行同层访问
  • 递归的层数,就是循环的嵌套层数,相当于对树进行深度访问
  • 递归的结束条件,就是回溯的条件,相当于访问到树的叶子节点

回到这道题,我们将问题转换为树形结构后,对应的就可以发现:

  • 一个数字也可以是一个子集,所以在每次循环中,只取一个数字,可以定一个变量 start 来标识当前应该取哪个下标的数字
  • 要求不能包含重复的子集,所以在进入递归时,要将当前的数字包括其之前的数字都分割出去,这里我们利用下标变量 start 来控制从数字开始访问
  • 每访问一个节点,其路径上的节点集合,就是一个结果,所以要在循环和遍历过程中就将结果加入结果集
  • 当nums数组为空时,即下标到达数组边界 start == nums.length 时,就是递归的结束条件,即回溯的条件

Java解法(递归,回溯):

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();

    public void backTrack(int[] nums, int start){
        if(nums.length == start){
            return;
        }

        for(int i = start; i < nums.length; i++){
            path.offer(nums[i]);
            ans.add(new ArrayList<Integer>(path));
            backTrack(nums, i + 1);
            path.removeLast();
        }
    }

    public List<List<Integer>> subsets(int[] nums) {
        ans.add(new ArrayList<Integer>());
        this.backTrack(nums, 0);
        return this.ans;
    }
}

子集II

题目详细:LeetCode.90

这道题与上一题的区别在于:整数数组nums可能包含重复的元素,依旧是要求返回不重复的子集集合。

那么其实就是在上一题的基础上,在树形结构上进行剪枝,达到在循环和递归过程中进行去重的目的,这样类似的题目在之前也有做过:【Day27】第七章|回溯算法|39. 组合总和|40.组合总和II

是首先画图辅助理解:
在这里插入图片描述
通过之前的练习,我知道在递归结束时再进行去重操作中,在这一题同样会出现超出时间限制的情况,所以在这里就不重蹈覆辙了,所以难点就时如何在循环和递归的过程中,就对结果进行去重。

通过画图我们可以发现,去重操作或者说如何去判断是否会出现重复的结果,都是在树的同一层中进行处理的:

  • 在循环过程中,当我们依次访问数组中的元素时,如果前一个元素与当前元素相等,那么我们再对当前元素进行递归操作的话,就有可能出现与前一个元素同样的子集
  • 为什么说是有可能呢,而不是一定呢,因为可能还存在着如图中 [1, 2, 2] 这样带有重复元素的子集,所以为了避免丢失了部分子集,我们需要提前对数组进行排序,使其相同大小的数字都是连续的
  • 同时也说明了去重操作需要在树的宽度访问中进行,而不是在树的深度访问中进行,也就是在进入递归前就进行去重判断
  • 在这一道题中,我们可以利用一个布尔数组used来辅助去重
    • nums[i] == nums[i - 1] && used[i] == true 时,说明虽然前一个数字已被访问,但是还未被回溯,进一步说明在树形结构中,当前路径还未到达叶子节点,在路径上还可能存在其他子集结果,需要继续递归到下一层
    • nums[i] == nums[i - 1] && used[i] == false 时,说明虽然前一个数字与当前数字相同,但是由于递归是深度优先遍历,所以前一个数字之所以是 used[i - 1] == false ,是因为它已经被回溯了,其所有路径上的子集都已添加进结果集,如果再对当前数字继续递归的话,则会出现与前一个数字重复的子集,所以不需要在对当前数字进行递归,直接进入下一层循环

Java解法(递归,回溯,哈希):

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();

    public void backTrack(int[] nums, boolean[] used, int startIndex){
        if(nums.length == startIndex){
            return;
        }

        for(int i = startIndex; i < nums.length; i++){
            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            path.offer(nums[i]);
            used[i] = !used[i];
            ans.add(new ArrayList<Integer>(path));
            backTrack(nums, used, i + 1);
            path.removeLast();
            used[i] = !used[i];
        }
    }

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        this.ans.add(new ArrayList<Integer>());
        this.backTrack(nums, used, 0);
        return this.ans;
    }
}

第一次接触到回溯,这两天做题总是停留在一知半解的地步,懵懵懂懂的,本来想要囫囵吞枣,得过且过算了,没想到最后两道练习题我突然醒悟,遇见回溯题就应该先将其转换为树形结构,通过画图来辅助理解整个循环、递归与回溯的过程,这入门的过程是如此的艰难,但是思路打开之后,一切都变得那么的通透了,真叫人:

初极狭,才通人,步行数十步,豁然开朗。

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

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

相关文章

Kafka基本概念

什么是Kafka Kafka是一个消息系统。它可以集中收集生产者的消息&#xff0c;并由消费者按需获取。在Kafka中&#xff0c;也将消息称为日志(log)。 一个系统&#xff0c;若仅有一类或者少量的消息&#xff0c;可直接进行发送和接收。 随着业务量日益复杂&#xff0c;消息的种类…

2.单例模式

基本概念 单例模式&#xff1a;保证一个类只有一个实例&#xff0c;并提供一个访问该实例的全局访问点 常见应用场景 读取配置文件的类一般设计为单例模式网站计数器应用程序的日志应用&#xff0c;因为共享日志文件一直处于打开状态&#xff0c;只能有一个实例去操作Spring…

新C++(11):unordered_map\set的封装

"假如可以让音乐停下来"一、unordered_map\unordered_set简介在C98中&#xff0c;STL底层提供了以红黑树封装的关联式容器map\set&#xff0c;其查询效率可以达到LogN(以2为底)。而在C11中&#xff0c;STL又提供了unordered(无序)容器&#xff0c;其使用方式与map\se…

企业对不同形态CRM系统价格需求不同

很多企业在选型时关心CRM客户管理系统的价格&#xff0c;有人对CRM的价格完全没有概念&#xff0c;也有的人先问价格再看其他。CRM价格在系统选型中到底有多重要&#xff1f;不同类型CRM系统的价格是否有所不同&#xff1f; CRM的不同产品形态也会影响价格 通常情况下&#x…

十五、MyBatis使用PageHelper

1.limit分页 limit分页原理 mysql的limit后面两个数字&#xff1a; 第一个数字&#xff1a;startIndex&#xff08;起始下标。下标从0开始。&#xff09; 第二个数字&#xff1a;pageSize&#xff08;每页显示的记录条数&#xff09; 假设已知页码pageNum&#xff0c;还有每页…

移动端笔记

目录 一、移动端基础 二、视口 三、二倍图/多倍图 四、移动端开发 &#xff08;一&#xff09;开发选择 &#xff08;二&#xff09;常见布局 &#xff08;三&#xff09;移动端技术解决方案 五、移动WEB开发之flex布局 六、移动WEB开发之rem适配布局 #END&#xff08…

嘀嗒出行再闯IPO:千军万马我无懈

羽扇纶巾笑谈间&#xff0c;千军万马我无懈。 在激烈竞争中再度冲刺港交所IPO的嘀嗒出行&#xff0c;闪露出一丝歌词里的气魄。交通运输部下属网约车监管信息交互系统的数据显示&#xff0c;截至2023年1月31日&#xff0c;全国共有300家网约车平台公司取得网约车平台经营许可。…

如何使用COM-Hunter检测持久化COM劫持漏洞

关于COM-Hunter COM- Hunter是一款针对持久化COM劫持漏洞的安全检测工具&#xff0c;该工具基于C#语言开发&#xff0c;可以帮助广大研究人员通过持久化COM劫持技术来检测目标应用程序的安全性。 关于COM劫持 微软在Windows 3.11中引入了(Component Object Model, COM)&…

2月第4周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年2月20日-2月26日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

基频估计算法简介

基频估计算法 F0 estimate methods 估计F0的方法可以分为三类:基于时域、基于频域、或混合方法。本文详细介绍了这些方法。 所有的算法都包含如下三个主要步骤&#xff1a; 1.预处理&#xff1a;滤波&#xff0c;加窗分帧等 2.搜寻&#xff1a;可能的基频值F0&#xff08;候选…

chatgpt到底颠覆了什么 第二部分

以第二个理由就是两个字&#xff0c;垄断。 现在谈到范式转变&#xff0c;如果首先谈的还是算法&#xff0c;那说明还没有透彻理解范式改变范式改变&#xff0c;首先要改的是什么。是什么&#xff1f;是参赛资格。 过去我相信大企业大团队聚拢了许多聪明的脑袋&#xff0c;但我…

基于神经网络补偿的主动悬架自适应控制

目录 前言 1. 1/4悬架模型 2.仿真分析 2.1仿真模型 2.2仿真结果 2.1 形① 2.2 形② 3. 总结 前言 上两篇博客我们介绍了神经网络补偿控制律的仿真测试&#xff0c;从仿真结果我们可以得知神经网络具有逼近扰动&#xff0c;并将其补偿的作用。 上两篇文章链接&#xf…

在nestjs中进行typeorm cli迁移(migration)的配置

在nestjs中进行typeorm cli迁移(migration)的配置 在学习nestjs过程中发现typeorm的迁移配置十分麻烦,似乎许多方法都是旧版本的配置&#xff0c;无法直接使用. 花了挺长时间总算解决了这个配置问题. db.config.ts 先创建db.config.ts, 该文件export了两个对象&#xff0c;其…

AcWing算法提高课-3.1.2信使

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 题目传送门点这里 题目描述 战争时期&#xff0c;前线有 nnn 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;…

CPU 偏高,和linux常用命令

CPU 偏高&#xff0c;和linux常用命令一、1、常用命令2、top 查看整体的cpu占有率&#xff08;哪个进程cpu占用过高&#xff09;3、top -Hp 8779 查看该pid 下哪个进程占用过高4、打印dump日志 重点关注&#xff08;RUNNABLE、BLOCKED&#xff09;一、 1、常用命令 整机 top …

k8s ConfigMap 中 subPath 字段和 items 字段

Kubernetes中什么是subPath 有时&#xff0c;在单个 Pod 中共享卷以供多方使用是很有用的。volumeMounts.subPath 属性可用于指定所引用的卷内的子路径&#xff0c;而不是其根路径。 这句话理解了&#xff0c;基本就懂subPath怎么用了&#xff0c;比如我们要替换nginx.cnf, 挂…

map和set的使用

文章目录关联式容器树形结构的关联式容器setinsert增减erase删除multiset修改mappair<key,value>insertoperator[] 的引入insert和operator[]的区别multimap小结map的使用统计最喜欢吃的前几种水果前K个高频单词&#xff0c;返回单词的频率由高到低,频率相同时&#xff0…

Isaac-gym(9):项目更新、benchmarks框架梳理

一、项目更新 近期重新git clone isaac gym的强化部分&#xff08;具体见系列第5篇&#xff09;时发现官方的github库有跟新&#xff0c;git clone下来后发现多了若干个task&#xff0c;在环境配置上也有一定区别。 例如新旧两版工程项目的setup.py区别如下&#xff1a; git …

现在的00后太强了,几个问题差点给我问懵了

前言 我们公司刚入职一个00后小伙&#xff0c;今天在办公室交流了一下&#xff0c;他问我会不会自动化测试&#xff0c;我说懂一点&#xff0c;然后直接问了我几个自动化测试问题&#xff0c;差点直接给我问懵了&#xff01; 问题如下&#xff1a; 我们在制定自动化测试实施…

计算机组成原理4小时速成5:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线

计算机组成原理4小时速成5&#xff1a;输入输出系统&#xff0c;io设备与cpu的链接方式&#xff0c;控制方式&#xff0c;io设备&#xff0c;io接口&#xff0c;并行串行总线 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c…