《剑指offer》39&42&44、数学类三则:1-n中1出现的个数、最大的礼物、丑数

这篇博客探讨了如何使用递归和动态规划方法解决数论问题,如计算1到n中1出现的次数。作者给出了三种不同的解决方案,包括简单的计数法、递归策略和高效的迭代方法。此外,还涉及了最优化问题,如最大价值路径选择,以及寻找丑数(只包含2、3、5为质因子的数)的动态规划算法。
摘要由CSDN通过智能技术生成

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(pow1)
千位上是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(pow1)
1000~1999中千位上是1的个数是pow,其他位是1的个数即是999中出现1的个数,也就是 f ( p o w − 1 ) f(pow-1) f(pow1)
2000-4999中1的个数是 3 f ( p o w − 1 ) 3f(pow-1) 3f(pow1)
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[i1][j],dp[i][j1])+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]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值