第11周leetcode记录

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个部分,然后反序,穿插,即可得到最终结果。

posted @ 2020-12-06 23:16  Jimmyhe  阅读(83)  评论(0编辑  收藏  举报