6.4 抽象建模能力

参考:

  1. 所有offer题目的LeetCode链接及python实现
  2. github Target offer

面试题43:n个骰子的点数

题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

思路梳理

n个骰子的点数和的最小值为 n,最大值为 6n。
先统计出每一个点数出现的次数,然后把每一个点数出现的次数除以6^n,就能求出每个点数出现的概率。
本质是求数列f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+f(n-5)+f(n-6)

  • 先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。
  • 接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。
  • 定义一个长度为 6n 的数组,保存所有 s 可能出现的情况,和为 s 的点数出现的次数保存到数组第 s 个元素里。

**解法一:**基于递归求骰子点数,时间效率不够高。

  • 递归结束的条件就是最后只剩下一个骰子。

**解法二:**基于循环求骰子点数,时间性能好。

  • 定义两个数组,来分别保存两次循环求得的点数之和 s 对应的出现次数;
  • 在一次循环中,第一个数组中的第n个数字表示骰子和为n出现的次数。
  • 在下一循环中,我们加上一个新的骰子,此时和为n的骰子出现的次数应该等于上一次循环中骰子点数和为 n-1、n-2、n-3、n-4、n-5与n-6的次数的总和,
  • 所以我们把另一个数组的第n个数字设为前一个数组对应的第n-1、n-2、n-3、n-4、n-5与n-6之和。
  • 需要注意的是,每次使用新数组的时候,需要把数组所有位置清零,因为我们对于第n位进行的累加操作,如果之前第n位有数字但不清零的话,会导致结果偏大。
def PrintProbability(number):
    maxValue = 6
    prob = [[], []]
    # 标记哪个数组为当前参照的数组
    flag = 0
    # 初始化第一个数组
    prob[0] = [0]*(maxValue*number+1)
    for i in range(1, maxValue+1):
        prob[0][i] = 1
    for time in range(2, number+1):
        # 更新数组之前将本数组清零
        prob[1-flag] = [0]*(maxValue*number+1)
        # 更新数组
        for pCur in range(time, time*maxValue+1):
            diceNum = 1
            # 累加另一个数组在当前索引的前六个元素
            while pCur > diceNum and diceNum <= maxValue:
                prob[1 - flag][pCur] += prob[flag][pCur-diceNum]
                diceNum += 1
        # 改变数组标记
        flag = 1-flag

    total = maxValue ** number
    for i in range(number, maxValue*number+1):
        # 计算每一种次数出现的概率,并且按照科学计数法的格式输出
        ratio = prob[flag][i] / float(total)
        print("{}: {:e}".format(i, ratio))

    return

s = PrintProbability(5)

面试题44:扑克牌的顺子

题目:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成任意数字。

思路梳理

  • 可以把5张牌看成由5个数字组成的数组。大、小王是特殊的数字,我们不妨把它们都定义为0,这样就能和其他扑克牌区分开来了。
  • 怎样判断5个数字是不是连续的,最直观的方法是把数组排序
  • 由于 0 可以当成任意数字,我们可以用0去补满数组中的空缺。如果排序之后的数组不是连续的,即相邻的两个数字相隔若干个数字,但只要我们有足够的 0 可以补满这两个数字的空缺,这个数组实际上还是连续的。

总结:

  1. 首先把数组排序,再统计数组中0的个数,最后统计排序之后的数组中相邻数字之间的空缺总数。
  2. 如果空缺的总数小于或者等于0的个数,那么这个数组就是连续的;反之则不连续。
  3. 最后,我们还需要注意一点:如果数组中的非0数字重复出现,则该数组不是连续的。换成扑克牌的描述方式就是如果一副牌里含有对子,则不可能是顺子。

由于扑克牌的值出现在0~13之间,我们可以定义一个长度为14的哈希表,这样在O(n)时间就能完成排序

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        if numbers == [] or len(numbers)<5:
            return False
        # 调用 python 自带的方法排序
        numbers.sort()
        cnt_0 = 0
        cnt_a = 0
        for i in range(len(numbers)):
            if numbers[i] == 0:
                cnt_0 += 1
            elif i > cnt_0:
                # 出现了对子
                if numbers[i] == numbers[i-1]:
                    return False
                # 记录差值
                if numbers[i] != numbers[i-1] + 1:
                    cnt_a += numbers[i] - numbers[i-1] - 1
                if cnt_a > cnt_0:
                    return False
        return cnt_a <= cnt_0

面试题45:圆圈中最后剩下的数字

题目:0,1,…,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
典型的约瑟夫(Josephuse)环问题:
例如,0、1、2、3、4这5个数字组成一个圆圈(如图6.2所示),从数字0开始每次删除第3个数字,则删除的前四个数字依次是2、0、4、1,因此最后剩下的数字是3
数组

题目梳理

解法一:创建环形链表

将原始数组转换成一个环形链表;依次删除节点即可。
每删除一个数字需要m步运算,总共有n个数字,因此总的时间复杂度是O(mn)。同时这种思路还需要一个辅助链表来模拟圆圈,其空间复杂度是O(n)

解法二:寻找输出数字的规律

递推公式:f[i] = (f[i-1]+m)%i (i>1),f[1] = 0
下一轮的输出是在上一轮输出中加上m之后取余。
假设有10个人,即n = 10,0 1 2 3 4 5 6 7 8 9 选择 m = 3:
那么第一个人(2)出列后的序列为:0 1 3 4 5 6 7 8 9 ,
下一次从3开始删除,即:3 4 5 6 7 8 9 0 1 (i)
可以将该式转换为:0 1 2 3 4 5 6 7 8 (ii)
寻找规律:那么则有 ((ii)+ 3 )% 10 = (i)

时间复杂度是O(n),空间复杂度是O(1)

class Solution:
    def LastRemaining_Solution(self, n, m):
        if n < 1 or m < 1:
            return -1
        remainIndex = 0
        for i in range(1, n+1):
            remainIndex = (remainIndex + m) % i
        return remainIndex
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值