加载中...
600-不含连续1的非负整数(Non-negative Integers without Consecutive Ones)
发表于:2021-12-03 | 分类: 困难
字数统计: 1.7k | 阅读时长: 7分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones

英文原文

Given a positive integer n, return the number of the integers in the range [0, n] whose binary representations do not contain consecutive ones.

 

Example 1:

Input: n = 5
Output: 5
Explanation:
Here are the non-negative integers <= 5 with their corresponding binary representations:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
Among them, only integer 3 disobeys the rule (two consecutive ones) and the other 5 satisfy the rule. 

Example 2:

Input: n = 1
Output: 2

Example 3:

Input: n = 2
Output: 3

 

Constraints:

  • 1 <= n <= 109

中文题目

给定一个正整数 n,找出小于或等于 n 的非负整数中,其二进制表示不包含 连续的1 的个数。

示例 1:

输入: 5
输出: 5
解释: 
下面是带有相应二进制表示的非负整数<= 5:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数3违反规则(有两个连续的1),其他5个满足规则。

说明: 1 <= n <= 109

通过代码

高赞题解

写在前面

昨天因为一些工作上的事情,实在没有时间回复大家的留言,看到了好多同学留言祝我节日快乐 🤣

没想到我也能成为老师 🤣 十分感谢,也祝你们天天开心 ~


数位 DP

这是一道典型的「数位 DP」题。

对于「数位 DP」题,都存在「询问 $[a, b]$($a$ 和 $b$ 均为正整数,且 $a < b$)区间内符合条件的数值个数为多少」的一般形式,通常我们需要实现一个查询 $[0, x]$ 有多少合法数值的函数 int dp(int x),然后应用「容斥原理」求解出 $[a, b]$ 的个数:$dp(b) - dp(a - 1)$。

对于本题,虽然只需要求解 $[0, n]$ 范围内数的个数,但其实拓展到求 $[a, b]$ 区间个数的也不会增加难度。

具体的,对于「数位 DP」问题通常是「从高位到低位」的分情况讨论。

不失一般性的考虑数值 $n$ 的某一位 $cur$ 是如何被处理的:

  1. 如果当前位 $cur = 1$ 的话,由于我们需要满足「小于等于 $n$」的要求,因此如果该位填 $0$ 的话,后面的低位填什么都是满足要求的,因此我们期望能够查表得出「长度为 $i + 1$,且二进制位置 $i$ 数值为 $0$ 时」有多少合法数值,将其累加到答案中;
    与此同时,我们需要确保当前位选 $1$ 是合法的,即我们需要记录上一位 $prev$ 是什么,确保 $cur$ 和 $prev$ 不同时为 $1$。
  2. 如果当前位 $cur = 0$ 的话,我们只能选 $0$,并决策下一位。

当出现「当前位无法填入 $cur$」或者「决策到最低位」时,则完成了所有合法答案的统计。

至于流程 $1$ 中的查表操作,我们可以使用 static 预处理出 f 数组,定义 $f[i][j]$ 为考虑二进制长度为 $i$,且最高位为 $j$($0$ or $1$)时的合法数个数(值不超过)。

PS. 值不超过的含义代表了不仅仅统计高位为 $j$ 的情况。例如 $f[4][1]$ 代表长度为 $4$,最高为 $1$,其包含了 1xxx 和 0xxx 的合法数的个数。

注意:为了防止重复计数问题,我们在不失一般性的计算 $f[i][0]$ 和 $f[i][1]$ 时,最好不要采用诸如 $f[i][cur] += f[i - 1][prev]$ 的 “后向查找依赖” 的方式进行转移,而是采用 $f[i + 1][cur] += f[i][prev]$ “前向主动更新” 的方式进行转移。

不失一般性的考虑 $f[i][0]$ 和 $f[i][1]$ 能够更新哪些状态:

  • 如果期望当前位填 $0$ 的话,需要统计所有满足 $(0…)_2$ 形式的合法数值,当前位的低一位只能填 $1$(填 $0$ 会出现重复计数,即需要忽略前导零的数值),此时有:$f[i + 1][0] = f[i][1]$;

  • 如果期望当前位填 $1$ 的话,需要统计所有满足 $(1…)_2$ 和 $(0…)_2$ 形式的合法数值:

    • $(1…)_2$ 时,当前位的低一位只能填 $0$;此时有:$f[i + 1][1] += f[i][0]$;
    • $(0…)_2$ 时,当前位的低一位只能填 $1$;此时有:$f[i + 1][1] += f[i][1]$。

image.png

代码:

[]
class Solution { static int N = 50; // f[i][j] 为考虑二进制长度为 i,而且最高位为 j(0 or 1)时的合法数个数(值不超过) // 如 f[2][1] 代表二进制长度为 2,且(值不超过)最高位为 1 的合法数的个数为 3 个:10、01、00 static int[][] f = new int[N][2]; static { f[1][0] = 1; f[1][1] = 2; for (int i = 1; i < N - 1; i++) { f[i + 1][0] = f[i][1]; f[i + 1][1] = f[i][0] + f[i][1]; } } int getLen(int n) { for (int i = 31; i >= 0; i--) { if (((n >> i) & 1) == 1) return i; } return 0; } public int findIntegers(int n) { int len = getLen(n); int ans = 0, prev = 0; for (int i = len; i >= 0; i--) { // 当前位是 0 还是 1 int cur = ((n >> i) & 1); // 由于始终要满足小于等于的要求,如果当前位本来为 1 的话,填成 0 的话,后面的低位无论怎么填,都是满足小于等于的要求的,因此将 f[i + 1][0] 累加到答案 if (cur == 1) ans += f[i + 1][0]; // 出现连续位为 1,分支结束,方案数被计算完 if (prev == 1 && cur == 1) break; prev = cur; if (i == 0) ans++; } return ans; } }
  • 时间复杂度:由于我们预处理 f 数组的操作使用了 static 修饰(在跑样例数据前已经预处理完,且预处理结果被所有样例数据所共享),因此访问 f 数组是 $O(1)$ 的查表操作;统计答案的复杂度与二进制长度相关,复杂度为 $O(\log{n})$。整体复杂度为 $O(\log{n})$
  • 空间复杂度:令 $C$ 为预处理数值的大小,固定为 $50 * 2$,复杂度为 $O(C)$

最后

如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (“▔□▔)/

也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。

所有题解已经加入 刷题指南,欢迎 star 哦 ~

统计信息

通过次数 提交次数 AC比率
19581 40652 48.2%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言

相似题目

题目 难度
打家劫舍 中等
打家劫舍 II 中等
一和零 中等
上一篇:
599-两个列表的最小索引总和(Minimum Index Sum of Two Lists)
下一篇:
601-体育馆的人流量(Human Traffic of Stadium)
本文目录
本文目录