leetcode:600. 不含连续1的非负整数

题目解析

传入一个数据n,要我们找出[0, n]范围内所有转换成的二进制中没有连续1的字符串的数的总数。
(好家伙,翻译得比原题还不说人话)
比如我传入一个5,从0到5我们写出所有的二进制:
0、1、10、11、100、101
其中11是有联系的1,我们不要,剩下的五个数都是符合范围的。

看数据的范围,直接10* *9,根本不可能去遍历,转二进制然后找,所以我们要总结一些规律,这里我想到的是按照位数

1位数:0、1;
2位数:10、11(这个不要);
3位数:100、101;
四位数:1000、1001、1010
…………
貌似也没什么规律吧。。

但是我们看这个:(六位数的处理)
在这里插入图片描述
不知道能不能看懂,我们来分析一下。
主要思想:1后面只能跟0。

最开始的两位一定是10,(6位数,所以第一位是1,然后根据思想,第二位是0),
剩下的我们只能进行测试了,感觉第三位可以放一个1,也可以放0。
如果放1的话,第四位只能放0了。

等等,这一幕怎么有一点眼熟
我们在处理6的时候,不也是这样的吗!
那这个是谁?
4!

然后第三位放0,我们第四位又可以选择,放1此时就是一个3,放0我们继续划分,最终结果:
在这里插入图片描述
距离最后有x位,那么这个数就是x。
所以我们就可以把dp[1:n-2]全部加起来,得到二级制为n位的总数

再转化一下,我们将dp[i] += dp[i-1],将dp[i]处理成从0到2i-1的数量。

然后是下一个问题,我们怎么处理" 10101001111’b "这样一个数据?
首先这个是一个11位二进制,我们先加一个dp[10],也就是从0到210-1,
然后剩下的范围是210到10101001111’b,我们可以确定:这些数都是11位二进制,并且前两位都是10
那么好了,我们加一个dp[8],dp[8]是从0到28-1,但是别忘了我们还有前两位的,所以就是10_000000000-10_011111111,刚好能和前面的数拼接上,同理,我们还可以拼接dp[6]、dp[3],到了这里我们停一下。

是不是准备在dp[3]后面接一个dp[2]了?
确实是需要添加的,因为我们的dp[2]只能到’11’,但再往下,就会出现两个1相邻的情况。
(这里的实现是我们是检测当前index的前一位,如果都是1那么就退出)

最后一个问题,我们其实漏了一个数,没错就是本身,加一个判断即可。

代码:

class Solution:
    def findIntegers(self, n: int) -> int:
        # 因为我们后面数组长度的问题,所以是要做限制的
        if n == 1:
            return 2
        elif n < 4:
            return 3
        elif n == 4:
            return 4
        # 长度这里要加一!
        length = int(math.log(n, 2))+1
        # 用长度先计算着,然后将剩下的位置选出来
        dp = self.get_num(length)
        #print(dp)
        # 计算剩下的,我们找到每一个为1的位置,然后将其转化成一个子问题
        s = bin(n)[2:]
        #print(s)
        # 注意可能是等于的情况是符合的,我们要添加上
        ret = 0 if '11' in s else 1
        for i in range(len(s)):
            if s[i] == '1':
                # 下表从0开始,i=4相当于是0-1111的全部值,所以我们前面要加上那个1
                #print(ret)
                ret += dp[i]
                if i and s[i-1] == '1':
                    break
        return ret
             
    def get_num(self, length):
        '''规律,我们5位的前两位一定是10,那么第三位是1,就是一个三位的问题
        如果选0,再下一位可以是0也可以是1,那么此时1是2位的问题,再下面是一位的问题
        那么dp[i] = sum(dp[0~i-2])'''
        dp = [0]*max(length,4)
        dp[1], dp[2], dp[3] = 2, 1, 2
        # 这个计算的是i位的数据有几个符合条件的
        for i in range(4, length):
            dp[i] = dp[i-1]+dp[i-2]
        # 这时我们还要再加一次,最后用的是前面所有的值
        for i in range(1, length):
            dp[i] += dp[i-1]
        # 这个情况是对最后一位为0的
        dp[0] = 1
        # dp[i]就是从0到2**i-1的所有合适值
        return dp[::-1]

这个是能跑过的,双80我记得是。

个人感觉和官方解答思想差不多,但我这个麻烦点不过好想?
区别就在他是预判了31位够了,直接计算,我是根据长度来计算的。
然后因为我dp启动要求长度最小为4,产生了一个很离谱的结构,昨天过了就没力气管了。
(秋赛是真的挺离谱的,对于我这种野生算法玩家)

代码优化

你说要是都看到这里了我再拿烂代码给人看,这好吗,这不好。
我整理了一下,时间从32ms到28ms,空间涨了一点。

class Solution:
    def findIntegers(self, n: int):
        # 长度我们需要加一,因为没法整除,即使整除了也要加一(这里想一下8是1000就知道了)
        length = int(math.log(n, 2))+1
        dp = [0]*32
        dp[1], dp[2], dp[3] = 2, 1, 2
        for i in range(4, 32):
            dp[i] = dp[i-1]+dp[i-2]
        for i in range(1,32):
            dp[i] += dp[i-1]
        # 针对最后一位为1的情况,比1小的还有一个0,前面的我们都没处理这个
        # 也是在最后才发现的这个问题
        dp[0] = 1
        dp = dp[length-1::-1]
        # 获取二进制字符串,并去掉前面的Ob
        s = bin(n)[2:]
        print(len(dp),len(s))
        ret = 0 if '11' in s else 1
        for i in range(len(s)):
            if s[i]=='1':
                ret += dp[i]
                if i and s[i-1]=='1':
                    break
        return ret

和答案的相比,还是没那么整洁,但是能更好的体现思路吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值