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 的子树里.
第二种情况可以直接判断. 所以只考虑第一种.
- 检查 root 的左 (右) 子树, 如果遇到 p 或 q 就返回, 如果检查完都没有遇到, 返回null.
- 如果左右子树都有非空返回值, 说明 p 和 q 分别位于两侧, root 就是最近祖先. 因为这个检查是递归从最下面开始的, 所以它的确是最近的. 所以返回当前节点.
- 如果某一个子树返回空值, 说明两个节点都在另一个子树里, 最近公共祖先也一定在另一个子树里, 并且一定会被另一个子树的检查结果返回, 所以直接返回另一个子树的检查结果.
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