leetcode刷题开始啦, 每天记录几道题.
目录
剑指 Offer 54. 二叉搜索树的第k大节点
题目描述
给定一棵二叉搜索树,请找出其中第 k 大的节点的值。
思路
右中左的遍历相当于按递减的顺序在打印这个二叉搜索树的节点, 我们可以把打印操作改成计数, 当计数到 k 时记录此时节点的值. 最后返回这个值.
python
class Solution:
def __init__(self):
self.counter = 0
self.res = int()
def kthLargest(self, root: TreeNode, k: int) -> int:
def inOrder(root):
if not root:
return
inOrder(root.right)
self.counter += 1
if self.counter == k:
self.res = root.val
inOrder(root.left)
inOrder(root)
return self.res
剑指 Offer 55 - I. 二叉树的深度
题目描述
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
思路
可以用一个成员变量来维护深度, 通过打擂台的方式来更新深度.
用一个深度搜索来往下挖到每个叶节点.
python
class Solution:
def __init__(self):
self.depth = -1
def maxDepth(self, root: TreeNode) -> int:
def dig(root, dps):
if not root:
if dps > self.depth:
self.depth = dps
return
dps += 1
dig(root.left, dps)
dig(root.right, dps)
dps -= 1
dig(root, 0)
return self.depth
剑指 Offer 55 - II. 平衡二叉树
题目描述
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路
看这个定义, 可以用递归的方式, 判断每个节点的左右子树是否平衡. 如果有子树不平衡, 就整个不平衡.
用一个深度函数测量子树的深度. 如果已经得到不平衡的子树, 就让返回值为 -1, 这样就不用再继续递归下去了, 剪枝. 否则返回当前的深度. 这样的话, 实际上可以通过根节点的深度函数返回值判断是否平衡. 如果根节点的深度函数返回 -1, 就说明不平衡了.
python
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def depth(root):
if not root:
return 0
left_dps = depth(root.left)
right_dps = depth(root.right)
if left_dps == -1 or right_dps == -1 or abs(left_dps - right_dps) > 1:
return -1
else:
return max(left_dps, right_dps) + 1
return depth(root) >= 0
剑指 Offer 56 - I. 数组中数字出现的次数
题目描述
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路
这个时间复杂度和空间复杂度实在是难倒我了. 我记得只有一个数字只出现一次的时候可以全部异或一次. 异或操作, 两个数相同异或为0, 两个数不同异或为1. 但是怎么处理两个数只出现一次呢. 看了题解, 原来是分组异或.
设两个目标为 a b. 把全部的数异或一遍, 得到的结果 x 就是 a ^ b. 如果 x 的某一位是1, 意味着 a 和 b 在这一位上不同. 那么把这一位上是 0 的数分成一组, 是 1 的数分成一组, a 和 b 就被分到两个组里了. 再两个组分别异或, 就得到 a 和 b 了.
python
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
x = 0
for num in nums:
x = x ^ num
digit = 0
while (x & 1) != 1:
digit += 1
x = x >> 1
res1 = res2 = 0
for num in nums:
if (num >> digit) & 1:
res1 = res1 ^ num
else:
res2 = res2 ^ num
return [res1, res2]
剑指 Offer 56 - II. 数组中数字出现的次数 II
题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
思路
这个题倒是没要求时间空间复杂度. 可以用笨办法, 用字典存出现次数, 再遍历字典.
我看见用统计各二进制位上一的个数再对3求模的, 实在太能想了. 干, 我怎么没有这么聪明的脑子? 就算想到了这个点子, 我也不知道怎么弄. 唉. 人和人的差距怎么这么大!
python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
dic = {}
for num in nums:
if num in dic:
dic[num] += 1
else:
dic[num] = 1
for num in dic:
if dic[num] == 1:
return num
剑指 Offer 57. 和为s的两个数字
题目描述
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
思路
我还是用哈希表吧. 但是好像可以用双指针? 我想想. 两个指针分别指左边右边, 求和, 如果大了, 右指针就往左移. 如果小了, 左指针向右移.这样应该更快.
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
left, right = 0, len(nums)-1
while left < right:
check = nums[left] + nums[right]
if check == target:
return [nums[left], nums[right]]
if check > target:
right -= 1
else:
left += 1
剑指 Offer 57 - II. 和为s的连续正数序列
题目描述
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
思路
刚刚用了双指针, 现在接着用! 首先, 如果只有两个元素, 那肯定是 target // 2 附近. 再大就不可能小于等于 target 了, 所以判断的范围应该是 1~target//2+1. 区间为 [left, right], 初始从 [1,2] 开始. 求和为
s
u
m
=
(
l
e
f
t
+
r
i
g
h
t
)
(
r
i
g
h
t
−
l
e
f
t
+
1
)
2
sum = \frac{(left + right)(right - left+1)}{2}
sum=2(left+right)(right−left+1)
- 如果和小于target, 说明区间不够长, right += 1.
- 如果等于, 就把这个区间添加到result里. 接下来就要找新的, 把左端点右移: left += 1
- 如果大于, 说明区间长了, 更新左端点 left += 1
- 如果左端点到了 target // 2 + 1, 就不会再有可能的结果了.
我用 sum 算了一下最开始右端点的位置想稍微能快点, 但结果并不好, 反而还差些. 可能算这个根号浪费的时间多于一次 O(target/2) 的遍历.
python
class Solution:
def findContinuousSequence(self, target: int) -> List[List[int]]:
res = []
if target < 3:
return res
left, right = 1, int((math.sqrt(1 + 8*target) - 1) / 2)
end_flag = target // 2 + 1
while left < end_flag and left < right:
Sum = (left + right) * (right - left + 1) / 2
if Sum == target:
res.append([i for i in range(left, right+1)])
left += 1
elif Sum < target:
right += 1
else:
left += 1
return res
剑指 Offer 58 - I. 翻转单词顺序
题目描述
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
思路
当然是马上想到用内置函数啦! 但是面试这么搞可能会被写成段子放到知乎博诸君一乐, 还是算了. 但在这里可以写写, 供自己一乐.
正经的想法还是用双指针, 找到每一个单词, 添加进一个栈, 然后依次弹出.
python
内置函数
class Solution:
def reverseWords(self, s: str) -> str:
return " ".join(s.split()[::-1])
正经写法
class Solution:
def reverseWords(self, s: str) -> str:
n = len(s)
words = []
res = ""
left = right = 0
while left < n:
if s[left] == " ":
left += 1
right = left
continue
while right < n and s[right] != " ":
right += 1
words.append(s[left:right])
left = right
while words:
res += words.pop() + " "
return res[:-1]
剑指 Offer 58 - II. 左旋转字符串
题目描述
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
思路
这个我不知道难点在哪里? 如果不用切片函数, 就用遍历, 先添加索引从 n ~ len(s) - 1, 再添加 0 ~ n - 1.
python
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
left = s[:n]
right = s[n:]
return right + left
剑指 Offer 59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
思路
和之前的 带最小值的栈 类似, 要定义一个 max 列表来保护出现过的 max. 这个均摊时间复杂度是什么意思呢? 对不起, 我看了答案也不是很明白.
这个 max 的维护和之前的也不一样.
如果添加的值是当前队列的最大值, 那在前面的值弹出之前它都是当前队列的最大值(在不考虑新添加的最大值的情况下). 也就是说, 不存在之前添加的元素会在它被弹出后成为最大值的可能性, 因为它们必须先被弹出. 所以, 可以考虑用一个递减的双端队列维护当前的最大值, 让当前的最大值是这个双端队列的第一个元素, 每次新添加一个元素, 都比较这个元素和这个双端队列队尾的元素, 把比它小的都弹出去.
- 如果添加的值是当前最大值, 那前面添加进来的元素都弹出之前它都是最大值;
- 如果不是最大值, 比如它是第二大的, 那把比它小的值都从最大值队列里弹出, 它将成为最大值队列里的第二个. 当最大值被弹出时, 它就成了当前最大值.
python
import queue
class MaxQueue:
def __init__(self):
self.max = queue.deque()
self.queue = queue.deque()
def max_value(self) -> int:
if not self.max:
return -1
else:
return self.max[0]
def push_back(self, value: int) -> None:
while self.max and self.max[-1] <= value:
self.max.pop()
self.max.append(value)
self.queue.append(value)
def pop_front(self) -> int:
if not self.max:
return -1
ans = self.queue.popleft()
if ans == self.max[0]:
self.max.popleft()
return ans
剑指 Offer 60. n个骰子的点数
题目描述
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率
思路
这个题吧, 我觉得要用动态规划. 用 f[n][x] 表示 n 个骰子点数和为 x 的概率. 转移公式应该是用 n-1 个骰子, 分别掷出 x - i 点, i = 1, …, 6. 的概率乘上1/6再累加.
这样有个问题是可能越界. 但如果用往前走的思路, 就不会了. 遍历 n-1 个骰子的点数 x, f[n-1][x] 会给 f[n][x+i]分别贡献 f[n-1][x]/6 的概率.
用两个数组滚动记录结果. n 个骰子「点数和」的范围为 [n, 6n], 所以新开辟的数组大小为 6n - n + 1 = 5n + 1.
python
class Solution:
def dicesProbability(self, n: int) -> List[float]:
f1 = [1 / 6] * 6
for i in range(2, n + 1):
f2 = [0] * (5 * i + 1)
for j in range(len(f1)):
for k in range(6):
f2[j + k] += f1[j] / 6
f1 = f2
return f1