LeetCode93 Restore IP Addresses 还原ip地址

题目描述

Restore IP Addresses
给定一个ip地址的字符串,但字符串中应该有的三个点被抹掉了,所以现在的字符串里面全是数字。要求是给定一个只有数字的字符串,输出该字符串可能构成的ip地址。
样例输入:
25525511135
样例输出:
[“255.255.11.135”, “255.255.111.35”]

思路

第一:分段合理性。将三个点插入到数字时,会形成四段ip,那么每段ip的长度至少是1,至多是3。
第二:段内ip合理性。段内数字的范围应为0-255范围,而且除非这段ip的长度为1,那么该段的ip的第一个数字就不能为‘0’。

其实一开始想肯定会想到用递归来分段,分段则是先分第一段,再分第二段,再分第三段,但是这样必然会递归到很多分段不合理的ip分段。下面我来阐述一下我的思路。

先考虑分段合理性
设输入字符长度为n,那么数字的索引则为0 - n-1。
想象在每两个数字之间有一个可以供点插入的位置,那么这样的位置有m = n-1个。点的索引为0 - m-1。
这里写图片描述
以上图为例,如果原数字只有4个位置,那么三个点可以插入的位置只有3个,那么分段就只有一种方案。
这里写图片描述
以上图为例,如果数字有0-10的索引,那么点的位置则是0-9索引。现在的问题就变成了要从0-9索引中,挑选三个位置供点插入,使得插入后的ip分段达到分段合理性

1.相邻两个点的索引之差不能超过3(这里也可以把左边界和右边界考虑进来作为相邻),比如第一个点索引为0,第二个点索引为3,它们之间的数字就有3个了。如果点索引选0和4,那么这个ip分段就不合理了。
2.考虑左右边界,所以第一个点索引,最多到达2;第三个点最多到达7。不然分段内的数字就会超过3个了。

正式讲解
在这里插入图片描述
本文思路是:1.先考虑分段的每段长度的合理性 2.再考虑每段内的数字是否合理
考验每段分段的长度是否合理,其实就是考虑如何插入三个点到点索引0 - m-1中去,使得每段分段的长度都是至少为1,至多为3的。
如上图所示,考虑ip地址中的三个点的可插入位置的范围,可以把点的索引分为三个部分。这里我先确定第一个点的可插入范围为i,再确定第三个点的可插入范围为j,最后再确定第二个点的可插入。
为什么是这么一个顺序呢,因为i的范围最多是延伸到2,而j的范围最多是延伸到m-3,而k的范围则需要在ij范围确定下来,才能确定下来。

按照这个思路,我们在ij合理地确定下来以后,我们唯一需要关心的就是k可不可以找到一个可以插入的位置?
ij合理性是指,i从左最多到2,j从右最多到m-3,而且ij之间至少得有一个索引,不然k就没有可以插入的位置。当然ij之间也不能离太远,ij之间空出来的索引的总长度最多为5,这样k当好可以插入到中间那个位置使得分出来的两个分段的长度都是3。(这段话读者可以在本文第二个图上验证,所以就有了min=1,max=5)。

1.首先是如何确定一个合理的ii从左边界往右延伸最多到2,但由于还留两个位置给jk,所以i最多到min(2,m-3)

2.利用min=1,max=5这个结论,我们开始进一步推理。现在假设已经确定一个合理的i,需要确定一个合理的j。所以剩下的范围就只有i+1 - m-1中可以选一个j了。
在这里插入图片描述
利用ij之间的索引长度最多为5(max=5),所以j的最大值为i+6,但是往右延伸有边界m-1,所以往右延伸的最大值为right=min(i+6,m-1)
在这里插入图片描述
利用第四段分段最长长度为3,所以j的最小值为m-3,但是往左延伸有边界i+2(因为有min=1,即ij之间至少有一个空位),所以往左延伸的最大值为left=max(m-3,i+2)

只有当right>=left时,j才有可能有一个合理值。j的取值范围为[left, right]。所以现在可以得到一个合理的j了。

3.现在已经有了一个合理的ij,现在只需要根据ij之间的空位个数,就可以知道合理的k是谁了。

  • 当空位个数为5即(j-i) == 6,那么k只能取中间值,即(j+i)//2
  • 当空位个数为4即(j-i) == 5,那么k只能取两个值,即(j+i)//2(j+i)//2+1
  • 当1<=空位个数<=3即(j-i) <= 4,那么k可以取多个值,即范围[i+1, j-1],也就是ij间的所有空位

以上三个结论读者可以通过本文第二个图验证。

之后再验证4个分段内的数字是否合理就可以了。

代码

class Solution:

    def check(self,s , i , k , j):
        n = len(s)
        m = n-1

        li = [[0,i],[i+1,k],[k+1,j],[j+1,n-1]]
        #每个段的开始索引和结束索引
        flag = True
        resultli = []
        for start,end in li:
            #分片
            temp = s[start:end+1]
            if (temp[0] == '0') and len(temp)!=1:
                flag = False
                break
            value = int(temp)
            if (value <0) or (value>255):
                flag = False
                break
            resultli.append(temp)
        if flag == True:
            return '.'.join(resultli)
        else:
            return False

    def restoreIpAddresses(self, s):
        """
        :type s: str
        :rtype: List[str]
        """
        #边界条件

        #非边界条件
        n = len(s)
        m = n-1#点可选位置的索引最大值

        li = []
        if (n<4) or (n>12):
            return li       
        for i in range(min(2,m-3)+1):#向右最多延伸到2,但还得至少留两个位置给jk
            #确定下来了左边的点

            right=min(i+6,m-1)
            left=max(m-3,i+2)
  
            if(right >= left):
                
                for j in range(left,right+1):
                    #确定下来了右边的点
                    if (j-i) <= 4:
                        for k in range(i+1,j):
                            result = self.check(s , i , k , j)
                            if result != False:
                                li.append(result)
                    elif (j-i) == 5:
                        middle = (j+i)//2
                        for k in range(middle,middle+2):
                            result = self.check(s , i , k , j)
                            if result != False:
                                li.append(result)
                    elif (j-i) == 6:
                        middle = (j+i)//2
                        result = self.check(s , i , middle , j)
                        if result != False:
                            li.append(result)
        return li

多次在LeetCode上面提交,运行时间40ms。

暴力求解

来自LeetCode上这道题的讨论WHO CAN BEAT THIS CODE ?

    vector<string> restoreIpAddresses(string s) {
        vector<string> ret;
        string ans;
        
        for (int a=1; a<=3; a++)
        for (int b=1; b<=3; b++)
        for (int c=1; c<=3; c++)
        for (int d=1; d<=3; d++)
            if (a+b+c+d == s.length()) {
                int A = stoi(s.substr(0, a));
                int B = stoi(s.substr(a, b));
                int C = stoi(s.substr(a+b, c));
                int D = stoi(s.substr(a+b+c, d));
                if (A<=255 && B<=255 && C<=255 && D<=255)
                    if ( (ans=to_string(A)+"."+to_string(B)+"."+to_string(C)+"."+to_string(D)).length() == s.length()+3)
                        ret.push_back(ans);
            }    
        
        return ret;
    }

很明显,a是第一段分段的长度,最小为1,最大为3。第一段的长度的值刚好是第二段的开始索引,以此类推。substr函数的第二个参数也是指长度。

暴力求解优化

class Solution {
public:
    vector<string> restoreIpAddresses(string s) {
        vector<string> ans;
        int len = s.length();
        if (len < 4 || len>12) return ans;
        string seg[4];
        for (int a = 1; a <= 3 && a + 3 <= len; a++)
        //第一段为[0,a-1],所以后面至少得有3个索引才可以分成三段
        //极端情况下,a + 3 = len即a + 2 <= len-1,所以后面刚好只有
        //三个索引a,a+1,a+2
        {
            seg[0] = s.substr(0, a);//取第一段
            if (stoi(seg[0])>255 || a > 1 && seg[0][0] == '0') break;
            for (int b = 1; b <= 3 && a + b + 2 <= len; b++)//至少留两个索引
            {
                seg[1] = s.substr(a, b);//取第二段
                if (stoi(seg[1]) > 255 || b > 1 && seg[1][0] == '0') break;
                for (int c = 1; c <= 3 && a + b + c + 1 <= len; c++)//至少留一个索引
                {
                    seg[2] = s.substr(a + b, c);//取第三段
                    if (stoi(seg[2]) > 255 || c > 1 && seg[2][0] == '0') break;
                    seg[3] = s.substr(a + b + c);//取完第三段,第四段也自然而然地出来了
                    if (stoi(seg[3]) > 255 || seg[3].length() > 1 && seg[3][0] == '0') continue;
                    ans.push_back(seg[0] + "." + seg[1] + "." + seg[2] + "." + seg[3]);
                }
            }
        }
        return ans;
    }
};

此c++代码运行时间为0ms,真是突破极限了啊。优化的地方在于,还考虑到了预留索引给后面分段的这种合理性,且四个分段内的数字的合理性是依次进行判断的。注意前3个是break,最后才是continue。前面是break是因为,一旦break当前循环和之后的循环都将会产生不合理的分段。最后是continue是因为,这句管的是第四个分段,第三个分段合理第四个分段不合理,但下一次循环就有可能出现第三第四都合理的情况,所以最后是continue。

posted @ 2018-09-20 12:35  allMayMight  阅读(99)  评论(0编辑  收藏  举报