1到n中1出现的个数
offer39的要求是,求1-n的整数中1出现的次数,比如input25,那么1在1,10-19,21中出现了1+1+10+1(11出现了两个1)=13次
语法糖
俗话说得好,有count不count猪头三。
# offer39-solution 1
def count_1_nums(self, n):
count = 0
for i in range(1,n+1):
count = count + str(i).count('1')
return count
递归
其实我一开始在想这是个数学问题,怎么就和递归扯上关系了。
但是仔细想想,我们可以用
f
(
n
)
f(n)
f(n)代表1~n这n个整数的十进制表示中1出现的次数,将n拆分为两部分,最高一位的数字top和其他位的数字other,分别判断情况后将结果相加。
举一个具体的例子吧:1995,看成top=1,other=995,单位
p
o
w
=
1
0
3
pow=10^3
pow=103
那么1-999中的1的个数就是
f
(
p
o
w
−
1
)
f(pow-1)
f(pow−1)
千位上是1的个数是
995
+
1
=
o
t
h
e
r
+
1
995+1=other+1
995+1=other+1
其它位是1的个数就是
f
(
o
t
h
e
r
)
f(other)
f(other)
1的总数就是上述之和
可以看到,由于我们还分了千位是否1的判别,所以还要考虑千位不是1的例子:5678。对于5678而言:
1~999这个范围1的个数是
f
(
p
o
w
−
1
)
f(pow-1)
f(pow−1)
1000~1999中千位上是1的个数是pow,其他位是1的个数即是999中出现1的个数,也就是
f
(
p
o
w
−
1
)
f(pow-1)
f(pow−1)
2000-4999中1的个数是
3
f
(
p
o
w
−
1
)
3f(pow-1)
3f(pow−1)
5000-5678中1的个数是
f
(
o
t
h
e
r
)
f(other)
f(other)
1的总数就是上述之和。
拆分完以后,递归就不难写了。
# offer39-solution 2
class Solution:
def count_1_nums_Digui(self, n):
l = len(str(n))
sum = 0
while(True):
top = n // (10 ** (l-1))
if l == 1:
if top == 0:
return 0
else:
return 1
other = n % (10 ** (l-1))
q = 10 ** (l - 1) - 1
if top == 1:
sum = sum + (other+1) + Solution().count_1_nums_Digui(other) + Solution().count_1_nums_Digui(q)
else:
sum = sum + 10**(l-1) + Solution().count_1_nums_Digui(other) + top * Solution().count_1_nums_Digui(q)
return sum
编程之美
接下来这里仅供参考。我虽然读懂了《编程之美》在说什么,也算是看懂了代码。但是我绝对难以想出如此这般的方法,只能说突出一个离谱。
① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100-199,1100-1199,2100-2199,……11100-11199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。
② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100-199,1100-1199,2100-2199,……11100-11199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共14个,等于低位数字(13)+1。
③ 如果百位上数字大于1(2-9),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100-199,1100-1199,2100-2199,……11100-11199,12100~12199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。
如此这般:
# offer39-solution 3
def count_1_nums(n):
count, m = 0, 1
while m <= n:
count += (n // m + 8) // 10 * m + (n // m % 10 == 1) * (n % m + 1)
m *= 10
return count
反正我不懂……
最大的礼物
offer42:在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。
你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角,现在给定一个棋盘及其上面的礼物,需要规划处一个路线,使得可以获得最大价值的礼物。
几乎是最典型的DP了,而且也很好写出递推式:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
+
a
[
i
]
[
j
]
dp[i][j]= max (dp[i-1][j], dp[i][j-1])+ a[i][j]
dp[i][j]=max(dp[i−1][j],dp[i][j−1])+a[i][j]
剩下的都是为这条递推式服务的一些辅助。
# offer42-solution
class Solution:
# 假设输入array为一维数组,行数为rows,列数为cols,要求输出为最大的那个数值
def getMaxValue(self,array,rows,cols):
if array == [] or rows <= 0 or cols <= 0:
return 0
maxValues = [[0 for i in range(cols)] for j in range(rows)] # 全是0的辅助矩阵
for i in range(rows):
for j in range(cols): # 从上往下搜索
left = 0
up = 0
if i > 0:
# 如果行号大于0,说明它上面有数字
up = maxValues[i - 1][j] # 那么up就为从上方来的路径的礼物值
if j > 0:
# 如果列号大于0,说明它左边有数字
left = maxValues[i][j - 1] # left就为从上方来的路径的礼物值
maxValues[i][j] = max(up, left) + array[i * cols + j] # 两条路中的最大者加当前值
return maxValues[rows - 1][cols - 1] # 输出右下角的值
丑数
offer44:丑数是指只包含质因子2、3和5的数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 我们认为:1是第一个丑数,要求按从小到大的顺序的第N个丑数。
同样可以写成DP,对每个丑数,乘以2,3,5,然后判断新的丑数的位置。可以考虑分别对质因子2,3,5制作三个队列进行出队入队,更为简洁的方法应该是写成一个队列,但是判断3次。
# offer44-solution
class Solution:
def GetUglyNumber_Solution(self,index):
if index <= 0:
return 0
res = [1]
nextIndex = 1
t2 = t3 = t5 = 0
while nextIndex < index:
min_val = min(res[t2]*2,res[t3]*3,res[t5]*5)# 对每个丑数,乘以2,3,5,然后判断新的丑数的位置
res.append(min_val)
while res[t2]*2 <= min_val: # 如果乘出来的结果较小,则指标向前移动
t2 += 1
while res[t3]*3 <= min_val:
t3 += 1
while res[t5]*5 <= min_val:
t5 += 1
nextIndex += 1
return res[index-1]