11.24 51. 鸡蛋掉落
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。
你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。
每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。
无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?
输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。
总结
看了一下题意的视频,难。理解了此题的动态规划思路:如果碎了,次数相当于用剩余的鸡蛋往下降一层。如果没碎,次数相当于用所有的鸡蛋往上一层。可以一直递推到1个鸡蛋或是1层楼的情况。
dp(K,N)=1+ min (max(dp(K−1,X−1),dp(K,N−X)))
待探究
11.25 52. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路
右边停止了向下,向下停止了向左,向左停止了向上,向上停止了回到向右。
最优解
class Solution:
def spiralOrder(self, matrix: list) -> list:
if not matrix or not matrix[0]:
return list()
rows, columns = len(matrix), len(matrix[0])
visited = [[False] * columns for _ in range(rows)]
total = rows * columns
order = [0] * total
dir = [(0, 1), (1, 0), (0, -1), (-1, 0)]
row, column = 0, 0
dir_index = 0
for i in range(total):
order[i] = matrix[row][column]
visited[row][column] = True
nextRow, nextColumn = row + dir[dir_index][0], column + dir[dir_index][1]
if not (0 <= nextRow < rows and 0 <= nextColumn < columns and not visited[nextRow][nextColumn]):
dir_index = (dir_index + 1) % 4
row += dir[dir_index][0]
column += dir[dir_index][1]
return order
总结
dir_index = (dir_index + 1) % 4
是方向循环的关键。
11.27 53. 相似度为k的字符串
如果可以通过将 A 中的两个小写字母精确地交换位置 K 次得到与 B 相等的字符串,我们称字符串 A 和 B 的相似度为 K(K 为非负整数)。
给定两个字母异位词 A 和 B ,返回 A 和 B 的相似度 K 的最小值。
输入:A = "ab", B = "ba"
输出:1
输入:A = "abac", B = "baca"
输出:2
输入:A = "aabc", B = "abca"
输出:2
待探究
最优解和图、环有关。
11.28 54. 不同的二叉搜索树
给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 。
输入:3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:
1 3 3 2 1
/ / /
3 2 1 1 3 2
/ /
2 1 2 3
思路
暴力破解时间复杂度n方。
最优解
递归
class Solution:
@classmethod
def generateTrees(self, n: int) -> list:
def get_ans(start, end):
import itertools
ans = []
# 此时无数字,将null加入结果中
if start > end:
return [None]
# 只有一个数字,当前数字作为一棵树加入结果中
if start == end:
return [TreeNode(start)]
# 每个数字作为根节点
for i in range(start, end+1):
# 得到所有可能的左子树
left_trees = get_ans(start, i-1)
# 得到所有可能的右子树
right_trees = get_ans(i+1, end)
# 组合左子树和右子树
for left_tree, right_tree in itertools.product(left_trees, right_trees):
root = TreeNode(i)
root.left = left_tree
root.right = right_tree
ans.append(root)
return ans
if n == 0:
return [None]
return get_ans(1,n)
动态规划(待研究)
解法3是动态规划,没有读懂此解法的状态转移的原理,待研究。
11.30 55. 摆动排序
给定一个无序的数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。
输入: nums = [1, 5, 1, 1, 6, 4]
输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6]
输入: nums = [1, 3, 2, 2, 3, 1]
输出: 一个可能的答案是 [2, 3, 1, 3, 1, 2]
思路
将数组heapfy,后序遍历。(错误,变成堆后不一定变成二叉搜索树,将数组变成二叉搜索树后,利用后序遍历)
后序遍历可以实现,一定要是个平衡二叉搜索树。
- 堆并不是平衡二叉搜索树
- 堆的父节点要么都大于子节点,要么都小于子节点。
最优解
class Solution:
def wiggleSort(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
if n < 2:return nums
mid = (0 + n-1) // 2 # 中位数索引
# 快速排序中的一次划分
def partition(begin,end):
left,right = begin,end
while left < right:
while left < right and nums[left] < nums[right]:right -= 1
if left < right:
nums[left],nums[right] = nums[right],nums[left]
left += 1
while left < right and nums[left] < nums[right]: left += 1
if left < right:
nums[left],nums[right] = nums[right],nums[left]
right -= 1
return left
# 找到中位数对应的数值
left,right = 0, n-1
while True:
pivot = partition(left,right)
if pivot == mid:break
elif pivot > mid:right = pivot - 1
else:left = pivot + 1
# 三路划分(荷兰旗)
midNum = nums[mid]
left,curr,right = 0, 0, n-1
while curr < right:
if nums[curr] < midNum:
nums[left],nums[curr] = nums[curr],nums[left]
left += 1
curr += 1
elif nums[curr] > midNum:
nums[curr],nums[right] = nums[right],nums[curr]
right -= 1
else:
curr += 1
# 交叉合并
small,big ,_nums = mid,n-1,nums[:]
for i in range(n):
if i%2 == 0:
nums[i] = _nums[small]
small -= 1
else:#big
nums[i] = _nums[big]
big -= 1
总结
例如,对于数组[1,1,2,2,3,3],分割为[1,1,2]和[2,3,3],虽然A和B都出现了2,但穿插后为[1,2,1,3,2,3],满足要求。
而如果2的个数再多一些,即[1,1,2,2,2,3],分割为[1,1,2]和[2,2,3],最终结果为[1,2,1,2,2,3],来自A的2和来自B的2出现在了相邻位置。
出现这一问题是因为重复数在A和B中的位置决定的,因为r在A尾部,B头部,所以如果r个数太多(大于等于(length(nums) + 1)/2),就可能在穿插后相邻。要解决这一问题,我们需要使A的r和B的r在穿插后尽可能分开。一种可行的办法是将A和B反序:
例如,对于数组[1,1,2,2,2,3],分割为[1,1,2]和[2,2,3],分别反序后得到[2, 1, 1]和[3, 2, 2],此时2在A头部,B尾部,穿插后就不会发生相邻了。
因此,我们第一步其实不需要进行排序,而只需要找到中位数即可。
找到中位数后,我们需要利用3-way-partition算法将中位数放在数组中部,同时将小于中位数的数放在左侧,大于中位数的数放在右侧。保证其左侧元素不大于自身,右侧元素不小于自身。我们只需要将数组从中间等分为2个部分,然后反序,穿插,即可得到最终结果。