2024-01-04 每日一练

发布时间 2024-01-04 17:37:55作者: 逝玄

LeetCode 每日一题

2397. 被列覆盖的最多行数

问题

给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix ;另给你一个整数 numSelect,表示你必须从 matrix 中选择的 不同 列的数量。

如果一行中所有的 1 都被你选中的列所覆盖,则认为这一行被 覆盖 了。

形式上,假设 s = {c1, c2, ...., cnumSelect} 是你选择的列的集合。对于矩阵中的某一行 row ,如果满足下述条件,则认为这一行被集合 s 覆盖:

对于满足 matrix[row][col] == 1 的每个单元格 matrix[row][col](0 <= col <= n - 1),col 均存在于 s 中,或者
row 中 不存在 值为 1 的单元格。
你需要从矩阵中选出 numSelect 个列,使集合覆盖的行数最大化。

返回一个整数,表示可以由 numSelect 列构成的集合 覆盖 的 最大行数 。

解答

  1. 看到矩阵首先想到 状态压缩
  2. 任何题目都先思考 枚举
  3. 看到 枚举为何/二选一 状态时,可以考虑 动态规划,然后才更进一步地考虑贪心
  4. 看到 覆盖问题,可以考虑 DLX 的思想,覆盖问题其实也是搜索问题,而搜索算法,本质上,就是枚举 + 递归
  5. 依据自动机理论,优先查看终止状态的特征

首先思考能否通过枚举实现,显然这是可行的,毕竟它的范围比较小,于是可以直接枚举每种选择,毕竟选择的最多次数也不过是 \(2^12 = 4096\) 次。每次选择可以与原有行进行比较,从而获取所满足的行的个数,求出最大值即为结果

而在枚举中存在技巧,因为给定了 选取 列 的最大个数 numSelect,于是我们可以利用 Gosper's Hack算法进行优化,简而言之,该算法就是求出 长度为 n 的比特序列中,有 k 个元素为 1 、其他元素为 0 的所有序列
它的思想是找到第一个 01 将其改成 10,并其后的连续的 1 移动到序列末尾,显然在这个过程中 得到的序列比原有的打,切在其中间不再存在满足上述条件(有 k 个元素为 1 、其他元素为 0 的所有序列)的序列
设当前序列为 x,其具体实现为

  1. 找到第一个 01 将其改成 10:l = x + x & -x,其中 x & -x表示 x 中从第一个 1 截断后得到的值,被称为 lowbit,此时 第一个 1 后面全为 0
  2. 其后的连续的 1 移动到序列末尾:r = (x ^ l) >> (lowbit 中 0 的个数 + 2),其中,(x ^ l) 将 x第一个 01 将其改成 11,其左边的变成 0,右边的不变,lowbit 中 0 的个数也即是 (x ^ l)的 0 的个数,2 是因为 01 变成了 11 ,其 01 的 1 已经在 l 中了,所以要再右移 2 位
  3. 结果 = l | r

代码:

class Solution:
    def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
        m, n = len(matrix), len(matrix[0])
        mask = [sum(v << j for j, v in enumerate(row)) for i, row in enumerate(matrix)]
        res, limit = 0, 1 << n
        for cur in range(1, limit):
            if cur.bit_count() != numSelect:
                continue
            t = sum((mask[j] & cur) == mask[j] for j in range(m))
            res = max(res, t)
        return res

# 作者:力扣官方题解
# 链接:https://leetcode.cn/problems/maximum-rows-covered-by-columns/solutions/2587986/bei-lie-fu-gai-de-zui-duo-xing-shu-by-le-5kb9/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

def count_trailing_zeros(x):
    return (x & -x).bit_length() - 1

class Solution:
    def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
        m, n = len(matrix), len(matrix[0])
        mask = [sum(v << j for j, v in enumerate(row)) for i, row in enumerate(matrix)]
        res, cur = 0, (1 << numSelect) - 1
        limit = 1 << n
        while cur < limit:
            t = sum((mask[j] & cur) == mask[j] for j in range(m))
            res = max(res, t)
            lb = cur & -cur
            r = cur + lb
            cur = ((r ^ cur) >> count_trailing_zeros(lb) + 2) | r
        return res

# 作者:力扣官方题解
# 链接:https://leetcode.cn/problems/maximum-rows-covered-by-columns/solutions/2587986/bei-lie-fu-gai-de-zui-duo-xing-shu-by-le-5kb9/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

拓展

依据灵神的网站题目顺序

1823. 找出游戏的获胜者

问题

共有 n 名小伙伴一起做游戏。小伙伴们围成一圈,按 顺时针顺序 从 1 到 n 编号。确切地说,从第 i 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置,其中 1 <= i < n ,从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置。

游戏遵循如下规则:

从第 1 名小伙伴所在位置 开始 。
沿着顺时针方向数 k 名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。
你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。
如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。
否则,圈子中最后一名小伙伴赢得游戏。
给你参与游戏的小伙伴总数 n ,和一个整数 k ,返回游戏的获胜者。

解答

很显然地存在子问题,构造子问题集,查看其之间的关系,然后dp

代码:

class Solution:
    def findTheWinner(self, n: int, k: int) -> int:
        x = 0
        for i in range(2, n + 1):
            x = (x + k) % i
        return x + 1

# 作者:Krahets
# 链接:https://leetcode.cn/problems/find-the-winner-of-the-circular-game/solutions/2362052/1823-zhao-chu-you-xi-de-huo-sheng-zhe-yu-tkst/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

拓展

问题

解答

代码:


拓展