Skip to content

第 436 场周赛-20250209

中国时间:2025-02-09 10:30,1 小时 30 分

https://leetcode.cn/contest/weekly-contest-436/

3446.按对角线进行矩阵排序

implementation, https://leetcode.cn/problems/sort-matrix-by-diagonals/

给你一个大小为 n x n 的整数方阵 grid。返回一个经过如下调整的矩阵:

  • 左下角三角形(包括中间对角线)的对角线按 非递增顺序 排序。
  • 右上角三角形 的对角线按 非递减顺序 排序。

示例 1:

输入: grid = [[1,7,3],[9,8,2],[4,5,6]]

输出: [[8,2,3],[9,6,7],[4,5,1]]

解释:

img

标有黑色箭头的对角线(左下角三角形)应按非递增顺序排序:

  • [1, 8, 6] 变为 [8, 6, 1]
  • [9, 5][4] 保持不变。

标有蓝色箭头的对角线(右上角三角形)应按非递减顺序排序:

  • [7, 2] 变为 [2, 7]
  • [3] 保持不变。

示例 2:

输入: grid = [[0,1],[1,2]]

输出: [[2,1],[1,0]]

解释:

img

标有黑色箭头的对角线必须按非递增顺序排序,因此 [0, 2] 变为 [2, 0]。其他对角线已经符合要求。

示例 3:

输入: grid = [[1]]

输出: [[1]]

解释:

只有一个元素的对角线已经符合要求,因此无需修改。

提示:

  • grid.length == grid[i].length == n
  • 1 <= n <= 10
  • -10^5 <= grid[i][j] <= 10^5
python
from typing import List
from collections import defaultdict

class Solution:
    def sortMatrix(self, grid: List[List[int]]) -> List[List[int]]:
        n = len(grid)
        dia_mx = defaultdict(list)
        for i in range(n):
            for j in range(n):
                dia_mx[i - j].append(grid[i][j])

        for key, _ in dia_mx.items():
            if key < 0:
                dia_mx[key].sort()
            else:
                dia_mx[key].sort(reverse=True)

        for i in range(n):
            for j in range(n):
                grid[i][j] = dia_mx[i - j].pop(0)

        return grid

if __name__ == "__main__":
    sol = Solution()
    grid = [[1,7,3],[9,8,2],[4,5,6]]
    print(sol.sortMatrix(grid))

3447.将元素分配给有约束条件的组

https://leetcode.cn/problems/assign-elements-to-groups-with-constraints/

给你一个整数数组 groups,其中 groups[i] 表示第 i 组的大小。另给你一个整数数组 elements

请你根据以下规则为每个组分配 一个 元素:

  • 如果 groups[i] 能被 elements[j] 整除,则元素 j 可以分配给组 i
  • 如果有多个元素满足条件,则分配下标最小的元素 j
  • 如果没有元素满足条件,则分配 -1 。

返回一个整数数组 assigned,其中 assigned[i] 是分配给组 i 的元素的索引,若无合适的元素,则为 -1。

注意:一个元素可以分配给多个组。

示例 1:

输入: groups = [8,4,3,2,4], elements = [4,2]

输出: [0,0,-1,1,0]

解释:

  • elements[0] = 4 被分配给组 0、1 和 4。
  • elements[1] = 2 被分配给组 3。
  • 无法为组 2 分配任何元素,分配 -1 。

示例 2:

输入: groups = [2,3,5,7], elements = [5,3,3]

输出: [-1,1,0,-1]

解释:

  • elements[1] = 3 被分配给组 1。
  • elements[0] = 5 被分配给组 2。
  • 无法为组 0 和组 3 分配任何元素,分配 -1 。

示例 3:

输入: groups = [10,21,30,41], elements = [2,1]

输出: [0,1,0,1]

解释:

elements[0] = 2 被分配给所有偶数值的组,而 elements[1] = 1 被分配给所有奇数值的组。

提示:

  • 1 <= groups.length <= 10^5
  • 1 <= elements.length <= 10^5
  • 1 <= groups[i] <= 10^5
  • 1 <= elements[i] <= 10^5

如果 max_group 非常大(例如接近 10^5),预处理部分可能会成为性能瓶颈。此时可以考虑以下优化:

  1. 限制预处理范围:
    • 只预处理 groups 中实际出现的数,而不是所有数到 max_group
    • 使用一个哈希表记录 groups 中出现的数,然后只对这些数进行预处理。
  2. 分解因数:
    • 对于每个 groups[i],分解其因数,然后检查这些因数是否在 elements 中。
    • 这种方法适合 groups[i] 较小的情况。
python
from typing import List
import math

class Solution:
    def assignElements(self, groups: List[int], elements: List[int]) -> List[int]:
        # 将 elements 转换为集合,方便快速查找
        element_set = set(elements)
        
        # 记录每个 elements[j] 的最小索引
        element_index = {}
        for j, elem in enumerate(elements):
            if elem not in element_index:
                element_index[elem] = j
        
        assigned = []
        for group in groups:
            # 找到 group 的所有因数
            factors = set()
            for i in range(1, int(math.sqrt(group)) + 1):
                if group % i == 0:
                    factors.add(i)
                    factors.add(group // i)
            
            # 找到满足条件的最小 j
            min_j = -1
            for factor in sorted(factors):
                if factor in element_index:
                    if min_j == -1 or element_index[factor] < min_j:
                        min_j = element_index[factor]
            
            assigned.append(min_j)
        
        return assigned

# 测试代码
if __name__ == "__main__":
    sol = Solution()
    print(sol.assignElements([10, 21, 30, 41], [2, 1]))  # [0, 1, 0, 1]
    print(sol.assignElements([8, 4, 3, 2, 4], [4, 2]))  # [0, 0, -1, 1, 0]
    print(sol.assignElements([2, 3, 5, 7], [5, 3, 3]))  # [-1, 1, 0, -1]

复杂度分析

  1. 时间复杂度:
    • 分解因数的时间复杂度为 O(sqrt(group))
    • 总体时间复杂度为 O(n * sqrt(group)),其中 ngroups 的长度。
  2. 空间复杂度:
    • 使用了一个哈希表记录 elements 的索引,空间复杂度为 O(m),其中 melements 的长度。

总结

  • 如果 groups 的最大值较小,推荐使用预处理倍数的方法。
  • 如果 groups 的最大值较大,推荐使用分解因数的方法。
  • 两种方法都可以有效避免超时问题。

预处理倍数方法超时了。超出时间限制 ,564/ 572 个通过的测试用例。

python
from typing import List

class Solution:
    def assignElements(self, groups: List[int], elements: List[int]) -> List[int]:
        max_val = max(groups)  # 只需计算到 `groups` 里的最大值
        multiple_map = [-1] * (max_val + 1)  # 用数组代替哈希表,初始化为 -1
        group_set = set(groups)  # 仅处理出现在 `groups` 里的数

        # 预处理 elements 的所有倍数
        for idx, elem in enumerate(elements):
            for mul in range(elem, max_val + 1, elem):  
                if mul in group_set and multiple_map[mul] == -1:  # 只记录最小索引
                    multiple_map[mul] = idx  

        # 查询 groups[i] 是否有可整除的元素
        return [multiple_map[num] for num in groups]

# 测试代码
if __name__ == "__main__":
    sol = Solution()
    print(sol.assignElements([10, 21, 30, 41], [2, 1]))  # [0, 1, 0, 1]
    print(sol.assignElements([8, 4, 3, 2, 4], [4, 2]))  # [0, 0, -1, 1, 0]
    print(sol.assignElements([2, 3, 5, 7], [5, 3, 3]))  # [-1, 1, 0, -1]

3448.统计可以被最后一个数位整除的子字符串数目

dp, https://leetcode.cn/problems/count-substrings-divisible-by-last-digit/

给你一个只包含数字的字符串 s

请你返回 s 的最后一位 不是 0 的子字符串中,可以被子字符串最后一位整除的数目。

子字符串 是一个字符串里面一段连续 非空 的字符序列。

注意:子字符串可以有前导 0 。

示例 1:

输入:s = "12936"

输出:11

解释:

子字符串 "29""129""293""2936" 不能被它们的最后一位整除,总共有 15 个子字符串,所以答案是 15 - 4 = 11

示例 2:

输入:s = "5701283"

输出:18

解释:

子字符串 "01""12""701""012""128""5701""7012""0128""57012""70128""570128""701283" 都可以被它们最后一位数字整除。除此以外,所有长度为 1 且不为 0 的子字符串也可以被它们的最后一位整除。有 6 个这样的子字符串,所以答案为 12 + 6 = 18

示例 3:

输入:s = "1010101010"

输出:25

解释:

只有最后一位数字为 '1' 的子字符串可以被它们的最后一位整除,总共有 25 个这样的字符串。

提示:

  • 1 <= s.length <= 10^5
  • s 只包含数字。

主要思想:

优化: 由于直接遍历所有子字符串会导致时间复杂度过高,使用余数来优化,只需要关注每个子字符串的余数是否能被它的最后一位整除,从而避免了计算整个数的整除问题。

作者:灵茶山艾府 链接:https://leetcode.cn/problems/count-substrings-divisible-by-last-digit/solutions/3068623/gong-shi-tui-dao-dong-tai-gui-hua-python-iw4a/

python
class Solution:
    def countSubstrings(self, s: str) -> int:
        ans = 0
        f = [[0] * 9 for _ in range(10)]
        for d in map(int, s):
            for m in range(1, 10):  # 枚举模数 m
                # 滚动数组计算 f
                nf = [0] * m
                nf[d % m] = 1
                for rem in range(m):  # 枚举模 m 的余数 rem
                    nf[(rem * 10 + d) % m] += f[m][rem]  # 刷表法
                f[m] = nf
            # 以 s[i] 结尾的,模 s[i] 余数为 0 的子串个数
            ans += f[d][0]
        return ans

超出时间限制683 / 699 个通过的测试用例

python
class Solution:
    def countSubstrings(self, s: str) -> int:
        n = len(s)
        count = 0

        # 遍历所有可能的最后一位
        for j in range(n):
            last_digit = int(s[j])
            if last_digit == 0:
                continue
            # 遍历所有可能的起点
            num = 0
            for i in range(j, -1, -1):
                num += int(s[i]) * (10 ** (j - i))
                if num % last_digit == 0:
                    count += 1

        return count

# 测试代码
if __name__ == '__main__':
    s = Solution()
    print(s.countSubstrings("12936"))  # 输出: 11
    print(s.countSubstrings("5701283"))  # 输出: 18
    print(s.countSubstrings("1010101010"))  # 输出: 25

3449.最大化游戏分数的最小值

binary search + greedy, https://leetcode.cn/problems/maximize-the-minimum-game-score/

给你一个长度为 n 的数组 points 和一个整数 m 。同时有另外一个长度为 n 的数组 gameScore ,其中 gameScore[i] 表示第 i 个游戏得到的分数。一开始对于所有的 i 都有 gameScore[i] == 0

你开始于下标 -1 处,该下标在数组以外(在下标 0 前面一个位置)。你可以执行 至多 m 次操作,每一次操作中,你可以执行以下两个操作之一:

  • 将下标增加 1 ,同时将 points[i] 添加到 gameScore[i]
  • 将下标减少 1 ,同时将 points[i] 添加到 gameScore[i]

注意,在第一次移动以后,下标必须始终保持在数组范围以内。

请你返回 至多 m 次操作以后,gameScore 里面最小值 最大 为多少。

示例 1:

输入:points = [2,4], m = 3

输出:4

解释:

一开始,下标 i = -1gameScore = [0, 0].

移动下标gameScore
增加 i0[2, 0]
增加 i1[2, 4]
减少 i0[4, 4]

gameScore 中的最小值为 4 ,这是所有方案中可以得到的最大值,所以返回 4 。

示例 2:

输入:points = [1,2,3], m = 5

输出:2

解释:

一开始,下标 i = -1gameScore = [0, 0, 0]

移动下标gameScore
增加 i0[1, 0, 0]
增加 i1[1, 2, 0]
减少 i0[2, 2, 0]
增加 i1[2, 4, 0]
增加 i2[2, 4, 3]

gameScore 中的最小值为 2 ,这是所有方案中可以得到的最大值,所以返回 2 。

提示:

  • 2 <= n == points.length <= 5 * 10^4
  • 1 <= points[i] <= 10^6
  • 1 <= m <= 10^9

问题解读

题目要求我们在最多 m 次操作内,通过移动下标并累加 points 数组中的值到 gameScore 数组中,使得最终 gameScore 数组中的最小值最大化。

代码作者:灵茶山艾府 链接:https://leetcode.cn/problems/maximize-the-minimum-game-score/solutions/3068672/er-fen-da-an-cong-zuo-dao-you-tan-xin-py-3bhl/

python
class Solution:
    def maxScore(self, points: List[int], m: int) -> int:
        def check(low: int) -> bool:
            n = len(points)
            rem = m
            pre = 0
            for i, p in enumerate(points):
                k = (low - 1) // p + 1 - pre  # 还需要操作的次数
                if i == n - 1 and k <= 0:  # 最后一个数已经满足要求
                    break
                if k < 1:
                    k = 1  # 至少要走 1 步
                rem -= k * 2 - 1  # 左右横跳
                if rem < 0:
                    return False
                pre = k - 1  # 右边那个数顺带操作了 k-1 次
            return True

        left = 0
        right = (m + 1) // 2 * min(points) + 1
        while left + 1 < right:
            mid = (left + right) // 2
            if check(mid):
                left = mid
            else:
                right = mid
        return left

时间复杂度分析

  1. 二分查找部分
    • leftright 的范围是 O(m * min(points)),但 二分查找 让搜索减少为 O(log(m * min(points)))
  2. check(low) 的执行
    • 需要遍历 points 一遍,复杂度 O(n)

总复杂度:

O(nlog(m⋅min(points)))

这比暴力枚举所有可能的 low快很多,特别是当 m 很大时。


总结

  • 该算法使用 二分查找 + 贪心
  • 二分查找 用于寻找最大可能的 maxScore
  • 贪心策略 (check) 用于判断在 m 步内是否能让 points 中的最小值达到 low
  • 优化点:
    • check(low) 通过 左右横跳 方式减少不必要的操作,保证 m 步内的最大化。
    • 避免暴力搜索,将问题缩小到 O(n log m) 的范围,提高效率。