前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【综合笔试题】难度 4/5,数位 DP 运用题

【综合笔试题】难度 4/5,数位 DP 运用题

作者头像
宫水三叶的刷题日记
发布2021-11-02 16:55:24
4750
发布2021-11-02 16:55:24
举报

题目描述

这是 LeetCode 上的「600. 不含连续1的非负整数」,难度为「困难」

Tag : 「数位 DP」

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

示例 1:

代码语言:javascript
复制
输入: 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

  1. 如果当前位
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]

代码:

代码语言:javascript
复制
class Solution {
    static int N = 50;
    // f[i][j] 为考虑二进制长度为 i,而且最高位为 j(0 or 1)时的合法数个数(值不超过)
    // 如 f[2][1] 代表二进制长度为 2,且(值不超过)最高位为 1 的合法数的个数: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 的话,后面随便填都符合,将方案数累加
            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)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.600 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 宫水三叶的刷题日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目描述
  • 数位 DP
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档