LeetCode笔记:Weekly Contest 204 比赛记录

  • LeetCode笔记:Weekly Contest 204
    • 0. 赛后总结
    • 1. 题目一
      • 1. 解题思路
      • 2. 代码实现
    • 2. 题目二
      • 1. 解题思路
      • 2. 代码实现
    • 3. 题目三
      • 1. 解题思路
      • 2. 代码实现
      • 3. 算法优化
    • 4. 题目四
      • 1. 解题思路
      • 2. 代码实现

0. 赛后总结

这次的比赛简直了,直接从结论开始说吧,居然才做出两题,虽然排名还是差不多1500的样子,不算太难看,但让人火大的是最后一题事实上是能够做出来的,思路完全正确,最后居然是开始排列组合的统计计算上由于python自己的精度问题导致结果一直不对,真的是让人蛋疼。

然后赛后看了一下别人的实现,几乎和我一毛一样,唯一的区别就是我自己在写 C n m C_n^m Cnm函数的实现,然后对方直接调用了python中math库里的实现。。。

emmmm…

好吧,计算机里面精度的把控确实是一个问题,别人知道这个库函数我不知道本身也就是我自己的问题,但是只是因为这个奇葩的原因导致最后一题一直通不过终究还是异常的不甘心。

不爽啊!!!

1. 题目一

给出题目一的试题链接如下:

  • 5499. 重复至少 K 次且长度为 M 的模式

1. 解题思路

这题没啥好说的,就是按照题目说的直接暴力枚举就可以了,唯一需要注意的就是稍微边界条件。

2. 代码实现

给出python的代码实现如下:

class Solution:
    def containsPattern(self, arr: List[int], m: int, k: int) -> bool:
        def is_match(idx, m, k):
            for i in range(m, m*k):
                if arr[idx + i % m] != arr[idx + i]:
                    return False
            return True
        
        n = len(arr)
        for i in range(n+1 - m*k):
            if is_match(i, m, k):
                return True
        return False

提交代码评测得到:耗时36ms,占用内存14MB。

2. 题目二

给出题目二的试题链接如下:

  • 5500. 乘积为正数的最长子数组长度

1. 解题思路

这一题首先要注意的是,每当出现一次0时,列表就被分成了前后两部分,我们需要分别考察前后两部分中的最长序列长度。

另一方面,我们需要考察的,对一个非零的序列,我们要怎么找到最长的积为正数的序列。

这个可以参考求和的方式,我们求出每个连乘到某一个元素之后的正负值,只需要比对头尾均为正值的序列长度和头尾均为负值的最大序列长度即可。

2. 代码实现

给出python的代码实现如下:

class Solution:
    def getMaxLen(self, nums: List[int]) -> int:
        def get_max(sign):
            n = len(sign)
            ans = n-1 - sign[::-1].index(1)
            if -1 in sign:
                st = sign.index(-1)
                ed = n-1 - sign[::-1].index(-1)
                ans = max(ans, ed - st)
            return ans
        
        sign = [1]
        ans = 0
        for n in nums:
            if n > 0:
                sign.append(sign[-1])
            elif n < 0:
                sign.append(-sign[-1])
            else:
                ans = max(ans, get_max(sign))
                sign = [1]
        ans = max(ans, get_max(sign))
        return ans

提交代码评测得到:耗时604ms,占用内存28MB。

3. 题目三

给出题目三的试题链接如下:

  • 5501. 使陆地分离的最少天数

1. 解题思路

这道题比赛的时候我没能做出来,甚至没啥靠谱的思路,唯一的想法就是暴力地枚举法求解。即每次将一个点从1变成0,然后考察能否出现隔离的情况。不过当时没能将代码写出来,赛后写了一下代码,果然遇到了超时问题,92个测试样例中仅通过了48个。真的是非常惨了。

2. 代码实现

下面,我们先给出枚举法的代码实现如下。

class Solution:
    def minDays(self, grid: List[List[int]]) -> int:
        n = len(grid)
        m = len(grid[0])
        
        def is_isolated():
            have_visited = set()
            counter = 0
            for i in range(n):
                for j in range(m):
                    if grid[i][j] == 0 or (i, j) in have_visited:
                        continue
                    stack = [(i, j)]
                    counter += 1
                    if counter >= 2:
                        return True
                    while stack != []:
                        x, y = stack.pop(0)
                        have_visited.add((x, y))
                        if x-1 >= 0 and grid[x-1][y] == 1 and (x-1, y) not in have_visited:
                            stack.append((x-1, y))
                        if x+1 < n and grid[x+1][y] == 1 and (x+1, y) not in have_visited:
                            stack.append((x+1, y))
                        if y-1 >= 0 and grid[x][y-1] == 1 and (x, y-1) not in have_visited:
                            stack.append((x, y-1))
                        if y+1 < m and grid[x][y+1] == 1 and (x, y+1) not in have_visited:
                            stack.append((x, y+1))
            return counter == 0
        
        ans = n * m
        
        def dfs(row, col, counter):
            nonlocal ans
            if counter >= ans:
                return
            if is_isolated():
                ans = min(ans, counter)
                return
            for i in range(row, n):
                if i == row:
                    for j in range(col, m):
                        if grid[i][j] == 0:
                            continue
                        grid[i][j] = 0
                        dfs(i, j, counter+1)
                        grid[i][j] = 1
                else:
                    for j in range(m):
                        if grid[i][j] == 0:
                            continue
                        grid[i][j] = 0
                        dfs(i, j, counter+1)
                        grid[i][j] = 1
            return
        
        dfs(0, 0, 0)
        return ans

如前所述,上述代码运行超时,92个测试样例仅能通过48个。

3. 算法优化

后续看了一下头几名的大神们的解法,发现主要有两点区别:

  1. 首先,他们对结果看得更为透彻,答案仅可能是0、1或者2,也就是说,在任何情况下,只需要最多2次操作就可以分离出一片孤立的区域,因此,只需要判断一次操作能否将现有区域分离开即可
  2. 对于区域分离的考察方法,我们采用的出栈时访问,但是后来发现这种方式会导致大量的节点被重复多次访问。其修改方式也极其简单,只需要将其修改为入栈时访问即可。。。

因此,我们修改代码得到:

class Solution:
    def minDays(self, grid: List[List[int]]) -> int:
        n = len(grid)
        m = len(grid[0])
        
        def is_isolated():
            have_visited = set()
            counter = 0
            for i in range(n):
                for j in range(m):
                    if grid[i][j] == 0 or (i, j) in have_visited:
                        continue
                    stack = [(i, j)]
                    have_visited.add((i, j))
                    counter += 1
                    if counter >= 2:
                        return True
                    while stack != []:
                        x, y = stack.pop(0)
                        if x-1 >= 0 and grid[x-1][y] == 1 and (x-1, y) not in have_visited:
                            stack.append((x-1, y))
                            have_visited.add((x-1, y))
                        if x+1 < n and grid[x+1][y] == 1 and (x+1, y) not in have_visited:
                            stack.append((x+1, y))
                            have_visited.add((x+1, y))
                        if y-1 >= 0 and grid[x][y-1] == 1 and (x, y-1) not in have_visited:
                            stack.append((x, y-1))
                            have_visited.add((x, y-1))
                        if y+1 < m and grid[x][y+1] == 1 and (x, y+1) not in have_visited:
                            stack.append((x, y+1))
                            have_visited.add((x, y+1))
            return counter == 0
        
        if is_isolated():
            return 0
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 0:
                    continue
                grid[i][j] = 0
                if is_isolated():
                    return 1
                grid[i][j] = 1
        return 2

此时,重新提交代码即可通过,评测得到:耗时1816ms,占用内存13.7MB,均属于当前最优值。

4. 题目四

给出题目四的试题链接如下:

  • 5502. 将子数组重新排序得到同一个二叉查找树的方案数

1. 解题思路

这一题事实上就是考察能够构成相同二叉查找树的序列总和。我们从中去除掉题中已给出的一个就是我们最终的答案。

考察二叉查找树的序列数目,它可以使用一个递归问题来解决:

  • 对任何一个二叉查找树,它的根节点都是第一个元素,然后左子树中所有的元素都小于根节点,右子树中的所有元素都大于根节点。

因此,对一个二叉查找树,它的输入序列就由三部分组成:

  1. 左子树中的二叉查找树可能的输入序列个数;
  2. 右子树中的二叉查找树可能的输入序列个数;
  3. 当固定一个左子树序列与一个右子树序列时的整体序列排序方式。

其中,前二者可以通过递归的方式获得,对于最后一个问题,他事实上就是一个插入问题,假设左子树中有n个元素,右子树中有m个元素,则可能的排序方式就有 C n + m n = ( n + m ) ! n ! × m ! C_{n+m}^{n} = \frac{(n+m)!}{n! \times m!} Cn+mn=n!×m!(n+m)!中排序方式。

因此,我们就可以得到最终的答案。

2. 代码实现

给出代码实现如下:

from math import factorial

class Solution:
    def numOfWays(self, nums: List[int]) -> int:
        MOD = 10**9 + 7
        
        @lru_cache(None)
        def _c(n, m):
            return factorial(n) // (factorial(n-m) * factorial(m))
        
        def dp(nums):
            if len(nums) <= 1:
                return 1
            left = [i for i in nums if i < nums[0]]
            right = [i for i in nums if i > nums[0]]
            ans = (_c(len(nums)-1, len(left)) * dp(left) * dp(right)) % MOD
            return ans
        
        return dp(nums) - 1

提交代码评测得到:耗时200ms,占用内存19.1MB。

然而,悲剧的是,实际比赛中我的代码并没能通过。原因在于 C n + m n C_{n+m}^{n} Cn+mn函数我是自己实现的,然后python在求解时出现了精度问题导致在处理高阶的数据当中答案出现了小数,与真实值不一致,然后就GG了。

真的是,算了,就当是吃个教训吧,与君共勉。

你可能感兴趣的:(leetcode笔记,leetcode)