weekly contest 413。

題目

輸入二維正整數矩陣 grid。

你必需從矩陣中選擇一或多個格子,滿足:

  • 選擇的格子列號不可相同。
  • 選擇的格子值不可相同。

你的分數為所選格子值的總和
求可達到的最大分數

解法

從範例二很明顯看出,優先選擇最大值的貪心策略並不管用。
肯定要使用 dp,枚舉選哪個比較好。

第一直覺是枚舉第 i 行中要選哪個元素,但還需要額外的狀態表示哪些值選過。
定義 dp(i, mask):從第 grid[i..N-1] 可選的最大分數,且 mask 為已選值的狀態。
但格子值的值域多達 100 種元素,共需 2^100 種狀態,好像也不太可行。


既然從列選擇值不可行,那就換一個角度,改成從值選擇列

定義 dp(i, mask) 從值為 [i..100] 中的格子中可選的最大分數,且 mask 為已選列的狀態。
轉移:dp(i, mask) = max(選, 不選)。
- 不選:dp(i+1, mask)。
- 選:max(dp(i+1, new_mask) + i),其中 new_mask = mask | (1«r),且第 r 列未選過。
base:當 i>MX 時,超過有效值域,回傳 0。

先遍歷一次 grid,將所有格子的列號依照值域來分組。
答案入口為 dp(1, 0)。

時間複雜度 O(MN * 2^M)。
空間複雜度 O(MN + MX * 2^M),其中 MX = max(grid[i][j])。

MX = 100
class Solution:
    def maxScore(self, grid: List[List[int]]) -> int:
        M, N = len(grid), len(grid[0])
        d = [[] for _ in range(MX + 1)]
        for r in range(M):
            for c in range(N):
                x = grid[r][c]
                d[x].append(r)

        @cache
        def dp(i, mask):
            if i > MX:
                return 0
            res = dp(i + 1, mask)
            for r in d[i]:
                if mask & (1 << r) != 0:
                    continue
                new_mask = mask | (1 << r)
                res = max(res, dp(i + 1, new_mask) + i)
            return res

        return dp(1, 0)

改成遞推寫法。
注意:最大值 MX = 100,還要考慮到 base case = 101。
因此陣列要開 102 = MX + 2,枚舉也是從 MX 開始。

MX = 100
class Solution:
    def maxScore(self, grid: List[List[int]]) -> int:
        M, N = len(grid), len(grid[0])
        d = defaultdict(list)
        for r in range(M):
            for c in range(N):
                x = grid[r][c]
                d[x].append(r)

        mask_sz = 1 << M
        f = [[0] * mask_sz for _ in range(MX + 2)]
        for i in reversed(range(1, MX + 1)):
            for mask in range(mask_sz):
                res = f[i + 1][mask]
                for r in d[i]:
                    if mask & (1 << r) != 0:
                        continue
                    new_mask = mask | (1 << r)
                    res = max(res, f[i + 1][new_mask] + i)
                f[i][mask] = res

        return f[1][0]

如果 grid[i][j] 的值域更加離散,要不就是做離散化,或是改用雜湊表來維護值域對應的列號。

再加上 dp(i) 只需要從固定其中一個不等於 i 的值域 j 轉移而來,其中 i, j 大小關係不重要。
因此可以滾動陣列,壓縮掉一個空間維度。
時間複雜度 O(MN * 2^M)。
空間複雜度 O(MN + 2^M)。

class Solution:
    def maxScore(self, grid: List[List[int]]) -> int:
        M, N = len(grid), len(grid[0])
        d = defaultdict(list)
        for r in range(M):
            for c in range(N):
                x = grid[r][c]
                d[x].append(r)

        mask_sz = 1 << M
        f0 = [0] * mask_sz
        for i in d:
            f1 = [0] * mask_sz
            for mask in range(mask_sz):
                res = f0[mask]
                for r in d[i]:
                    if mask & (1 << r) != 0:
                        continue
                    new_mask = mask | (1 << r)
                    res = max(res, f0[new_mask] + i)
                f1[mask] = res
            f0 = f1

        return f0[0]