恢复ip地址——回溯算法

问题描述

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 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 地址。

示例 1:

输入:s = “25525511135”

输出:[“255.255.11.135”,“255.255.111.35”]

问题分析

把字符串分解成符合规定的ip地址,首先要明白ip地址的性质

  • ip地址分为四节,每节取值范围在[0,255]
  • 对于取出的字符串,若它的第一位为’0’,那么它的长度只能为1,多位字符不能以0作为开头

明白这两点,我们可以每次拿出[1,3]个字符,判断是否符合ip地址的条件,若符合,则递归剩下的字符串,直到四节都符合,那么就得到了一个解

代码实现

#define MaxSize 100
#include<stdio.h>
typedef struct
{
    char data[MaxSize];
    int length;
}IP;
void addch(IP&ip,char ch)
{
    ip.data[ip.length]=ch;
    ip.length++;
}
IP addot(IP ip)
{
    addch(ip,'.');
    return ip;
}
void solveip(char s[],int n,int start,int step,IP ip)
{
    if(start<=n)
    {
        if(start==n&&step==4)
        {
            for(int i=0;i<ip.length-1;i++)
                printf("%c",ip.data[i]);
            printf("\n");
        }
    }
    int num=0;
    for(int i=start;i<n&&i<start+3;i++)
    {
        num=10*num+(s[i]-'0');
        if(num<=255)
        {
            addch(ip,s[i]);
            solveip(s,n,i+1,step+1,addot(ip));
        }
        if(num==0) break;
    }
}
int main()
{
    char s[MaxSize]="25525511135 ";
    int n=11;
    IP ip;
    ip.length=0;
    solveip(s,n,0,0,ip);
    return 1;
}

代码分析

算法

使用回溯算法,即每一次都按最优解向前搜索,不满足条件时则回溯到上一次的情况,进行次优解的搜索。

表现在本次代码中,最优解,就是每取一位字符,就按每一节的取值范围在[0,9]进行设置。若最优解不能满足,则回溯到上一个时间点,使用次优解进行搜索,而次优解即是取到两位字符,每一节按照取值范围[10,99]进行设置。若次优解仍不能满足,则回溯到上一个时间点,用最次解进行搜索,即取到三位字符,每一节按照取值范围[100,225]进行设置。

需要注意的是,其中的一节所选取的解的优劣,不会影响到下一节的选择,即下一节依然会使用最优解。

而且所谓回溯到上一个时间点,就是回溯到前一节的设置。

核心模块

void solveip(char s[],int n,int start,int step,IP ip)
{
    if(start<=n)
    {
        if(start==n&&step==4)
        {
            for(int i=0;i<ip.length-1;i++)
                printf("%c",ip.data[i]);
            printf("\n");
        }
    }
    int num=0;
    for(int i=start;i<n&&i<start+3;i++)
    {
        num=10*num+(s[i]-'0');
        if(num<=255)
        {
            addch(ip,s[i]);
            solveip(s,n,i+1,step+1,addot(ip));
        }
        if(num==0) break;
    }
}

终止条件

    if(start<=n)
    {
        if(start==n&&step==4)
        {
            for(int i=0;i<ip.length-1;i++)
                printf("%c",ip.data[i]);
            printf("\n");
        }
    }

这里的终止条件有两个

  1. start==n,即字符串已经读取到最后一个
  2. step==4,即节数刚好是四个

注意这里的step,满足的终止条件是正好是四个,不论是过多还是过少都是不满足的。并且,过多的情况可以有效的限制从头开始数起的已经满足条件的含有三个字符的节不会被回溯掉,具体会在下面的执行过程中展示。

输出时,for循环的终止条件之所以设置为length-1,是为了避免输出ip串中最后储存的‘.’

而输出完成一个满足条件的ip串后,程序也不会停止,而是回溯到上一个时间点,去寻找其他形式的满足条件的串。

例如,在找到

255.255.11.135

之后,程序会回溯到

255.255.11

来继续查找,进而找到

255.255.111.35

直到再也找不出来,然后被调函数不断出栈,回到最开始调用的第一个solveip,结束。

递归过程

    int num=0;
    for(int i=start;i<n&&i<start+3;i++)
    {
        num=10*num+(s[i]-'0');
        if(num<=255)
        {
            addch(ip,s[i]);
            solveip(s,n,i+1,step+1,addot(ip));
        }
        if(num==0) break;
    }

这里的for循环以取到三个字符或是字符串被取完为终止条件 ,限制了每一节的最大长度。

同时两个if语句,限制了:

  • 每节取值范围在[0,255]
  • 对于取出的字符串,若它的第一位为’0’,那么它的长度只能为1,多位字符不能以0作为开头

addch函数将取到的字符填入ip串中。

addot函数将传入下一个solveip的IP串尾部加上‘.’,意味着下一个soveip中进行的是下一个字节的操作,当然,在回溯的过程中,无论是下一个字节中的操作,还是调用时加上的‘.’,都会被回溯掉。

在还没有进行回溯时,会不断的调用函数进栈,然后一个节都是最优解法,如

2

2.2

2.2.5

2.2.5.2.2.5.1.1.1.3.5

在调用下一次的时候

2.2.5.2.2.5.1.1.1.3.5.

就会出现问题,字符串被取完了,for循环无法进行,调用终止,函数出栈,回到上一个时间点

2.2.5.2.2.5.1.1.1.3.5

但是这时再进行下一次循环的时候,同样达到了终止条件,于是调用终止,函数出栈,回到上一个时间点

2.2.5.2.2.5.1.1.1.3

进行下一次for循环

2.2.5.2.2.5.1.1.1.35

依然不满足条件,回溯

2.2.5.2.2.5.1.1.1

2.2.5.2.2.5.1.1.13

2.2.5.2.2.5.1.1.13.5

如此不断的搜索与回溯,可以达到第一个被调用的函数

2

这时继续for循环

22

22.5.2.2.5.1.1.1.3.5

重复上述过程,可以达到第一节被满足的情况

225

225.2.2.5.1.1.1.3.5

如果继续回溯,会自然的想到,如果回溯到

225

那么继续进行第一个被调函数中的for循环的话,循环终止,函数结束,就无法完成。

其实,根本不会回溯到这种情况,因为第二节会在达到这种情况之前被满足,即回溯到

225.22

就会有

225.225

225.225.1.1.1.3.5

最终,达到终止条件

225.225.11.135

总结

递归过程较为复杂,在搜索与回溯之间,函数不断地出栈入栈,因此时间复杂度会比较高。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
给定一个包含数字字符串用以表示一个 IP 地址,要求恢复出所有可能IP 地址。这是一个典型的回溯算法问题,可以通过递归实现。具体步骤如下: 1. 定义一个数组 `segments`,用于存储 IP 地址的四个段; 2. 定义一个变量 `start`,表示当前处理到字符串的哪个位置; 3. 定义一个变量 `segmentCount`,表示已经处理了几个段; 4. 如果已经处理了四个段并且字符串已经处理完毕,则将 `segments` 中的四个段拼接成一个 IP 地址,并将其加入结果集; 5. 如果已经处理了四个段但字符串还没有处理完毕,则直接返回; 6. 如果还没有处理完四个段但字符串已经处理完毕,则直接返回; 7. 对于每个段,从 `start` 开始,枚举可能的结束位置,如果这个段合法,则将其加入 `segments` 数组中,并递归处理下一个段; 8. 处理完当前段后,需要将 `segments` 数组中的当前段删除,以便处理下一个可能的结束位置。 代码如下(Python实现): ```python class Solution: def restoreIpAddresses(self, s: str) -> List[str]: def backtrack(start: int, segmentCount: int): if segmentCount == 4: if start == n: ip = ".".join(str(seg) for seg in segments) res.append(ip) return if start == n: return if s[start] == "0": segments[segmentCount] = 0 backtrack(start + 1, segmentCount + 1) addr = 0 for end in range(start, n): addr = addr * 10 + int(s[end]) if 0 < addr <= 255: segments[segmentCount] = addr backtrack(end + 1, segmentCount + 1) else: break n = len(s) if n < 4 or n > 12: return [] segments = [0] * 4 res = [] backtrack(0, 0) return res ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值