算法力扣刷题记录 七十二【93.复原IP地址】

前言

本文练习回溯章节。回溯章节第七篇。

记录 七十二【93.复原IP地址】。


一、题目阅读

有效 IP 地址 正好由四个整数每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

输入:s = "0000"
输出:["0.0.0.0"]

示例 3:

输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

1 <= s.length <= 20
s 仅由数字组成

二、尝试实现

分析题目

  1. 需要从题目分析应该用什么方法。不应该直接以章节来确定方法。
  2. 题目说给一个只包含数字的字符串s,所以无效ip地址中有@符号一类的已经被排除掉。输出结果是该字符串可能表达的ip地址:不让重新排序和删除,相当于在字符串中划分出四部分,插入三个’.'。在一个集合中切割问题,应该用回溯算法解决。

回溯3步曲

  1. 确定递归的返回值:用vector< string>& result作为参数存放结果,所以不需要返回值。void类型。
  2. 确定递归的参数:
  • const string& s表示传入的字符串,用const修饰以防被修改。
  • vector< string>& result用来放最后的返回值,引用形式;
  • string temp用来放中间分割的结果。不用引用了,值传递,传递副本。
  • int startindex表示下一层从哪个字符所在下标的后面开始分割。
  • int& depth表示第几层,因为ip地址是4个整数,所以到depth == 4时就应该判断有没有分割完成
  1. 确定终止条件:如果startindex == s.size()说明已经分割整个字符串,并且判断此时depth == 4说明分成了4个整数。如果depth不是4或者没有分割整个字符串都不满足ip地址
  • if(startindex == s.size() && depth == 4)
  1. 确定单层逻辑:
  • for循环应该从startindex 开始,到s.size()结束。i++.
  • for循环内部:
    • 首先根据 i 和startindex分割出子串str。用substr成员函数。
    • 得到子串后判断这个子串是否在[0,255]并且前导不为0。(这部分逻辑在第5条说)
    • depth++。代表分割的数字多了一个;
    • 把子串str放到temp中。不过需要加’.',所以有个判断:if(depth > 1)说明不是第一个整数,后面的整数加到temp之前先加‘.’。
    • 递到下一层:startindex传入i+1.
    • 回溯:temp.earse掉子串str;如果temp不是空,还要pop,去掉’.';depth–。(3个回溯)
  1. 得到子串后判断这个子串是否在[0,255]并且前导不为0。
  • 首先把子串str转换成int类型。但是发现int类型不够,整数会超出int范围,所以改成long long类型;但是提交后又发现用例还能超出long long类型;所以第一步应该先判断如果str.size() > 3,直接continue。
  • 把子串str用stoi函数转换成int类型;
  • 范围判断:if(num < 0 || num > 255) continue;
  • 前导’0’判断:if(str.size() > 1 && str[0] == ‘0’) continue;
  1. 总结:需要解决以下问题,才完整做出题目——
  • 如何控制分割出来的是4个子串并且分割完整的字符串?(终止条件)
  • 如何判断整数符合ip条件?(注意范围控制)
  • 如何回溯?不要少任何一个变量的回溯
  • 如何处理’.'?

代码实现

class Solution {
public:
    void backtracking (const string& s,int startindex,int& depth,string temp,vector<string>& result ){
        if(startindex == s.size() && depth == 4){
            result.push_back(temp);
            return;
        }

        for(int i = startindex;i < s.size();i++){
            string str = s.substr(startindex,i-startindex+1);
            //判断子串符不符合ip要求
            if(str.size() > 3) continue;
            int num = stoi(str);//这种转变不能覆盖整个用例。
            if(num < 0 || num > 255){//整数不在0-255.
                continue;
            }
            if(str.size() > 1 && str[0] == '0'){//前导0,也不行
                continue;
            }
            //以下是符合整数的
            depth ++;
            if(depth > 1){
                temp += '.';
            }
            temp += str;
            backtracking(s,i+1,depth,temp,result);
            //回溯
            temp.erase(temp.size()-str.size(),str.size());//位置和长度.删去放入的str
            if(!temp.empty()){
                temp.pop_back();//弹出'.'
            }
            depth--;
        }
        return;
    }
    vector<string> restoreIpAddresses(string s) {
        vector<string> result;
        string temp;//可能的结果
        int depth = 0;
        backtracking(s,0,depth,temp,result);
        return result;
    }
};

三、参考学习

【93.复原IP地址】参考学习链接

学习内容

  1. 分割问题类似组合问题要用到回溯。需要解决的问题依然是以上总结出来的,但是参考给的方法有所区别:
  • 区别一:直接在原有字符串s中加’.',不需要temp;
  • 区别二:因为直接在原有字符串s中加’.‘,所以计数’.'的数量pointnum。
  • 区别三:把判断子串合法性另写一个函数。
  1. 所以根据参考给的思路重新实现:
class Solution {
public:
    //判断子串是否合法,输入起始下标和终止下标。左闭右闭区间。
    bool isvalid(string s,int start,int end){
        //前导存在0.
        if((end-start+1) > 1 && s[start] == '0') return false;
        //范围
        if((end-start+1) >= 1 && (end-start+1) <= 3){
            string sub = s.substr(start,end-start+1);
            int subnum = stoi(sub);
            if(subnum >=0 && subnum <= 255) return true;
        }
        return false;
    }
    void backtracking(string s,int pointnum,int startindex,vector<string>& result){
        //终止条件
        if(pointnum == 3){//说明已经插入了三个'.'
            //仍然需要判断最后一个整数
            if(isvalid(s,startindex,s.size()-1)){//最后一个整数区间:[startindex,s.end()-1]
                result.push_back(s);
                return;
            }
        }

        for(int i = startindex;i < s.size();i++){
            if(isvalid(s,startindex,i)){
                //第一步插入'.'
                s.insert(s.begin()+i+1,'.');//insert在第一个参数所指位置之前插入'.'
                //数量+1
                pointnum++;
                backtracking(s,pointnum,i+2,result);//递到下一层,注意已经插入了'.'
                //回溯
                s.erase(s.begin()+i+1);//所指位置删除
                pointnum--;
            }else break;//这从参考中学到。
        }
        return;
    }
    vector<string> restoreIpAddresses(string s) {
        vector<string> result;
        backtracking(s,0,0,result);
        return result;
    }
};
  • 在for循环的 最后else break。在本题的用例中,如果不合法,就是整数已经是4位了,所以后面无需遍历,可以直接跳出,不用再isvalid执行,效率更高。
  • 对比参考的isvalid函数:参考的函数在num获取中判断了非法字符。
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) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }

总结

在这里插入图片描述
(欢迎指正,转载标明出处)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值