日渐头秃的代码日记 -- 第323场周赛

41 阅读5分钟

今天的周赛很有趣,没有公司赞助了,就变成了力扣自己出题,doge

一、删除每行中的最大值

给你一个 m x n 大小的矩阵 grid ,由若干正整数组成。

执行下述操作,直到 grid 变为空矩阵:

  • 从每一行删除值最大的元素。如果存在多个这样的值,删除其中任何一个。
  • 将删除元素中的最大值与答案相加。

注意 每执行一次操作,矩阵中列的数据就会减 1 。

返回执行上述操作后的答案。

 

示例 1:

输入: grid = [[1,2,4],[3,3,1]]
输出: 8
解释: 上图展示在每一步中需要移除的值。
- 在第一步操作中,从第一行删除 4 ,从第二行删除 3(注意,有两个单元格中的值为 3 ,我们可以删除任一)。在答案上加 4 。
- 在第二步操作中,从第一行删除 2 ,从第二行删除 3 。在答案上加 3 。
- 在第三步操作中,从第一行删除 1 ,从第二行删除 1 。在答案上加 1 。
最终,答案 = 4 + 3 + 1 = 8

示例 2:

输入: grid = [[10]]
输出: 10
解释: 上图展示在每一步中需要移除的值。
- 在第一步操作中,从第一行删除 10 。在答案上加 10 。
最终,答案 = 10

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • 1 <= grid[i][j] <= 100

解析

题目描述的很复杂,都是障眼法,可以用两种方法来完成。

一种是按照题目意思来模拟,反正最大也才50*50.

第二种方法就是把每一行排序,然后选出每一列的最大值即可。 比赛时候选择的是第二种方法。

代码

class Solution:
    def deleteGreatestValue(self, grid: List[List[int]]) -> int:
        row = len(grid)
        col = len(grid[0])
        for i in range(row):
            grid[i] = sorted(grid[i])[::-1]
        res = 0
        for j in range(col):
            tmp = 0
            for i in range(row):
                tmp = max(tmp, grid[i][j])
            res += tmp
        return res

二、数组中最长的方波

给你一个整数数组 nums 。如果 nums 的子序列满足下述条件,则认为该子序列是一个 方波 :

  • 子序列的长度至少为 2 ,并且
  • 将子序列从小到大排序 之后 ,除第一个元素外,每个元素都是前一个元素的 平方 。

返回 **nums **中 最长方波 的长度,如果不存在 方波 **则返回 **-1 。

子序列 也是一个数组,可以由另一个数组删除一些或不删除元素且不改变剩余元素的顺序得到。

 

示例 1 :

输入: nums = [4,3,6,16,8,2]
输出: 3
解释: 选出子序列 [4,16,2] 。排序后,得到 [2,4,16] 。
- 4 = 2 * 2.
- 16 = 4 * 4.
因此,[4,16,2] 是一个方波.
可以证明长度为 4 的子序列都不是方波。

示例 2 :

输入: nums = [2,3,5,6,7]
输出: -1
解释: nums 不存在方波,所以返回 -1 。

 

提示:

  • 2 <= nums.length <= 10^5
  • 2 <= nums[i] <= 10^5

解析

注意到最大的数值是十万,最小是2,所以不会存在全1的情况。同时,定义的方波是前一个数字的平方,因此最大的结果,也只能是2 4 16 256 65536。十万的平方根是大于316小于317,因此可以直接从2遍历到316(含316)依次看每个数字的平方、再平方、再再平方。。。是否都在原数组当中,当然最多就看5次就行了。

代码

from collections import defaultdict
class Solution:
    def longestSquareStreak(self, nums: List[int]) -> int:
        # 2 4 16 256 65536
        # 3 9 81 6561
        # 5 25 625
        # 6 36 1296
        # ...
        # 315 99225
        # 316 over
        d = defaultdict(int)
        for num in nums:
            d[num] = 1
        res = 0
        for x in range(2, 317):
            tmp = 0
            for i in range(5):
                if d[x ** (2**i)] == 1:
                    tmp += 1
                else:
                    break
            res = max(tmp, res)
        if res <= 1:
            res = -1
        return res

三、设计内存分配器

给你一个整数 n ,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。

请你设计一个具备以下功能的内存分配器:

  1. 分配 一块大小为 size 的连续空闲内存单元并赋 id mID 。
  2. 释放 给定 id mID 对应的所有内存单元。

注意:

  • 多个块可以被分配到同一个 mID 。
  • 你必须释放 mID 对应的所有内存单元,即便这些内存单元被分配在不同的块中。

实现 Allocator 类:

  • Allocator(int n) 使用一个大小为 n 的内存数组初始化 Allocator 对象。
  • int allocate(int size, int mID) 找出大小为 size 个连续空闲内存单元且位于  最左侧 的块,分配并赋 id mID 。返回块的第一个下标。如果不存在这样的块,返回 -1 。
  • int free(int mID) 释放 id mID 对应的所有内存单元。返回释放的内存单元数目。

 

示例:

输入
["Allocator", "allocate", "allocate", "allocate", "free", "allocate", "allocate", "allocate", "free", "allocate", "free"]
[[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]]
输出
[null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]

解释
Allocator loc = new Allocator(10); // 初始化一个大小为 10 的内存数组,所有内存单元都是空闲的。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 0 。内存数组变为 [1, , , , , , , , , ]。返回 0 。
loc.allocate(1, 2); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,2, , , , , , , , ]。返回 1 。
loc.allocate(1, 3); // 最左侧的块的第一个下标是 2 。内存数组变为 [1,2,3, , , , , , , ]。返回 2 。
loc.free(2); // 释放 mID 为 2 的所有内存单元。内存数组变为 [1, ,3, , , , , , , ] 。返回 1 ,因为只有 1 个 mID 为 2 的内存单元。
loc.allocate(3, 4); // 最左侧的块的第一个下标是 3 。内存数组变为 [1, ,3,4,4,4, , , , ]。返回 3 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,1,3,4,4,4, , , , ]。返回 1 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 6 。内存数组变为 [1,1,3,4,4,4,1, , , ]。返回 6 。
loc.free(1); // 释放 mID 为 1 的所有内存单元。内存数组变为 [ , ,3,4,4,4, , , , ] 。返回 3 ,因为有 3 个 mID 为 1 的内存单元。
loc.allocate(10, 2); // 无法找出长度为 10 个连续空闲内存单元的空闲块,所有返回 -1 。
loc.free(7); // 释放 mID 为 7 的所有内存单元。内存数组保持原状,因为不存在 mID 为 7 的内存单元。返回 0 。

 

提示:

  • 1 <= n, size, mID <= 1000
  • 最多调用 allocate 和 free 方法 1000 次

解析

主要是读明白题目意思,并且有很多corner case需要考虑到,多提交几次就知道了。照着题目意思模拟即可。

代码

from collections import defaultdict
class Allocator:

    def __init__(self, n: int):
        self.n = n
        self.memory = [None] * n
        self.bits = defaultdict(int)
        self.used = defaultdict(list)

    def allocate(self, size: int, mID: int) -> int:
        index = 0
        while index < self.n:
            if self.bits[index] == 1:
                index += 1
                continue
            else:
                i = index
                if i < self.n and i + size <= self.n:
                    x = sum([self.bits[m] for m in range(i, i+size)])
                    if x == 0:
                        for m in range(i, i+size):
                            self.bits[m] = 1
                            self.memory[m] = mID
                        self.used[mID].extend(range(i, i+size))
                        return i
                    else:
                        for delta, b in enumerate([self.bits[m] for m in range(i, i+size)]):
                            if b == 1:
                                index += delta
                                break
                else:
                    break
        return -1              

    def free(self, mID: int) -> int:
        if mID in self.used.keys():
            length = len(self.used[mID])
            for x in self.used[mID]:
                self.bits[x] = 0
                self.memory[x] = None
            self.used[mID] = []
            return length
        else:
            return 0
            

四、矩阵查询可获得的最大分数

给你一个大小为 m x n 的整数矩阵 grid 和一个大小为 k 的数组 queries 。

找出一个大小为 k 的数组 answer ,且满足对于每个整数 queres[i] ,你从矩阵 左上角 单元格开始,重复以下过程:

  • 如果 queries[i] 严格 大于你当前所处位置单元格,如果该单元格是第一次访问,则获得 1 分,并且你可以移动到所有 4 个方向(上、下、左、右)上任一 相邻 单元格。
  • 否则,你不能获得任何分,并且结束这一过程。

在过程结束后,answer[i] 是你可以获得的最大分数。注意,对于每个查询,你可以访问同一个单元格 多次 。

返回结果数组 answer 。

 

示例 1:

输入: grid = [[1,2,3],[2,5,7],[3,5,1]], queries = [5,6,2]
输出: [5,8,1]
解释: 上图展示了每个查询中访问并获得分数的单元格。

示例 2:

输入: grid = [[5,2,1],[1,1,2]], queries = [3]
输出: [0]
解释: 无法获得分数,因为左上角单元格的值大于等于 3

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 10^5
  • k == queries.length
  • 1 <= k <= 10^4
  • 1 <= grid[i][j], queries[i] <= 10^6

解析

读明白题目意思,其实就是查看包含左上角且每个格子都小于一个数值的联通区域的面积最大是多少。自然而然地就想到了dfs来写,然而超时了

代码

class Solution:
    def maxPoints(self, grid: List[List[int]], queries: List[int]) -> List[int]:
        self.row_length = len(grid)
        self.col_length = len(grid[0])
        res = []
        for k in queries:
            g = copy.deepcopy(grid)
            ans = self.dfs(g, 0, 0, k)
            res.append(ans)
        return res

    def inArea(self, x, y) -> bool:
        if 0 <= x < self.row_length and 0 <= y < self.col_length:
            return True
        else:
            return False
    
    def dfs(self, grid, x, y, k):
        if not self.inArea(x, y):  # 出界终止条件
            return 0
        if grid[x][y] >= k or grid[x][y] == 0:
            return 0
        grid[x][y] = 0
        ans = 1
        for a, b in [(x-1, y), (x, y+1), (x+1, y), (x, y-1)]: # 四个方向,上、右、下、左
            ans += self.dfs(grid, a, b, k)
        return ans

赛后看了一下大佬们的做法,不约而同的采用了并查集。离线+并查集(Python/Java/C++/Go) - 矩阵查询可获得的最大分数 - 力扣(LeetCode)

class Solution:
    def maxPoints(self, grid: List[List[int]], queries: List[int]) -> List[int]:
        m, n = len(grid), len(grid[0])
        mn = m * n

        # 并查集模板
        fa = list(range(mn))
        size = [1] * mn
        def find(x: int) -> int:
            if fa[x] != x:
                fa[x] = find(fa[x])
            return fa[x]
        def merge(from_: int, to: int) -> None:
            from_ = find(from_)
            to = find(to)
            if from_ != to:
                fa[from_] = to
                size[to] += size[from_]

        # 矩阵元素从小到大排序,方便离线
        a = sorted((x, i, j) for i, row in enumerate(grid) for j, x in enumerate(row))

        ans, j = [0] * len(queries), 0
        # 查询的下标按照查询值从小到大排序,方便离线
        for i, q in sorted(enumerate(queries), key=lambda p: p[1]):
            while j < mn and a[j][0] < q:
                _, x, y = a[j]
                # 枚举周围四个格子,值小于 q 才可以合并
                for x2, y2 in (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1):
                    if 0 <= x2 < m and 0 <= y2 < n and grid[x2][y2] < q:
                        merge(x * n + y, x2 * n + y2)  # 把坐标压缩成一维的编号
                j += 1
            if grid[0][0] < q:
                ans[i] = size[find(0)]  # 左上角的连通块的大小
        return ans

作者:endlesscheng
链接:https://leetcode.cn/problems/maximum-number-of-points-from-grid-queries/solution/by-endlesscheng-qeei/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。