剑指offer系列之抽象建模能力(p44-47)

34 篇文章 0 订阅
15 篇文章 0 订阅

44. 扑克牌顺子

题目描述

本题知识点: 字符串
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路

分析可知,必须要满足三个条件:

  • 数组长度为5
  • 除零以外没有重复数
  • 最大值减去最小值小于5

显然第一条和第三条非常容易实现,因此我们主要考虑第二条的实现。

思路一

由于只有14个可能的取值(0-13),我们可以设定一个长度为14的数组来记录这14个数出现的次数。同时我们设定一个最大值和最小值来标记相应的值,需要注意的是这两个值都不取0。由于使用了辅助计数数组,该方法的时间复杂度和空间复杂度都为 O ( n ) O(n) O(n)

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        l = len(numbers)
        if l != 5:
            return False
        minNum, maxNum = 14, -1
        count = [0 for i in range(14)]
        # 得到了两个最值并且确定了是否有除0外的重复数值
        for i in range(l):
            count[numbers[i]] += 1
            if numbers[i] == 0:
                continue
            if count[numbers[i]] > 1:
                return False
            if numbers[i] > maxNum:
                maxNum = numbers[i]
            if numbers[i] < minNum:
                minNum = numbers[i]
        if maxNum - minNum < 5:
            return True
        else:
            return False
思路二

该方法首先对数组进行排序,再对数组进行遍历。遍历的过程中记录0出现的个数并判断前后两个数是否相等,若循环可以执行完成,则说明一定不存在除0外的两个相同的数字,此时只需要判断最大值和最小值之差是否满足条件即可。该方法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1)

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        l = len(numbers)
        if l != 5:
            return False
        numbers = sorted(numbers)
        count0 = 0
        # 统计了0的个数->得到最小值的位置;并判断了是否有除0外的重复数
        for i in range(l):
            if numbers[i] == 0:
                count0 += 1
                continue
            if i < l-1 and numbers[i] == numbers[i+1]:
                return False
        # 函数运行到这一步说明一定没有除零外的重复数
        if numbers[l-1] - numbers[count0] < 5:
            return True
        else:
            return False

45. 圆圈中最后剩下的数

题目描述

本题知识点: 模拟
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

思路

思路一:模拟法

该方法直接用数组模拟整个流程,时间复杂度为 O ( m n ) O(mn) O(mn),这个时间复杂度非常高,差点没通过。空间复杂度为 O ( n ) O(n) O(n)

# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n < 1 or m < 1:
            return -1
        array = list(range(n))
        count, i, step = n, -1, 0
        while count > 0:
            i += 1
            # 模拟环
            if i >= n:
                i = 0
            # 跳过已经被删除的元素
            if array[i] == -1:
                continue
            # 经过没有被删除的步数才算是有效的
            step += 1
            # 直接写step==m会超时
            if step == (m%count) or step == m:
                array[i] = -1
                step = 0
                count -= 1
        return i
思路二:归纳法

假设孩子编号为 0 , 1 , 2... n 0,1,2 ... n 0,1,2...n,那么第一次出局的孩子编号为 k = ( m − 1 ) % n k=(m-1)\%n k=(m1)%n,我们假设根据这样的序列得到的最后一个孩子为 f ( m , n ) f(m,n) f(m,n),显然函数f跟m,n相关。第二次问题变为 k + 1 , k + 2... n , 1 , 2... k − 1 k+1,k+2...n,1,2...k-1 k+1,k+2...n,1,2...k1,我们设最后一个孩子为 q ( m , n − 1 ) q(m, n-1) q(m,n1)。显然有 f ( m , n ) = q ( m , n − 1 ) f(m,n)=q(m,n-1) f(m,n)=q(m,n1)。下面我们考虑:
k + 1 , k + 2... n , 1 , 2... k − 1 = &gt; q ( m , n − 1 ) k+1,k+2...n,1,2...k-1=&gt;q(m,n-1) k+1,k+2...n,1,2...k1=>q(m,n1)
1 , 2 , 3... n − 1 = &gt; f ( m , n − 1 ) 1,2,3...n-1=&gt;f(m,n-1) 1,2,3...n1=>f(m,n1)
不难看出,上面的式子左右两边存在一样的变化规律。我们假设存在一个函数g可以把 k + 1 , k + 2... n , 1 , 2... k − 1 k+1,k+2...n,1,2...k-1 k+1,k+2...n,1,2...k1映射为 0 , 1 , 2... n 0,1,2 ... n 0,1,2...n,不难求出 g ( x ) = ( x − k − 1 ) % n g(x)=(x-k-1)\%n g(x)=(xk1)%n,那么显然g也可以把 q ( m , n − 1 ) q(m,n-1) q(m,n1)映射为 f ( m , n − 1 ) f(m,n-1) f(m,n1),即 g ( q ( m , n − 1 ) ) = f ( m , n − 1 ) g(q(m,n-1))=f(m,n-1) g(q(m,n1))=f(m,n1)。当然也可以得出:
q ( m , n − 1 ) = g − 1 ( f ( m , n − 1 ) ) q(m, n-1)=g^{-1}(f(m,n-1)) q(m,n1)=g1(f(m,n1))
又有 g − 1 ( x ) = ( x + k + 1 ) % n g^{-1}(x)=(x+k+1)\%n g1(x)=(x+k+1)%n,因此
q ( m , n − 1 ) = g − 1 ( f ( m , n − 1 ) ) = ( f ( m , n − 1 ) + k + 1 ) % n = ( f ( m , n − 1 ) + m ) % n q(m, n-1)=g^{-1}(f(m,n-1))=(f(m,n-1)+k+1)\%n=(f(m,n-1)+m)\%n q(m,n1)=g1(f(m,n1))=(f(m,n1)+k+1)%n=(f(m,n1)+m)%n
因此:
f ( m , n ) = 0 , n = 1 f(m,n)=0, n=1 f(m,n)=0,n=1
f ( m , n ) = ( f ( m , n − 1 ) + m ) % n , n &gt; = 2 f(m,n)=(f(m,n-1)+m)\%n,n&gt;=2 f(m,n)=(f(m,n1)+m)%n,n>=2
该方法时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n < 1 or m < 1:
            return -1
        res = 0
        # 长度为2-n
        for i in range(2, n+1):
            res = (res + m) % i
        return res

46. 求 1 + 2 + . . . + n 1+2+...+n 1+2+...+n

题目描述

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

思路

该题要求不能用循环的方法,因此想到递归的方法。递归的方法难点在于如何判断终止条件,因为题目还规定了不能使用条件判断语句。因此使用0 and others = 0这个特性来当做终止条件。

# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here
         return n and (n + self.Sum_Solution(n-1))

下面这种方法只是练习一下内置函数,当然使用sum函数会更简单,但是不属于题目考察知识点。

# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here
         def add(x, y):
            return x + y
         return reduce(add, range(1, n+1))

47. 不用加减乘除做加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

思路

下面的方法来源于牛客网评论。
首先看十进制是如何做的: 5 + 7 = 12 5+7=12 5+7=12,三步走

  • 第一步:相加各位的值,不算进位,得到 2 2 2
  • 第二步:计算进位值,得到 10 10 10. 如果这一步的进位值为 0 0 0,那么第一步得到的值就是最终结果。
  • 第三步:重复上述两步,只是相加的值变成上述两步的得到的结果 2 2 2 10 10 10,得到 12 12 12
    5 + 7 = 2 + 10 = 12 + 0 = 12 5+7=2+10=12+0=12 5+7=2+10=12+0=12

同样我们可以用三步走的方式计算二进制值相加: 5 − 101 5-101 5101 7 − 111 7-111 7111

  • 第一步:相加各位的值,不算进位,得到 010 010 010,二进制每位相加就相当于各位做异或操作, 101 ⨁ 111 101\bigoplus111 101111
  • 第二步:计算进位值,得到 1010 1010 1010,相当于各位做与操作得到 101 101 101,再向左移一位得到 1010 1010 1010 ( 101 &amp; 111 ) &lt; &lt; 1 (101\&amp;111)&lt;&lt;1 (101&111)<<1
  • 第三步重复上述两步, 各位相加 010 ⨁ 1010 = 1000 010\bigoplus1010=1000 0101010=1000,进位值为 100 = ( 010 &amp; 1010 ) &lt; &lt; 1 100=(010\&amp;1010)&lt;&lt;1 100=(010&1010)<<1
  • 继续重复上述两步: 1000 ⨁ 100 = 1100 1000\bigoplus100 = 1100 1000100=1100,进位值为 0 0 0,跳出循环, 1100 1100 1100为最终结果。

python没有无符号左移操作,所以需要越界检查.

# -*- coding:utf-8 -*-
class Solution:
    def Add(self, num1, num2):
        # write code here
        while num2 != 0:
            # 这两步不设置边界会出错
            tmp = (num1 ^ num2) & 0xFFFFFFFF
            num2 = ((num1 & num2) << 1) & 0xFFFFFFFF
            num1 = tmp
        return num1 if num1<=0x7FFFFFFF else ~(num1^0xFFFFFFFF)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值