LeetCode剑指offer题目记录10

leetcode刷题开始啦, 每天记录几道题.

剑指 Offer 61. 扑克牌中的顺子

题目描述

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

思路

只有五张, 所以可以比较暴力. 先判断除开0, 有没有重复, 再判断除开零, 最大和最小的差是否小于5.

遍历, 用一个字典记录数字出现的次数, 如果已经出现过且非零, 返回false. 用两个擂台记录最大最小值. 因为是若干副牌, 所以可以有多于2个零.

python

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        Max, Min = -1, 14
        dic = {}
        for num in nums:
            if num in dic and num != 0:
                return False
            else:
                dic[num] = 1
                if num != 0:
                    if num > Max:
                        Max = num
                    if num < Min:
                        Min = num
        
        return Max - Min < 5

剑指 Offer 62. 圆圈中最后剩下的数字

题目描述

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

思路

把这 n 个数重复着排成长列, 那每次删除的就是索引为 (m - 1) % n 的数.

简单题, 我却又看了答案.

记f(n-1,m)=x 为n-1个数从下标为0开始反复删掉第m个时剩下的数的下标. 那也就是说, 如果从下标为i开始, 就会剩下下标为 (x+i) 的数.

现在在n个数里删掉了一个, 就是下标为 (m - 1) % n 的数. 此时变成n-1个数了. n-1个数从 (m - 1 ) % n + 1 = m % n 的下标开始数, 删掉 m 个应该剩下谁我们刚刚已经知道了, 是下标为 (x + (m - 1 ) % n + 1) = (x + m % n) 的数. 但这个下标可能越界, 所以取个模. (m % n) % n = m % n. 所以最后等于 (x + m) % n.

我觉得不是很简单.

python

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        f = 0
        for i in range(2, n+1):
            f = (f + m) % i
        return f

剑指 Offer 63. 股票的最大利润

题目描述

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

思路

用两个指针, 一个指针记录当前最小值, 另一个指针往后走, 记录之后的各元素和当前最小值的差值. 返回最大的.

python

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n < 2:
            return 0

        max_profit = 0
        Min = prices[0]
        for i in range(1, n):
            Min = min(Min, prices[i])
            max_profit = max(prices[i]-Min, max_profit)
        return max_profit

剑指 Offer 64. 求1+2+…+n

题目描述

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

思路

这个题我真没思路. 只能想到调用内置函数. 又要丢人了.

答案的用逻辑判断符的短路来递归倒是还能看懂.

python

内置函数
class Solution:
    def sumNums(self, n: int) -> int:
        return sum(range(n+1))
短路递归
class Solution:
    def sumNums(self, n: int) -> int:
        return n and (n + self.sumNums(n - 1))

剑指 Offer 65. 不用加减乘除做加法

题目描述

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

思路

对不起我一下又想到调内置函数. 但我知道应该是用位运算.

二进制, 0 + 0 = 0(进位0), 0 + 1 = 1 + 0 = 1(进位0), 1 + 1 = 0(进位1), 不进位可以用异或, 进位可以用与再左移一位. 如果所有位上都不用进位, 那异或的结果就是解; 如果需要进位, 那两个数的和就是 它们异或的结果 和 它们与再左移一位的结果 的和. 由此可以递归实现.

python

class Solution:
    def add(self, a: int, b: int) -> int:
        if b == 0:
            return a
        return add(a ^ b, (a & b) << 1)

剑指 Offer 66. 构建乘积数组

题目描述

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

思路

  • b[i-1] = a[0] * a[1] * … * 1 * a[i] * a[i+1] * … * a[n-1]
  • b[i] = a[0] * a[1] * … * a[i-1] * 1 * a[i+1] * … * a[n-1]

所以对 b[i] 来说, 前 i-1 项的乘积就是 b[i-1] 前 i-1 项的乘积再乘 a[i-1]. 进一步推, b[i] 的后面从 i 开始的项的积, 也可以推出 b[i-1]的后面从 i 开始的项的积. 所以这可以看成是两个动态规划.

python

class Solution:
    def constructArr(self, a: List[int]) -> List[int]:
        n = len(a)
        b = [1] * n
        for i in range(1, n):
            b[i] = b[i-1] * a[i-1]
        last_terms = 1
        for i in range(n-2, -1, -1):
            last_terms *= a[i+1]
            b[i] *= last_terms
        
        return b

剑指 Offer 67. 把字符串转换成整数

题目描述

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231,  231 − 1]。如果数值超过这个范围,请返回  INT_MAX (231 − 1) 或 INT_MIN (−231) 。

思路

看着怪长的. 但和之前的 反转字符串中的单词 是异曲同工的. 用两个指针来找左边的可能的数字. 左指针从左端一路向右移, 直到碰到第一个字符. 如果不是正负号也不是数字, 说明不能转换, 返回0.

  • 如果左指针指到数字: 把右指针移到左指针处, 一路向右, 把已经遇到的数字乘十加上新数字. 如果中间数值超出上界了, 就输出对应的范围. 直到碰到边界或者右指针指向的元素不是数字.
  • 如果左指针指到正负号, 标记一下正负, 然后左右指针都指向正负号之后的字符. 然后按上面的情况处理.

用字典存对应的字符和数字转换.

越界判断可以用 / 10 判断是否发生变化来处理. 但python比较灵活, 可以直接赋值.

python

class Solution:
    def strToInt(self, string: str) -> int:
        n = len(string)
        if n == 0:
            return 0

        dic = {"0":0, "1":1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9}
        ans = 0
        low_bound, high_bound = 2 ** 31, 2 ** 31 - 1
        left = right = 0
        while left < n and string[left] == " ":
            left += 1
        if left >= n:
            return 0
        elif string[left] in dic:
            right = left
            while right < n and string[right] in dic:
                ans *= 10 
                ans += dic[string[right]]
                if ans > high_bound:
                    return high_bound
                right += 1
        elif string[left] == "+" or string[left] == "-":
            right = left + 1
            while right < n and string[right] in dic:
                ans *= 10 
                ans += dic[string[right]]
                if string[left] == "+" and ans > high_bound:
                    return high_bound
                if string[left] == "-" and ans > low_bound:
                    return -low_bound
                right += 1
            if string[left] == "-":
                return -ans
        return ans

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

题目描述

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路

这是一个二叉搜索树, 所以两个节点的共同祖先的值应该在两个节点的值之间. 一个一个节点检查, 如果两个节点的值都比当前节点小, 那它们都在当前节点的左子树里; 如果都大, 就在右子树里. 相应的最近公共祖先也就在左子树或者右子树里. 如果落在两个中间, 那这个节点就是一个祖先, 由于二叉搜索树的性质, 两个目标节点不可能都在当前这个节点的任一个子树里, 所以当前节点就是最近的公共祖先.

python

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        ancestor = root

        low, high = p.val, q.val
        if low > high:
            low, high = high, low
        
        while ancestor.val < low or ancestor.val > high:
            if ancestor.val < low:
                ancestor = ancestor.right
            if ancestor.val > high:
                ancestor = ancestor.left
                
        return ancestor

剑指 Offer 68 - II. 二叉树的最近公共祖先

题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路

这个题没有二叉搜索树那么好的性质了. 一个思路是遍历两遍, 找到这两个节点, 并且记录路径. 那么两个路径的最后一个公共节点就是最近公共祖先.

太慢了. 看了看题解

如果节点 root 是 p 和 q 的最近公共祖先,

  • p, q 分别在 root 的左右子树里
  • p 或 q 是 root, 另一个节点在 root 的子树里.

第二种情况可以直接判断. 所以只考虑第一种.

  1. 检查 root 的左 (右) 子树, 如果遇到 p 或 q 就返回, 如果检查完都没有遇到, 返回null.
  2. 如果左右子树都有非空返回值, 说明 p 和 q 分别位于两侧, root 就是最近祖先. 因为这个检查是递归从最下面开始的, 所以它的确是最近的. 所以返回当前节点.
  3. 如果某一个子树返回空值, 说明两个节点都在另一个子树里, 最近公共祖先也一定在另一个子树里, 并且一定会被另一个子树的检查结果返回, 所以直接返回另一个子树的检查结果.

python

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q:
            return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if not left:
            return right
        if not right:
            return left
        return root
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值