↓快轉到主要內容
  1. LeetCode/

LeetCode 4081: Prime Subset Selection II

·2 分鐘· ·
LeetCode Hard Knapsack-Dp Number-Theory
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

基本資料
#

難易度: hard 第一次嘗試:2026-10-13

  • 提交紀錄:Accepted → Wrong Answer ×2 → Accepted ×9(後面幾次是在練不同的寫法)

解題思路
#

  1. 篩質數:n <= 1000,用 Number Theory 的篩法找出所有 <= n 的質數。
  2. 0/1 背包(Knapsack DP):dp[j] 是剛好湊出 j 的最少質數個數,best[j] 是對應的組合。每個質數只能用一次,所以 j 由大到小更新。
  3. 三層比較:先要總和最大(從 min(s, 質數總和) 往下找第一個湊得出來的),再要個數最少(dp 本身),最後要字典序最小。
  4. 字典序的處理:質數由大到小放進背包。個數一樣時,新的組合是 [p] + best[j - p],p 比之前放過的質數都小,所以新的組合開頭比較小、字典序一定比較小。因此用 <= 更新就同時處理了平手。

我的嘗試
#

第一次用「總和 -> 組合」的字典做,直接 AC。之後想改成陣列版的背包練習,前兩次 WA 是因為線性篩的內層迴圈縮排進了 if 是質數 裡面,合數不會去劃掉它的倍數。例如 n = 9、s = 49:8 沒被劃掉,回傳 [2, 3, 5, 7, 8],答案是 [2, 3, 5, 7]。把內層迴圈移出來就 AC 了。

最後一版另外加了 dp[j - p] != inf 的檢查:inf + 1 <= inf 在 Python 是 True,不檢查的話湊不出來的狀態也會被寫進一個假的組合,只是之後沒有被用到。

正確解法
#

class Solution:
    def maxPrimeSubset(self, n: int, s: int) -> List[int]:
        primes, composite = [], [False] * (n + 1)
        for i in range(2, n + 1):                  # 線性篩:外層跑所有 i
            if not composite[i]:
                primes.append(i)
            for p in primes:
                if i * p > n:
                    break
                composite[i * p] = True
                if i % p == 0:
                    break
        cap = min(s, sum(primes))
        dp = [inf] * (cap + 1)
        dp[0] = 0
        best = [[] for _ in range(cap + 1)]
        for p in reversed(primes):                 # 由大到小:平手時新組合的開頭比較小
            for j in range(cap, p - 1, -1):
                if dp[j - p] != inf and dp[j - p] + 1 <= dp[j]:
                    dp[j] = dp[j - p] + 1
                    best[j] = [p] + best[j - p]
        for j in range(cap, 0, -1):
            if dp[j] != inf:
                return best[j]
        return []

複雜度
#

篩法 O(n);背包有 π(n) 個質數(n = 1000 時是 168 個)、容量 s,每次更新複製組合,最壞 O(π(n) · s · π(n));在這個範圍很快。空間 O(s · π(n))。

要特別避免的錯誤
#

  • 線性篩的內層迴圈要對所有 i 跑,不只質數。
  • inf + 1 <= inf 是 True,湊不出來的狀態要先排除。
  • 字典序的平手要想清楚放入順序;由大到小放,<= 才會選到字典序最小的。

面試口說整理
#

我先用線性篩找出質數,再做 0/1 背包,dp[j] 是湊出 j 的最少個數並記住組合。答案取湊得出來的最大總和。為了字典序最小,質數由大到小放,個數相同時新的組合以更小的質數開頭,所以用 <= 更新就對了。

相關文章

LeetCode 2584: Split the Array to Make Coprime Products
·2 分鐘
LeetCode Hard Math Number-Theory Sieve Hash-Table Array
LeetCode 2584 解題筆記:兩段乘積互質 ⇔ 沒有質因數同時出現在兩段;記下每個質因數最後出現的 index,從左往右擴張 reach,reach == i 時就能在 i 後面切
LeetCode 1998: GCD Sort of an Array
·2 分鐘
LeetCode Hard Math Number-Theory Union-Find Sieve Sorting Array
LeetCode 1998 解題筆記:同一個連通塊裡的數可以任意交換;用 SPF 把數字跟質因數合併,排序後每個位置的新舊數字在同一組就能排好
LeetCode 1627: Graph Connectivity With Threshold
·1 分鐘
LeetCode Hard Math Number-Theory Union-Find Array
LeetCode 1627 解題筆記:不用兩兩算 GCD;對每個 z > threshold,把 z 的所有倍數連起來,總共 O(n log n) 次合併
LeetCode 2709: Greatest Common Divisor Traversal
·2 分鐘
LeetCode Hard Math Number-Theory Union-Find Sieve Gcd Array
LeetCode 2709 解題筆記:任兩個 index 都能互通 ⇔ 全部在同一個連通塊;用 SPF 分解質因數,數字跟它的質因數在 Union-Find 裡合併;1 要特別處理
LeetCode 952: Largest Component Size by Common Factor
·2 分鐘
LeetCode Hard Math Number-Theory Union-Find Hash-Table Array
LeetCode 952 解題筆記:有共同因數 ⇔ 有共同的質因數;把每個數分解質因數,同一個質因數的 index 用 Union-Find 合併,不用兩兩算 GCD
LeetCode 2267: Check if There Is a Valid Parentheses String Path
·2 分鐘
LeetCode Hard Dynamic-Programming Grid-Dp Matrix Bracket-Sequences
LeetCode 2267 解題筆記:狀態是 (r, c, 左括號餘額);餘額不能是負的,也不能超過剩下的步數;BFS / DP 搜尋這些狀態,走到終點時餘額為 0 就成立