LeetCode No.233 Number of Digit One 题解
来源:互联网 发布:校园全覆盖网络弊端 编辑:程序博客网 时间:2024/06/05 23:56
LeetCode No.233 Number of Digit One 题解
题目描述
Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.
For example:
Given n = 13,
Return 6, because digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.
题目分析
这题这形式给出来就直接让人想到数位DP。。果断试着推了一下, 发现不是很难。 尤其是这题只要讨论数字1
出现的次数, 下面推导的过程我们可以看到这比其他数字更加容易推导。
由于这是第一篇数位DP的博客。 就先简单介绍一下数位DP是什么。
先从DP开始说起。Dynamic Programming 我的简单理解是,将原问题分解为相同性质的子问题 (这一步分解很重要!). 然后需要的就是推导出子问题与原问题之间的联系(如果把不同规模的问题,看作是相同问题的不同状态, 这这个联系的函数表达即是常说的状态转移方程)
数位DP其实就是DP的一种形式罢了。有区别与一般的DP在于, 对于一般的数n
,状态的转移一般是在同一个数量级上的转移。不负责任的写一个常用的一维DP形式: f[k] = func(f[k - 1], f[k - 2], ... f[k - n])
这个式子里面, func
函数是实现状态转移的主体, 视不同情况而不同。 需要注意的是这里func
的参数是该问题的前n个状态。 这与当前状态处于同一个数量级上。
而数位DP则显得更加“效率”, 它的状态转移是从f[10]
转移到f[100]
,再到f[1000]
之类。 这类DP的状态转移是在数位上进行的。换个角度去看, 即是从状态2(2位)转移至状态3(3位), 再到状态4.(4位)
这道题的数位DP形式是特别典型的那种。所以就先闲扯了一点。 下面来看这题本身。由于这是裸数位DP, 所以思路什么的就略去了。下面直接开始推最关键的, 状态转移方程。
要推方程还得先看问题要怎么分解为子问题。来举个栗子:n = 2101
.
如果稍微能类比一下的话, 套用上面写的一维DP的形式, 很容易猜想到可能方程会以如下形式出现: f[2101] = func(f[1000], f[100], f[10], f[1])
这就是在数位上而言, 的前几个状态嘛~
这里我们也可以看到, 数位DP可以用非常少的状态来得到我们需要的下一个状态。 这侧面反映了我们如果迭代去计算时必然包含了大量的重复运算。重复在哪里? 以上面那个例子,来具体列举几个: f[2000] = f[1000] + f[2000] - f[1000]
这样写的目的,就是把2000分成两部分去看。然后对应上, 是不是发现, 除了最高位, 其他都是相等的1的个数呢。。。 001,002...100...998, 999
1001,1002...1100...1998, 1999
所以可以看到, 我们只要算出了f[1000]
, 事实上f[2000]
是可以直接推出的, 有f[2000] = 2 * f[1000] + 1000
在这里我要特别说明一下, 这里的f[1000]
是不包含1000本身的,计算的是从1~999中1
的个数。这样做的目的就是为了使状态转移方程好看一些。
类似的, 我们应该能够推出,f[2101] = 2 * f[1000] + 1000 + f[100] + f[1]
推到这一步, 我们做到什么程度了呢? 就是把任意一个数n的f[n]
分解为了几个
实际上呢。。。基状态的转移和上面的, 把原问题分为一系列基状态的过程, 十分类似。 考虑f[10]->f[100]
:
f[100] = f[100] - f[90] + f[90] - f[80]...- f[10] + f[10]
拆分出来的10个部分, 每一部分的值与
f[10]
都有紧密联系。除了10~19
这一部分, 其他的9部分值是和f[10]
相同的。 讲了这么多, 我觉得大概直接贴状态转移应该也能懂了吧。。
f[i] => 1 ~ (10 ^ i - 1) 中 1 的出现次数
f[i] = 10 * f[i - 1] + num(i - 1), num(i - 1)为 pow(10, i - 1)
到这里, 基本就解释完了整个过程。其实不算复杂。 只是我表达能力欠缺。orz。
一行代码胜千言, 下面是实现代码:
class Solution {public: int countDigitOne(int n) { if (n < 1) return 0; int i = 0, dealt = 0, ans = 0; int f[20]; memset(f, 0, sizeof(f)); // f[i] => 1 ~ (10 ^ i - 1) 中 1 的出现次数 // f[i] = 10 * f[i - 1] + num(i - 1), num(i - 1)为 pow(10, i - 1) // dealt: 已经处理完的数字(n的某个右侧部分) while (n > 0) { int t = n % 10; n/= 10; f[i] = i * pow(10, i - 1); if (t == 1) ans+= dealt + 1 + f[i]; else if (t > 1) ans+= t * f[i] + pow(10, i); dealt+= t * pow(10, i); i++; } return ans; }};
总代码量就22行,还包含了三行注释。。。前文看不懂的直接看代码算了。。。
其他细节
- 由于我想把这篇博客的重点放在讲解数位DP上,因此有很多实现细节没有说明清楚。 不过略看一下代码应该很清楚的。
- 实际上当数位为1时, 需要另外讨论。 这在代码中也有体现。 正是这一点, 使得计算1的个数比其他数字来的稍微容易一点: 1的话只要讨论>1和=1的情况。其他数字要讨论3种(0除外)
- 数位DP博大精深,这题只是比较水的一题而已, 我自己对数位DP的题也没多少自信(事实上DP我都很虚)。。。如果上面的有说错的地方,希望大家不吝赐教。
The End.
- LeetCode No.233 Number of Digit One 题解
- LeetCode No.233 Number of Digit One
- LeetCode 题解(148): Number of Digit One
- LeetCode题解——Number of Digit One
- LeetCode Algorithms 233. Number of Digit One 题解
- leetcode 233: Number of Digit One
- Leetcode 233 Number of Digit One
- Number of Digit One(leetcode 233)
- Leetcode #233 Number of Digit One
- [Leetcode 233, Medium] Number of Digit One
- leetcode 233: Number of Digit One
- [leetcode-233]Number of Digit One(C)
- 【LeetCode】(233)Number of Digit One (Medium)
- LeetCode(233)Number of Digit One
- leetcode 233: Number of Digit One
- [LeetCode 233] Number of Digit One
- leetcode 233 Number of Digit One
- leetcode 233: Number of Digit One
- 进程间通信-信号量2
- HTTP协议优秀博客推荐
- HTML5动画
- 大学生职业规范生涯
- LeetCode57 Insert Interval
- LeetCode No.233 Number of Digit One 题解
- 安装eclipse和安装jre过程中出现 An error has occurred. see the log file的我的解决办法
- java第二次作业(2)
- java
- Linux环境下,用Django和MySQL搭建第一个项目
- 数组和字符串相互转换
- npm管理员身份install时出现权限问题
- HDU 3709 Balanced Number 数位dp
- 自定义View圆的点击事件实现