代码随想录算法训练营DAY28|C++回溯算法Part.4|93.复原IP地址、78.子集、90.子集II

文章目录

  • 93.复原IP地址
    • 思路
      • 确定非法的范围
      • 树形结构
    • 伪代码
  • 78.子集
    • 思路
    • 伪代码实现
    • CPP代码
  • 90.子集II
    • 思路
    • CPP代码
      • 用used去重的办法
      • 用set去重的版本
      • 不使用used数组、set的版本

93.复原IP地址

力扣题目链接

文章讲解:93.复原IP地址

视频讲解:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址

状态:一种十分典型的分割问题,根据IP地址的特征从分割一个字符、两个字符、三个字符推进,但是其中肯定是要进行剪枝的。代码的实现应该是什么样的呢?

思路

确定非法的范围

  1. 字符中出现非法字符串;
  2. 数字前面不允许有0:“1.013.1.1”;
  3. 不允许大于255。

这种分割问题,我们一定要用暴力枚举每一种分割的情况。

树形结构

树形结构中仍然还有许多细节需要注意:切割线如何固定在那里?然后剩余的切割线还能到后面继续切割?

如何去表达切割子串呢?

在这里插入图片描述

伪代码

  • 递归的返回值和参数:

    • 首先我们必须要有一个全局变量来记录结果vector<string> result
    • 其次,我们需要定义指示分割线的变量和我们添加逗点的数量
    • pointNum:我们的结果一定是要有逗点的,所以树的深度只有三层(不算根)
    vector<string> result;
    void backtracking(string& s, int startIndex, int pointNum){
    }
    
  • 递归终止条件:本题题目明确要求只分四段,也就如上文所说,3个pointNum就说明字符串分成四段了,我们检查一下第四段是否合法,就可以放入到结果集中了。

if (pointNum == 3){	//逗点数量为3时,分割结束
  //判断第四段子字符串是否合法,如果合法就放入result中
  if (isValid(s, startIndex, s.size() - 1)){
    result.push_back(s);
  }
  return ;
} 
  • 单层搜索逻辑

    • 本题中最重要的逻辑我觉得是没分割一次,检查一次子串合法性,如何加上逗点,不合法直接剪掉分支

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

      • 递归调用时,下一层递归的startIndex从i + 2开始,因为我们在字符串里加了逗点。同时记录分割符的数量pointNum + 1
      • 回溯时,记得删除逗点,然后pointNum也要-1
      for (int i = startIndex; i < s.size(); i++){
        if (isValid(s, startIndex, i)){
          s.insert(s.begin() + i + 1, '.'); //在i的后面插入一个逗点
          pointNum++;
          backtracking(s, i+ 2, pointNum);
          pointNum--;
          s.erase(s.begin() + i + 1);				// 回溯删掉逗点
        }else break;
      }
      
  • 判断子串是否合法

    • 段位以0开头的数字不合法
    • 段位里有非正整数字符不合法
    • 段位如果大于255不合法
    bool isValid(const string& s, int start, int end){
      if (start > end){
        return false;
      }
      if (s[start] == '0' && start != end) //0开头的数字不合法
        return false;
      int num = 0;
      for (int i = start; i <= end; i++){
        if (s[i] > '9' || s[i] < '0')	return false;//遇到非法数字
          
        num = num * 10 + (s[i] - '0');
        if (num > 255)	return false;	//如果大于255
          
      }
      return true;
    }
    

78.子集

力扣题目链接

文章讲解:78.子集问题

视频讲解:回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集

状态:第一次碰到子集 问题毫无思路,但是看完卡哥的树形结构和讲解,真的是豁然开朗

思路

啥也不说,先上树形结构图
在这里插入图片描述

从图中可以很明显得看出:组合问题和切割问题都是收集树的叶子结点,而自己问题是找树的所有结点

但是究其本质,子集问题仍然是一个组合问题,因为它的集合是无序的,子集{1, 2}{2, 1}是一样的。

所以既然是无序,取过的元素不会重复取,那么我们写回溯算法,for循环仍然要从startIndex开始而不是从0开始(后序的排列问题我们for就得从0开始啦,因为{1, 2}和{2, 1}是两个集合)

伪代码实现

  • 递归函数参数

    • 全局变量path和result
    • startIndex
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
    }
    
  • 递归终止条件:从树形结构可以看出,剩余集合为空的时候,我们的递归就结束了。startIndex已经大于数组的长度了,就终止了,因为没有元素可取了

if (startIndex >= nums.size()) {
    return;
}
  • 单层递归逻辑:子集问题不需要任何剪枝,因为子集就是要遍历整颗树!
for (int i = startIndex; i < nums.size(); i++) {
    path.push_back(nums[i]);    // 子集收集元素
    backtracking(nums, i + 1);  // 注意从i+1开始,元素不重复取
    path.pop_back();            // 回溯
}

CPP代码

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
        if (startIndex >= nums.size()) { // 终止条件可以不加
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

90.子集II

力扣题目链接

文章讲解:90.子集II

视频讲解:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II

状态:这个问题就是一个:去重。在之前的40.组合总和II中我们已经讨论过回溯算法中的去重问题。

思路

在这里插入图片描述

去重思路和40.组合总和II一致,这里直接给出代码

CPP代码

用used去重的办法

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
        result.push_back(path);
        for (int i = startIndex; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0, used);
        return result;
    }
};

用set去重的版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++) {
            if (uset.find(nums[i]) != uset.end()) {
                continue;
            }
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0);
        return result;
    }
};

不使用used数组、set的版本

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

如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        for (int i = startIndex; i < nums.size(); i++) {
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
                continue;
            }
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0);
        return result;
    }
};

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

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

相关文章

curlftpfs和fusermount

curlftpfs 是一种 Linux 系统下用来将 FTP 服务器挂载为文件系统的工具&#xff0c;这意味着可以通过本地目录来访问和操作 FTP 服务器上的文件。 挂载FTP服务器到本地系统 为了挂载FTP服务器到本地系统中&#xff0c;使用curlftpfs工具&#xff0c;可以按照以下格式书写命令…

如何通过MSTSC连接Ubuntu的远程桌面?

正文共&#xff1a;666 字 12 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面我们介绍了如何通过VNC连接Ubuntu 18.04的远程桌面&#xff08;Ubuntu 18.04开启远程桌面连接&#xff09;&#xff0c;非常简单。但是有小伙伴咨询如何使用微软的远程桌面连接MSTSC&#xff08…

黑灰产行业简介

参考&#xff1a;2021年黑灰产行业研究及趋势洞察报告 1. 有哪些场景面临大量黑灰产攻击&#xff1f; 1.营销活动场景 -- 该场景最为猖獗 1. 抹机及接码注册&#xff1a;黑灰产会使用抹机工具修改设备参数伪装成一台新设备&#xff0c;再配合联系卡商进行手机号接码&#xf…

项目7-音乐播放器3(删除模块+播放音乐模块设计)

1.播放音乐模块设计 1.1 请求响应设计 请求&#xff1a; { get, /music/get?pathxxx.mp3 } 响应&#xff1a; { 音乐数据本身的字节信息 } 1.2 后端代码 1. Files.readAllBytes(String path) : 读取文件中的所有字节&#xff0c;读入内存 &#xff…

java/C#语言开发的医疗信息系统10套源码

java/C#语言开发的医疗信息系统10套源码 云HIS系统源码&#xff0c;云LIS系统源码&#xff0c;PEIS体检系统&#xff0c;手麻系统 源 码&#xff0c;PACS系统源码&#xff0c;微源预约挂号源码&#xff0c;医院绩效考核源码&#xff0c;3D智能导诊系统源码&#xff0c;ADR药物…

UE5(基础动作)多人游戏制作蹲伏

1.创建输入操作&#xff0c;IA_Crouch 在输入映射中添加 IA_Crouch,在触发器中创建两个索引&#xff0c;已按下已松开来创建蹲伏输入。 蹲伏操作必须要勾选角色-角色移动-crouch勾选可蹲伏否则你的人物无法真正蹲下。 为蹲伏创建函数&#xff0c;创建布尔来判断是否蹲伏。 通过…

链表经典算法OJ题目

1.单链表相关经典算OJ题目1&#xff1a;移除链表元素 思路一 直接在原链表里删除val元素&#xff0c;然后让val前一个结点和后一个节点连接起来。 这时我们就需要3个指针来遍历链表&#xff1a; pcur —— 判断节点的val值是否于给定删除的val值相等 prev ——保存pcur的前…

LCR 023. 相交链表

给定两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&#xf…

大话设计模式-装饰器模式

大话设计模式书中&#xff0c;作者举了一个穿衣服的例子来为我们引入装饰器模式。 概念 定义 装饰模式在书中的定义是&#xff1a;动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰模式比生成子类更灵活。 这句话直接去理解可能会有点抽象&#…

javase__进阶 day13stream流和方法引用

1.不可变集合 1.1 什么是不可变集合 ​ 是一个长度不可变&#xff0c;内容也无法修改的集合 1.2 使用场景 ​ 如果某个数据不能被修改&#xff0c;把它防御性地拷贝到不可变集合中是个很好的实践。 ​ 当集合对象被不可信的库调用时&#xff0c;不可变形式是安全的。 简单…

java:Java中的抽象类

什么是抽象类&#xff1a; 我们知道&#xff0c;类用来模拟现实的事物&#xff0c;一个类模拟一类事物&#xff0c;某个类的一个实例化对象可以模拟某个属于该类的具体事物。类中描绘了该类所有对象的共同的特性&#xff0c;当一个类中给出的信息足够全面时候&#xff0c;我们就…

如何从零开始创建React应用:简易指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【大模型应用极简开发入门(1)】LLM概述:LLM在AI中所处位置、NLP技术的演变、Transformer与GPT、以及GPT模型文本生成逻辑

文章目录 一. AI中大语言模型的位置与技术发展1. 从AI到Transformer2. NLP&#xff1a;自然语言处理3. LLM大型语言模型&#xff1a;NLP的一种特定技术3.1. LLM定义3.2. LLM的技术发展3.2.1. n-gram模型3.2.2. RNN与LSTM 二. Transformer在LLM中脱颖而出1. Transformer架构能力…

【Linux】详解进程通信中信号量的本质同步和互斥的概念临界资源和临界区的概念

一、同步和互斥的概念 1.1、同步 访问资源在安全的前提下&#xff0c;具有一定的顺序性&#xff0c;就叫做同步。在多道程序系统中&#xff0c;由于资源有限&#xff0c;进程或线程之间可能产生冲突。同步机制就是为了解决这些冲突&#xff0c;保证进程或线程之间能够按照既定…

sketchup创建3D打印机的模型

查了一下&#xff0c;这玩意有几个版本&#xff0c;其中一个sketchup free是免费的&#xff0c;到官网上看看 下载 SketchUp | 免费试用 3D 建模软件 | SketchUp 是个在线网页版&#xff0c;然后可以再这个网站上注册一个账号 弄个邮箱试试看 创建好进入后&#xff0c;里面就…

解锁多智能体路径规划新境界:结合启发式搜索提升ML本地策略

引言&#xff1a;多智能体路径寻找&#xff08;MAPF&#xff09;问题的重要性与挑战 在现代自动化和机器人技术迅速发展的背景下&#xff0c;多智能体路径寻找&#xff08;Multi-agent path finding&#xff0c;简称MAPF&#xff09;问题的研究变得日益重要。MAPF问题涉及为一…

《QT实用小工具·二十七》各种炫酷的样式表

1、概述 源码放在文章末尾 该项目实现了各种炫酷的样式表&#xff0c;如单选、多选、按钮、日历、表格、下拉框、滚轮等&#xff0c;下面是项目demo演示&#xff1a; 项目部分代码如下&#xff1a; #include "frmmain.h" #include "ui_frmmain.h" #inc…

【C++】飞机大战项目记录

源代码与图片参考自《你好编程》的飞机大战项目&#xff0c;这里不进行展示。 本项目是仅供学习使用的项目 飞机大战项目记录 飞机大战设计报告1 项目框架分析1.1 敌机设计&#xff1a;1.2 玩家飞机控制&#xff1a;1.3 子弹发射&#xff1a;1.4 游戏界面与互动&#xff1a;1.5…

高精度算法(2)

前言 延续上次所讲的内容再对乘法和除法进行说明&#xff0c;希望有所帮助 注意这里的乘除法都是针对于整数如果要是涉及到小数&#xff0c;我们得使用二分法 通过二分同样可以解决小数精度问题 高精度乘法 思路 我们只能用字符串来读取一个很大很大的数&#xff0c;所以…

一文讲透彻Redis 持久化

文章目录 ⛄1.RDB持久化&#x1fa82;&#x1fa82;1.1.执行时机&#x1fa82;&#x1fa82;1.2.RDB原理&#x1fa82;&#x1fa82;1.3.小结 ⛄2.AOF持久化&#x1fa82;&#x1fa82;2.1.AOF原理&#x1fa82;&#x1fa82;2.2.AOF配置&#x1fa82;&#x1fa82;2.3.AOF文件…