DSA Interview Prep · Lektion

Lige sum af delmængder

Omformulér partitionsproblemet som en 0/1-knapsack med målet total-sum/2, og afgør gennemførligheden med et boolsk DP-array.

Lektion 3 af 413 trin

Lige sum af delmængder er en gratis DSA Interview Prep-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i DSA Interview Prep, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. DSA Interview Prep-kurset indeholder 4 lektioner i alt.

Problemformulering

Givet et ikke-tomt array af positive heltal nums skal du afgøre, om du kan opdele det i to delmængder med samme sum. For eksempel kan [1, 5, 11, 5] opdeles i [1, 5, 5] og [11], som begge har summen 11. Hvis den samlede sum er ulige, er svaret straks False. Ellers skal vi finde en delmængde, hvis sum er total_sum // 2 — et klassisk delmængdesumproblem.

Reduktion til delmængdesum

Den centrale reduktion: Hvis den samlede sum S er lige, og en delmængde summerer til S//2, summerer de resterende elementer automatisk også til S//2. Så problemet med partitionering i delmængder med samme sum reduceres til spørgsmålet: summerer en delmængde af nums til S//2? Dette er det klassiske NP-komplette problem Delmængdesum, som vi løser med DP for 0/1-rygsækproblemet i tiden O(n × S).

def canPartition(nums):
    total = sum(nums)
    if total % 2 != 0:
        return False  # odd sum: impossible
    target = total // 2
    # Now: does any subset of nums sum to target?

Boolsk DP-array

Definér et boolsk array dp[c], hvor dp[c] = True betyder, at der findes en delmængde, der summerer præcis til c. Initialisér dp[0] = True (den tomme delmængde summerer til 0) og alle andre til False. For hvert tal num gennemløber du kapaciteten fra target ned til num (baglæns iteration i 0/1-rygsækproblemet) og sætter dp[c] = dp[c] or dp[c - num].

def canPartition(nums):
    total = sum(nums)
    if total % 2 != 0:
        return False
    target = total // 2
    
    dp = [False] * (target + 1)
    dp[0] = True
    
    for num in nums:
        for c in range(target, num - 1, -1):  # backward: 0/1 knapsack
            dp[c] = dp[c] or dp[c - num]
    
    return dp[target]

print(canPartition([1, 5, 11, 5]))  # True
print(canPartition([1, 2, 3, 5]))   # False

Gennemgang af eksemplet

For [1, 5, 11, 5] er total=22 og target=11. I begyndelsen er dp[0]=True. Efter num=1: dp[1]=True. Efter num=5: dp[5]=True, dp[6]=True. Efter num=11: dp[11]=True (ved kun at bruge 11). Vi har allerede fundet dp[11]=True — men vi fortsætter med at behandle alle tal. Det endelige svar er: dp[11]=True, så en opdeling er mulig.

Optimering med tidlig afslutning

Vi kan tilføje et tidligt stop: Hvis dp[target] bliver True på noget tidspunkt, returnerer vi straks True. Det kan gøre scenarier i bedste fald markant hurtigere. Hvis et enkelt element er lig med target, kan vi også straks returnere True. Hvis et enkelt element overstiger target, kan det ikke indgå i en delmængde, der summerer til target, men vi skal stadig kontrollere resten.

def canPartition_fast(nums):
    total = sum(nums)
    if total % 2 != 0:
        return False
    target = total // 2
    if max(nums) > target:  # any element > target makes it impossible
        return False
    
    dp = [False] * (target + 1)
    dp[0] = True
    
    for num in nums:
        for c in range(target, num - 1, -1):
            dp[c] = dp[c] or dp[c - num]
            if dp[target]:
                return True  # early exit
    
    return dp[target]

print(canPartition_fast([1, 5, 11, 5]))  # True

Brug af en Python-mængde i stedet for et DP-array

Et alternativ er at vedligeholde en mængde med opnåelige summer. Begynd med {0}. For hvert tal føjer du tallet til hver sum i den aktuelle mængde: reachable = reachable | {s + num for s in reachable}. Filtrér, så du kun beholder summer, der ikke overstiger target. Til sidst tjekker du, om target findes i mængden. Denne tilgang er intuitiv, men kan bruge mere hukommelse og kan være langsommere i praksis.

def canPartition_set(nums):
    total = sum(nums)
    if total % 2 != 0:
        return False
    target = total // 2
    
    reachable = {0}
    for num in nums:
        reachable = {s + num for s in reachable if s + num <= target} | reachable
    
    return target in reachable

print(canPartition_set([1, 5, 11, 5]))  # True

Kompleksitetsanalyse

DP-tilgangen kører i tiden O(n × S), hvor S = sum(nums), og bruger O(S) hukommelse til det boolske array. For begrænsningerne i LeetCode (n ≤ 200, sum ≤ 20.000) er det højst 4.000.000 operationer — meget hurtigt. Mængdetilgangen har samme asymptotiske kompleksitet, men kan være langsommere i praksis på grund af omkostningerne ved at opbygge mængder.

Generalisering: Optælling af delmængder med en given sum

Et beslægtet problem er at tælle antallet af delmængder, der summerer til et mål. Ændr DP'en fra boolsk til heltalsbaseret: dp[c] = number of ways to reach sum c. Brug addition i stedet for OR: dp[c] += dp[c - num]. Initialisér dp[0] = 1. Brug den samme baglæns iteration. Denne generalisering viser, hvordan skabelonen for rygsækproblemet tilpasses forskellige spørgsmål om delmængder.

def count_subsets(nums, target):
    dp = [0] * (target + 1)
    dp[0] = 1
    for num in nums:
        for c in range(target, num - 1, -1):
            dp[c] += dp[c - num]
    return dp[target]

print(count_subsets([1, 1, 1, 1, 1], 3))  # 10 (C(5,3))

Typiske opfølgende spørgsmål til jobsamtalen

Forvent opfølgende spørgsmål: (1) Hvad hvis du skal returnere den faktiske opdeling? — det kræver 2D-DP til rekonstruktion. (2) Hvad hvis elementerne kan være negative? — forskyd target, eller brug en ordbog i stedet for et array. (3) Hvad er tidskompleksiteten? — O(n × sum). (4) Kan du forbedre løsningen, hvis mange tal er ens? — ja, brug frekvensoptælling til at reducere antallet af ydre iterationer. Nævn altid disse afvejninger proaktivt.

Forbindelsen til 0/1-rygsækproblemet

Problemet med opdeling i delmængder med samme sum er en direkte anvendelse af 0/1-rygsækproblemet: Elementerne er tallene, vægtene er lig med værdierne, og rygsækkens kapacitet er lig med target. Vi spørger, om den maksimale værdi er lig med target (gennemførlighed), ikke hvad den maksimale værdi er. Den baglæns iteration er den samme; kun operationen ændres fra max til boolsk or. Hvis du genkender denne forbindelse til en jobsamtale, viser det stærk mønstergenkendelse.

Særtilfælde

Særtilfælde, der skal håndteres: (1) array med længde 1 — et enkelt element kan ikke opdeles, så resultatet er altid False; (2) alle elementer er identiske, og antallet er lige — det fungerer måske eller måske ikke afhængigt af de enkelte værdier; (3) meget store summer — tjek begrænsningerne, før du allokerer DP-arrayet; (4) elementer, der er større end target — de kan springes over, fordi de aldrig kan indgå i en delmængde, der summerer til target. Tjekket af det største element som tidligt stop håndterer tilfælde (4) effektivt.

Hurtigt tjek

Tjek din forståelse af begreberne fra lektionen Data Structures & Algorithms — Coding Interview Prep.

Opsummering af lektionen

I denne lektion lærte du: Problemet med partitionering i delmængder med samme sum reduceres til delmængdesum med target = total//2, det boolske 1D-DP-array dp[c] bruger baglæns iteration, som er identisk med 0/1-rygsækproblemet, og tilgangen generaliseres til optælling af delmængder ved at erstatte boolsk OR med heltalsaddition. Nu tager vi fat på målsum, hvor fortegnstildelinger omdannes til et rygsækproblem baseret på forskellen mellem delmængdesummer.

Gratis at komme i gang

Lær Python med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Lige sum af delmængder” gratis?

Ja — alle 3 lektioner i læringssporet DSA Interview Prep, inklusive “Lige sum af delmængder”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. DSA Interview Prep-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Lige sum af delmængder”?

Omformulér partitionsproblemet som en 0/1-knapsack med målet total-sum/2, og afgør gennemførligheden med et boolsk DP-array. Du øver dig i DSA Interview Prep med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på DSA Interview Prep?

Der kræves ingen tidligere erfaring. DSA Interview Prep på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Lige sum af delmængder”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne DSA Interview Prep-lektion?

Ja. Alle DSA Interview Prep-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. 0/1-knapsack og pladseffektivisering
  2. Ubegrænset knapsack og Coin Change II
  3. Lige sum af delmængder
  4. Målsum med positive og negative fortegn
← Tilbage til DSA Interview Prep