【算法训练-回溯算法 三】【回溯算法最佳实践】括号生成、复原IP地址

news2024/11/19 22:51:46

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【回溯算法】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
在这里插入图片描述
在这里插入图片描述

明确目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍

括号生成

终于轮到解这两道高频题了

题干

在这里插入图片描述

解题思路

原解题思路括号问题可以简单分成两类,一类是括号的合法性判断 ,一类是合法括号的生成。对于括号合法性的判断,主要是借助「栈」这种数据结构,而对于括号的生成,一般都要利用回溯递归的思想

有关括号问题,你只要记住以下性质,思路就很容易想出来:

  1. 一个「合法」括号组合的左括号数量一定等于右括号数量,这个很好理解。
  2. 对于一个「合法」的括号字符串组合 p,必然对于任何 0 <= i < len(p) 都有:子串 p[0..i]左括号的数量都大于或等于右括号的数量

如果不跟你说这个性质,可能不太容易发现,但是稍微想一下,其实很容易理解,因为从左往右算的话,肯定是左括号多嘛,到最后左右括号数量相等,说明这个括号组合是合法的

算法输入一个整数 n,让你计算 n 对儿括号能组成几种合法的括号组合,可以改写成如下问题:

现在有 2n 个位置,每个位置可以放置字符 ( 或者 ),组成的所有括号组合中,有多少个是合法的?

这个命题和题目的意思完全是一样的对吧,那么我们先想想如何得到全部 2^(2n) 种组合,然后再根据我们刚才总结出的合法括号组合的性质筛选出合法的组合,不就完事儿了?那么对于我们的需求,如何打印所有括号组合呢,伪代码如下

void backtrack(int n, int i, string& track) {
    // i 代表当前的位置,共 2n 个位置
    // 穷举到最后一个位置了,得到一个长度为 2n 组合
    if (i == 2 * n) {
        print(track);
        return;
    }

    // 对于每个位置可以是左括号或者右括号两种选择
    for choice in ['(', ')'] {
        track.push(choice); // 做选择
        // 穷举下一个位置
        backtrack(n, i + 1, track);
        track.pop(choice); // 撤销选择
    }
}

那么,现在能够打印所有括号组合了,如何从它们中筛选出合法的括号组合呢?很简单,加几个条件进行「剪枝」就行了。

对于 2n 个位置,必然有 n 个左括号,n 个右括号,所以我们不是简单的记录穷举位置 i,而是用 left 记录还可以使用多少个左括号,用 right 记录还可以使用多少个右括号,这样就可以通过刚才总结的合法括号规律进行筛选了

在这里插入图片描述

代码实现

给出代码实现基本档案

基本数据结构数组
辅助数据结构
算法回溯算法
技巧

import java.util.*;


public class Solution {

    // 定义结果集和路径
    private ArrayList<String> result = new ArrayList<String>();
    StringBuilder path = new StringBuilder();
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param n int整型
     * @return string字符串ArrayList
     */
    public ArrayList<String> generateParenthesis (int n) {
        if (n < 0) {
            return new  ArrayList<String>();
        }
        // 左右括号剩余数当做入参传入
        backTrack( n,  n);
        return result;
    }

    private void backTrack(int leftRemaind, int rightRemaind) {

        // 1 超量使用括号不合法,则剪枝
        if (leftRemaind < 0 || rightRemaind < 0) {
            return;
        }

        // 2 如果左括号剩余数量大于右括号,则认为右括号在前面过多使用,无法闭合,剪枝
        if (leftRemaind > rightRemaind) {
            return;
        }

        // 3 如果左右括号同时消耗完,证明到了叶子节点,结算
        if (leftRemaind == 0 && rightRemaind == 0) {
            result.add(path.toString());
            return;
        }

        // 4 左边括号回溯
        path.append('(');
        backTrack(leftRemaind - 1, rightRemaind);
        path.deleteCharAt(path.length() - 1);

        // 5 右边括号回溯
        path.append(')');
        backTrack(leftRemaind, rightRemaind - 1);
        path.deleteCharAt(path.length() - 1);

    }
}

复杂度分析

下面是对给定代码的时间和空间复杂度分析:

时间复杂度:

  • 生成所有可能的括号组合需要进行回溯操作,因此时间复杂度取决于生成的括号字符串的数量。
  • 最坏情况下,可以生成的括号字符串数量为卡特兰数的第 n 项,约为 O(4^n / (n^(3/2))),这是因为每个位置都有两种选择,左括号或右括号,总共 2^(2n) 种组合。
  • 因此,时间复杂度为 O(4^n / (n^(3/2)))

空间复杂度:

  • 空间复杂度主要取决于递归调用的栈空间和存储结果的数据结构。
  • 在递归调用中,栈的最大深度不会超过 n,因为每一步都减少一个左括号或右括号,最多递归 n 层。因此,栈空间的空间复杂度为 O(n)。
  • 存储结果的 result 列表和 path 字符串的长度也会受到影响,但在最坏情况下,result 中的字符串数量为卡特兰数的第 n 项,所以结果列表的空间复杂度也可以视为 O(4^n / (n^(3/2)))。path 字符串的最大长度为 2n,所以它的空间复杂度是 O(n)。
  • 因此,总的空间复杂度是 O(n) + O(4^n / (n^(3/2)))。

需要注意的是,在实际应用中,生成括号字符串的数量通常较小,因此代码的实际运行时间和空间占用可能会更加可控。

对于递归相关的算法,时间复杂度这样计算(递归次数)*(递归函数本身的时间复杂度)

复原IP地址【MID】

元素无重不可复选,且不可重排序【子集组合框架】再来一道复原IP地址

题干

在这里插入图片描述

解题思路

原解题地址回溯算法事实上就是在一个树形问题上做深度优先遍历,因此 首先需要把问题转换为树形问题

在这里插入图片描述

在画树形图的过程中,你一定会发现有些枝叶是没有必要的,把没有必要的枝叶剪去的操作就是剪枝,在代码中一般通过 break 或者 contine 和 return (表示递归终止)实现。分析剪枝条件:

  1. 一开始,字符串的长度小于 4 或者大于 12 ,一定不能拼凑出合法的 ip 地址(这一点可以一般化到中间结点的判断中,以产生剪枝行为);
  2. 每一个结点可以选择截取的方法只有 3 种:截 1 位、截 2 位、截 3 位,因此每一个结点可以生长出的分支最多只有 3 条分支,根据截取出来的字符串判断是否是合理的 ip 段,这里写法比较多,可以先截取,再转换成 int ,再判断。
  3. 由于 ip 段最多就 4 个段,因此这棵三叉树最多 4 层,这个条件作为递归终止条件之一;
  4. 每一个结点表示了求解这个问题的不同阶段,需要的状态变量有:splitTimes:已经分割出多少个 ip 段begin:截取 ip 段的起始位置path:记录从根结点到叶子结点的一个路径(回溯算法常规变量,是一个栈);res:记录结果集的变量,常规变量

接下来套用回溯模版来写一下

代码实现

给出代码实现基本档案

基本数据结构数组
辅助数据结构
算法回溯算法
技巧


import java.util.*;


public class Solution {

    // 1 定义结果集和路径
    private List<String> result = new ArrayList<String>();
    private List<String> path = new ArrayList<>();
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param n int整型 the n
     * @return int整型
     */
    public List<String> restoreIpAddresses(String s) {
        // 1 字符串合法性判断
        if (s.length() == 0 || s.length() < 4 || s.length() > 12) {
            return new ArrayList<String>();
        }
        backTrack(s, 0);
        return result;
    }

    // 构造的是子集树
    private void backTrack(String s, int start) {

        // 剪枝1:暴力遍历结束,指针溢出了
        if (s.length() < start) {
            return;
        }

        // 剪枝2:可用字符太多
        if (s.length() - start > (4 - path.size()) * 3) {
            return;
        }

        // 剪枝3:可用字符太少
        if (s.length() - start < 4 - path.size()) {
            return;
        }

        // 叶子节点,路径添加
        if (path.size() == 4) {
            result.add(String.join(".", path));
            return;
        }

        // 选择列表
        for (int i = start; i < s.length() && i < start + 3; i++) {
            // 1 截取子串,从当前位置出发,横向的3种可能性
            String subStr = s.substring(start, i + 1);

            // 2 如果子串不满足条件,则不添加到路径上
            if (!isValidSubIpAddress(subStr)) {
                continue;
            }

            // 3 前进与回溯,子集问题,前面用过的重复可能不再使用,所以start为i+1
            path.add(subStr);
            backTrack(s, i + 1);
            path.remove(path.size() - 1);
        }

    }

    // 判断一段字符串是否为合法IP段
    private boolean isValidSubIpAddress(String subStr) {
        // 1 如果字符串截取的长度不满足则为false
        int subIpAddressLen = subStr.length();
        if (subIpAddressLen < 1 || subIpAddressLen > 3 ) {
            return false;

        }
        // 2 如果长度大于1位,则第1位不能为0,也就是不能有前导0
        if (subIpAddressLen > 1 && subStr.charAt(0) == '0') {
            return false;
        }

        // 3 值验证,值必须大于等于0且小于等于255
        int sum = 0;
        for (int i = 0; i < subStr.length(); i++) {
            sum = sum * 10 +  subStr.charAt(i) - '0';
            if (sum > 255) {
                return false;
            }
        }
        return true;
    }
}

这里的backTrack(s, i + 1);与子集问题有异曲同工之妙,都是不可复选的意思。这里表示字符串下一层只能往后选,子集里的则表示用过的元素不能再被使用

1 剪枝分析:剩余字符太少

if (s.length() - begin < (4 - path.size())) return;

让我们通过一个示例来说明代码中的这部分剪枝逻辑。

假设输入字符串 s 为 “25525511135”,我们希望生成合法的 IP 地址。在这个示例中,s 的长度为 11 个字符。

  • 初始时,begin 为 0,path 为空,即 path.size() 为 0。
  • 我们需要生成 4 个IP地址段。
  • 假设我们已经生成了 path 中的部分IP地址段。

在这个时刻,剪枝逻辑 if (s.length() - begin < (4 - path.size())) return; 发挥作用。我们可以通过具体的示例来解释:

  • path.size() 是已经生成的 IP 地址段的数量,假设为 k
  • (4 - path.size()) 表示剩下需要生成的IP地址段数量,即 4 - k
  • s.length() - begin 表示从当前 begin 位置开始的剩余字符串的长度。

剪枝条件的含义是,如果剩余的字符串长度不足以填充需要生成的剩余IP地址段,那就没有必要继续搜索,因为在这种情况下,无法生成合法的IP地址。

对于示例 “25525511135”:

  • begin 为 0 时,已经生成 0 个IP地址段,需要生成 4 个,因此 (4 - path.size()) 为 4。
  • 剩余的字符串长度为 11(整个输入字符串的长度) - 0(当前位置 begin) = 11。
  • 因此,s.length() - begin 大于 (4 - path.size()),即 11 > 4,这意味着可以继续搜索下一个IP地址段。

这个剪枝条件的目的是在每个搜索步骤中,确保剩余的字符串足够填充所有需要生成的IP地址段。如果不满足这个条件,就没有必要继续搜索,因为无法生成合法的IP地址。

2 剪枝分析:剩余字符太多

if (s.length() - begin > (4 - path.size()) * 3) return;

这行代码是用于进一步剪枝的逻辑。它的目的是确保剩余的字符串长度不会过长,以至于无法生成合法的 IP 地址。让我通过一个示例来说明这部分剪枝逻辑:

假设输入字符串 s 为 “25525511135”,我们依然希望生成合法的 IP 地址。

  • 初始时,begin 为 0,path 为空,即 path.size() 为 0。
  • 我们需要生成 4 个 IP 地址段。

在这个示例中,我们将关注这部分剪枝逻辑 if (s.length() - begin > (4 - path.size()) * 3) return;。我们可以通过具体的示例来解释:

  • path.size() 是已经生成的 IP 地址段的数量,假设为 k
  • (4 - path.size()) 表示剩下需要生成的 IP 地址段数量,即 4 - k
  • s.length() - begin 表示从当前 begin 位置开始的剩余字符串的长度。

剪枝条件的含义是,如果剩余的字符串长度超过了填充需要生成的剩余 IP 地址段所需的最大字符数,那就没有必要继续搜索,因为在这种情况下,也无法生成合法的 IP 地址。

对于示例 “25525511135”:

  • begin 为 0 时,已经生成 0 个 IP 地址段,需要生成 4 个,因此 (4 - path.size()) 为 4。
  • 剩余的字符串长度为 11(整个输入字符串的长度) - 0(当前位置 begin) = 11。

剪枝条件 if (s.length() - begin > (4 - path.size()) * 3) 表示如果剩余字符串长度大于 (4 - path.size()) * 3,就没有必要继续搜索。因为每个 IP 地址段最多有 3 个字符,所以 (4 - path.size()) * 3 表示需要填充剩余 IP 地址段所需的最大字符数。如果剩余字符串长度超过这个最大字符数,那么就不可能生成合法的 IP 地址。

在这个示例中,11 < 4 * 3 成立,所以搜索会继续。但如果 s 长度更长,例如 “255255111356789”,那么 s.length() - begin 将大于 (4 - path.size()) * 3,并且搜索会被剪枝,因为不可能生成合法的 IP 地址。

复杂度分析

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

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

相关文章

由Django-Session配置引发的反序列化安全问题

漏洞成因 漏洞成因位于目标配置文件settings.py下 关于这两个配置项 SESSION_ENGINE&#xff1a; 在Django中&#xff0c;SESSION_ENGINE 是一个设置项&#xff0c;用于指定用于存储和处理会话&#xff08;session&#xff09;数据的引擎。 SESSION_ENGINE 设置项允许您选择不…

MSQL系列(六) Mysql实战-SQL语句优化

Mysql实战-SQL语句优化 前面我们讲解了索引的存储结构&#xff0c;BTree的索引结构&#xff0c;以及索引最左侧匹配原则&#xff0c;Explain的用法&#xff0c;可以看到是否使用了索引&#xff0c;今天我们讲解一下SQL语句的优化及如何优化 文章目录 Mysql实战-SQL语句优化1.…

统信uos 1030 企业版 安装.net core环境

安装.net core步骤 添加密钥和包存储库 安装 .NET 之前&#xff0c;请运行以下命令&#xff0c;将 Microsoft 包签名密钥添加到受信任密钥列表&#xff0c;并添加包存储库wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-mic…

nuxt使用i18n进行中英文切换

中文效果图&#xff1a; 英文效果图&#xff1a; 版本&#xff1a; 安装&#xff1a; npm install vue-i18n8.27.0 --savenpm i nuxtjs/i18n # npm 新建en.js与zh.js两个文件进行切换显示 en.js内容 import globals from ./../js/global_valexport default {/******* 公共内…

什么是软件测试? 软件测试都有什么岗位 ?软件测试和调试的区别? 软件测试和开发的区别?软件测试等相关概念入门篇

1、什么是软件测试&#xff1f; 常见理解&#xff1a; 软件测试就是找BUG&#xff0c;发现缺陷 真正理解&#xff1a; 软件测试就是验证软件产品特性是否满足用户的需求 测试定义&#xff1a; 测试人员验证软件是否符合需求的这个过程就是测试 2、为什么要有测试 标准情况下&a…

ShareMouse for Mac(多台电脑鼠标键盘共享软件)

ShareMouse mac版是一款Mac平台上可以在多台电脑间共享鼠标的工具软件&#xff0c;sharemousefor Mac支持 Windows 与 Mac&#xff0c;并可以在不同电脑间共享剪贴板。只需要移动鼠标指针的到想控制的显示器那里去、鼠标光标就会神奇地“跨越”到邻近的电脑屏幕上。每个计算机都…

vue中使用coordtransform 互相转换坐标系

官方网站&#xff1a;https://www.npmjs.com/package/coordtransform 在使用高德sdk时&#xff0c;其返回的坐标在地图上显示时有几百米的偏移&#xff0c;这是由于高德用的是 火星坐标&#xff08;GCJ02&#xff09;&#xff0c;而不是wgs84坐标。为了消除偏移&#xff0c;将G…

KubeSphere安装mysql8

需要持久化储存数据的&#xff0c;建立有状态服务。 无状态服务是不会持久化的&#xff0c;重启就归零 KubeSphere 创建自建应用后&#xff0c;创建有状态服务&#xff0c;但是自己应用的有状态服务不能外放端口&#xff0c;需要在服务哪里删除pod&#xff0c;在创建负载指定相…

微信小程序会议OA系统其他页面

前言&#xff1a; 及上一文章&#xff1a;https://blog.csdn.net/djssubddbj/article/details/133895170?spm1001.2014.3001.5501我们所写的会议OA的首页&#xff0c;在这个上面我们继续完成我们的会议OA系统&#xff0c;这是我们的本期所要完成的页面 自定义组件 微信小程序…

基于MATLAB的GPS卫星绕地运行轨迹动态模拟仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 Prn NavData(PRNS_SEL,1);%识别导航数据中的PRNiode NavData(PRNS_SEL,11);%企…

客户端post请求,服务器收到{}数据解决方法

当我们发起登录请求时&#xff0c;后台接收到的为{}数据 原因&#xff1a;传送过去的对象格式不对 解决方案&#xff1a; 引入qs npm install qs 在data中格式化数据 const res await axios({url:http://127.0.0.1:3000/post,method:post,data:Qs.stringify({username:te…

【试题024】C语言强制转型小例题

1.题目&#xff1a;设int a7; float x2.5,y4.7;,则表达式x3%(int)(xy)/4的值是 &#xff1f; 2.代码分析&#xff1a; #include <stdio.h> int main() { //设int a7; float x2.5,y4.7;,则表达式x3%(int)(xy)/4的值是 &#xff1f;int a 7;float x 2.5, y 4.7;printf…

持续集成工具jenkins操作

安装Jenkins 下载jenkins安装包 linux上下载jenkins失败 开始在windows上安装jenkins 1、先安装JDK https://jingyan.baidu.com/article/fdbd4277dd90f0b89e3f489f.html 免安装版本JDK只需要解压配置环境变量即可 2、安装Jenkins 参考文档&#xff1a; https://www.cnb…

Spring framework Day24:定时任务

前言 在我们的日常生活和工作中&#xff0c;时间管理是一项至关重要的技能。随着各种复杂任务的增加和时间压力的不断增加&#xff0c;如何更好地分配和利用时间成为了一项迫切需要解决的问题。在这样的背景下&#xff0c;定时任务成为了一种非常有效的解决方案。 定时任务&a…

在中国,技术到底有多有用?

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言1.…

SpringBoot结合Druid实现SQL监控

1、前言 SpringBoot不用我多介绍了吧&#xff0c;目前后端最流行的框架。后端开发人员最基本的要求。 Druid数据库连接池&#xff0c;出自国内 ”java圣地" 阿里巴巴。 Druid是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统&#xff0c;旨在快速处理大规模…

迅为RK3568开发板RTMP推流之视频监控

1 搭建 RTMP 媒流体服务器 nginx-rtmp 是一个基于 nginx 的 RTMP 服务模块&#xff0c;是一个功能强大的流媒体服务器模块&#xff0c; 它提供了丰富的功能和灵活的配置选项&#xff0c;适用于构建各种规模的流媒体平台和应用。无论是搭建实时视频直播平台、点播系统或多屏互…

【Qt】常见控件

文章目录 按钮组QListWidget列表容器TreeWidget树控件TableWidget 表格控件其它控件介绍下拉框QLabel显示图片和动图 自定义控件封装 按钮组 QPushButton 常用按钮 QToolButton 工具按钮&#xff1a; 用于显示图片 如果想显示文字&#xff1a;修改风格&#xff1a;toolButto…

分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测

分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测 目录 分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于BiGRU-AdaBoos…

Hadoop3教程(二十五):Yarn的多队列调度器使用案例

文章目录 &#xff08;136&#xff09;生产环境多队列创建&好处&#xff08;137&#xff09;容量调度器多队列提交案例如何创建多个队列如何向指定队列提交任务 &#xff08;138&#xff09;容量调度器任务优先级&#xff08;139&#xff09;公平调度器案例参考文献 &#…