Subset dan Set Kuasa
Jana semua subset bagi suatu set menggunakan pengunduran dan topeng bit, serta kendalikan pendua dengan mengisih dan melangkau unsur berulang.
Subset dan Set Kuasa ialah pelajaran Persediaan Temu Duga Pengaturcaraan percuma di CoddyKit. Ini ialah pelajaran 2 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Persediaan Temu Duga Pengaturcaraan, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.
Subhimpunan dan Himpunan Kuasa
Himpunan kuasa bagi suatu himpunan S ialah kumpulan semua subhimpunan S yang mungkin, termasuk himpunan kosong dan S itu sendiri. Himpunan yang mempunyai n unsur mempunyai tepat 2ⁿ subhimpunan. Bagi [1, 2, 3], 8 subhimpunannya ialah: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]. Ini ialah masalah kombinatorik asas yang muncul dalam soalan temu duga tentang mencari semua gabungan, pembahagian atau pilihan yang mungkin.
# A set of n elements → 2^n subsets
for n in range(5):
print(f'n={n}: {2**n} subsets')
# n=0: 1 (just the empty set)
# n=1: 2 ([], [x])
# n=2: 4 ([], [a], [b], [a,b])
# n=3: 8 (as enumerated above)
# n=4: 16Menjana Subhimpunan dengan Penjejakan Balik
Gunakan templat pilih-teroka-nyahpilih. Keputusan reka bentuk utama ialah: pada setiap panggilan rekursif, tambahkan laluan separa semasa kepada keputusan serta-merta (sebelum memilih lebih banyak unsur). Dengan cara ini, setiap keadaan — kosong, separa dan lengkap — direkodkan sebagai subhimpunan yang sah. Majukan indeks start supaya hanya unsur di sebelah kanan unsur terakhir yang dipilih dipertimbangkan, sekali gus memastikan tiada pendua dan mengekalkan susunan.
def subsets(nums):
result = []
def backtrack(start, path):
result.append(list(path)) # every state is a valid subset
for i in range(start, len(nums)):
path.append(nums[i]) # CHOOSE
backtrack(i + 1, path) # EXPLORE (advance start)
path.pop() # UNCHOOSE
backtrack(0, [])
return result
print(subsets([1, 2, 3]))
# [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]Pendekatan Topeng Bit
Alternatif kepada penjejakan balik ialah topeng bit: setiap subhimpunan sepadan dengan nombor n-bit, dengan bit i bernilai 1 bermaksud unsur i disertakan. Lelarkan daripada 0 hingga 2ⁿ - 1 dan bagi setiap nombor, ambil bit-bitnya untuk membina subhimpunan tersebut. Kaedah ini bersifat lelaran, selalunya lebih pantas dalam amalan dan sangat mudah ditulis. Walau bagaimanapun, kaedah ini tidak digeneralisasikan dengan begitu baik kepada masalah yang mempunyai kekangan (seperti had jumlah).
def subsets_bitmask(nums):
n = len(nums)
result = []
for mask in range(1 << n): # 0 to 2^n - 1
subset = []
for i in range(n):
if mask & (1 << i): # bit i is set
subset.append(nums[i])
result.append(subset)
return result
print(subsets_bitmask([1, 2, 3]))
# Same 8 subsets, order may differMenjana Subhimpunan Secara Lelaran
Pendekatan lelaran membina himpunan kuasa unsur demi unsur. Mulakan dengan [[] ] (himpunan kosong). Bagi setiap unsur baharu, gandakan semua subhimpunan sedia ada dan tambahkan unsur baharu itu pada setiap pendua. Selepas memproses n unsur, keputusan mengandungi semua 2ⁿ subhimpunan. Kaedah ini setara dengan topeng bit tetapi lebih mudah dibaca oleh mereka yang tidak biasa dengan operasi bit.
def subsets_iterative(nums):
result = [[]] # start with empty set
for num in nums:
# For each existing subset, create a new subset with num added
result += [subset + [num] for subset in result]
return result
print(subsets_iterative([1, 2, 3]))
# After num=1: [[], [1]]
# After num=2: [[], [1], [2], [1,2]]
# After num=3: [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]Subhimpunan II: Mengendalikan Pendua
Apabila input mengandungi pendua, pendekatan naif akan menghasilkan subhimpunan pendua. Bagi [1, 2, 2], kedua-dua kemunculan 2 akan menghasilkan [1, 2] secara berasingan. Penyelesaiannya: susun tatasusunan dahulu, kemudian langkau calon pada aras semasa jika calon itu sama dengan calon sebelumnya pada aras yang sama. Secara khusus, dalam gelung: if i > start and nums[i] == nums[i-1]: continue.
def subsets_with_dups(nums):
nums.sort() # sort to group duplicates together
result = []
def backtrack(start, path):
result.append(list(path))
for i in range(start, len(nums)):
# Skip duplicates at the same tree level
if i > start and nums[i] == nums[i-1]:
continue
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return result
print(subsets_with_dups([1, 2, 2]))
# [[], [1], [1,2], [1,2,2], [2], [2,2]] — no duplicate subsetsMengapa Langkauan Pendua Berfungsi
Syarat i > start and nums[i] == nums[i-1] hanya melangkau nilai pendua pada aras rekursi yang sama (start yang sama). Syarat ini tidak menghalang pemilihan nilai yang sama pada kedalaman yang berbeza. Untuk [1, 2, 2]: pada aras 0, kita memasukkan 2 pertama (indeks 1), kemudian pada aras seterusnya (start=2), kita memasukkan 2 kedua untuk membentuk [2, 2]. Namun, jika kita cuba memasukkan 2 kedua sekali lagi pada aras 0, syarat tersebut akan mengesannya lalu melangkauinya.
# Visual: [1, 2, 2] sorted
# Level 0 (start=0): pick nothing, pick 1, pick first-2, pick second-2 (SKIP)
# Level 1 after picking 1 (start=1): pick first-2, pick second-2 (SKIP)
# Level 2 after picking 1,first-2 (start=2): pick second-2
# → [1,2,2] is generated but only once
nums = [1, 2, 2]
nums.sort()
result_set = set(tuple(sorted(s)) for s in subsets_with_dups(nums[:]))
result_naive = set(tuple(sorted(s)) for s in subsets(nums))
print('With dedup:', sorted(result_set))
print('Same results:', result_set == result_naive)
def subsets(nums):
result = []
def bt(start, path):
result.append(list(path))
for i in range(start, len(nums)):
path.append(nums[i]); bt(i+1, path); path.pop()
bt(0, [])
return result
def subsets_with_dups(nums):
result = []
def bt(start, path):
result.append(list(path))
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]: continue
path.append(nums[i]); bt(i+1, path); path.pop()
bt(0, [])
return result
print(len(subsets_with_dups([1,2,2])), 'unique subsets') # 6Subhimpunan Bersaiz Tetap (Kombinasi-k)
Menjana hanya subhimpunan yang tepat-tepat bersaiz k (LeetCode 77: Kombinasi) menambah syarat penamatan awal: jika elemen yang berbaki tidak dapat melengkapkan laluan sehingga bersaiz k, pangkas carian. Syarat yang boleh dipangkas ialah i > n - (k - len(path)): jika elemen yang berbaki tidak mencukupi, hentikan carian lebih awal. Hal ini mengurangkan ruang carian dengan ketara berbanding menjana semua subhimpunan kemudian menapisnya.
def combine(n, k):
result = []
def backtrack(start, path):
if len(path) == k:
result.append(list(path))
return
# Prune: need (k - len(path)) more elements from [start..n]
# At most (n - start + 1) elements remain
if n - start + 1 < k - len(path):
return # not enough elements left
for i in range(start, n + 1):
path.append(i)
backtrack(i + 1, path)
path.pop()
backtrack(1, [])
return result
print(combine(4, 2)) # [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
print(len(combine(10, 3))) # C(10,3) = 120Aplikasi Set Kuasa
Pola set kuasa muncul dalam banyak variasi soalan temu duga: (1) Memecahkan kepada dua subhimpunan sama besar — semak sama ada terdapat subhimpunan yang jumlahnya sama dengan total/2. (2) XOR maksimum bagi dua subhimpunan — cuba semua pasangan subhimpunan. (3) Kos minimum untuk memilih k item — senaraikan semua subhimpunan-k. Walaupun penyenaraian terus adalah eksponen, banyak masalah ini menerima penyelesaian DP sebaik sahaja anda mengenal pasti strukturnya. Kerangka set kuasa membantu anda mengenal pasti ruang keadaan walaupun anda kemudiannya mengoptimumkannya.
def max_subset_sum(nums, k):
'''Maximum sum of any k elements (for comparison: O(n log n) alternative)'''
# Backtracking approach: enumerate all k-subsets
max_s = [float('-inf')]
def bt(start, path, curr_sum):
if len(path) == k:
max_s[0] = max(max_s[0], curr_sum)
return
remaining_spots = k - len(path)
for i in range(start, len(nums)):
if len(nums) - i < remaining_spots: break # prune
bt(i+1, path+[nums[i]], curr_sum+nums[i])
bt(0, [], 0)
return max_s[0]
# Much faster: just sort and take top k
def max_subset_sum_fast(nums, k):
return sum(sorted(nums, reverse=True)[:k])
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(max_subset_sum(nums, 3)) # 20 (9+6+5)
print(max_subset_sum_fast(nums, 3)) # 20Semakan Jumlah Subhimpunan
Jumlah Subhimpunan bertanya: adakah mana-mana subhimpunan dalam tatasusunan mempunyai jumlah yang sama dengan sasaran? Masalah ini boleh diselesaikan dengan penjejakan balik (eksponen) atau DP (polinomial). Versi penjejakan balik adalah mudah, tetapi menjadi tidak praktikal untuk masukan yang besar. Versi DP (jadual boolean dp[target+1]) ialah pendekatan yang lebih diutamakan dalam temu duga. Memahami kedua-duanya membantu anda menerangkan pertukaran: penjejakan balik memberikan semua penyelesaian, manakala DP menjawab masalah keputusan dengan cekap.
# Backtracking version: finds a subset if it exists
def subset_sum_bt(nums, target):
def bt(start, remaining):
if remaining == 0: return True
if remaining < 0 or start == len(nums): return False
# Include nums[start]
if bt(start + 1, remaining - nums[start]): return True
# Exclude nums[start]
return bt(start + 1, remaining)
return bt(0, target)
# DP version: O(n * target) time
def subset_sum_dp(nums, target):
dp = {0}
for num in nums:
dp |= {s + num for s in dp}
return target in dp
print(subset_sum_bt([3, 1, 4, 1, 5], 6)) # True (1+5 or 1+1+4)
print(subset_sum_dp([3, 1, 4, 1, 5], 6)) # TrueKerumitan Penyenaraian Subhimpunan
Menjana semua subhimpunan mempunyai kerumitan masa O(n × 2ⁿ) yang tidak dapat dielakkan — 2ⁿ subhimpunan, setiap satunya bersaiz purata n/2. Tiada algoritma yang boleh melakukan lebih baik apabila semua subhimpunan diminta. Bagi masalah yang meminta satu subhimpunan dengan sifat tertentu (seperti jumlah maksimum), DP atau algoritma tamak harus diutamakan. Wawasan penting dalam temu duga: sentiasa tanya sama ada anda perlu menyenaraikan semua subhimpunan atau hanya mencari sama ada mana-mana subhimpunan memenuhi syarat — jawapannya menentukan sama ada masa eksponen atau polinomial boleh diterima.
import time
def count_subsets(n):
nums = list(range(n))
result = []
def bt(start, path):
result.append(None) # count without storing
for i in range(start, len(nums)):
path.append(i); bt(i+1, path); path.pop()
bt(0, [])
return len(result)
for n in [10, 15, 20]:
start = time.time()
cnt = count_subsets(n)
elapsed = time.time() - start
print(f'n={n}: {cnt} subsets ({2**n} expected) in {elapsed:.3f}s')Membandingkan Ketiga-tiga Pendekatan
Untuk menjana semua subhimpunan: Penjejakan balik ialah pendekatan yang paling mudah digeneralisasikan — ia mudah disesuaikan dengan pendua dan kekangan. Penyamaran bit ringkas dan pantas, tetapi terhad kepada n ≤ 30 (saiz integer). Beriterasi adalah intuitif dan mengelakkan lebihan kos rekursi. Ketiga-tiganya menghasilkan hasil berkerumitan O(n × 2ⁿ). Dalam temu duga, penjejakan balik menunjukkan pemahaman tentang proses keputusan rekursif, yang boleh digeneralisasikan kepada masalah yang lebih sukar. Nyatakan ketiga-tiga pendekatan apabila membincangkan pilihan penyelesaian.
# All three approaches for [1,2,3]
nums = [1, 2, 3]
# 1. Backtracking
def bt(start, path, res):
res.append(list(path))
for i in range(start, len(nums)):
path.append(nums[i]); bt(i+1, path, res); path.pop()
res1 = []; bt(0, [], res1)
# 2. Bit masking
res2 = [[nums[i] for i in range(len(nums)) if mask & (1<<i)]
for mask in range(1<<len(nums))]
# 3. Iterative
res3 = [[]]
for num in nums:
res3 += [s+[num] for s in res3]
print('All produce', len(nums)**2, '-ish subsets:',
len(res1), len(res2), len(res3)) # all 8Semakan Pantas
Uji pemahaman anda tentang konsep Struktur Data & Algoritma — Persediaan Temu Duga Pengekodan daripada pelajaran ini.
Imbas Kembali Pelajaran
Dalam pelajaran ini, anda telah belajar bahawa: penjejakan balik menjana semua subhimpunan dengan menambahkan setiap laluan separa kepada hasil sebelum meneroka dengan lebih lanjut, pendua dikendalikan dengan mengisih dan melangkau nilai berulang pada kedalaman rekursi yang sama menggunakan syarat i > start and nums[i] == nums[i-1], dan penyamaran bit menyediakan alternatif beriterasi yang ringkas, dengan setiap subhimpunan dipetakan kepada bitmask yang unik. Seterusnya, kita akan membincangkan Permutasi dan Kombinasi — masalah penyenaraian berkaitan dengan kekangan yang berbeza.
Pelajari Persediaan Temu Duga Pengaturcaraan dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 90
- Pelajaran
- 360
Soalan Lazim
Adakah pelajaran “Subset dan Set Kuasa” percuma?
Ya — teks penuh “Subset dan Set Kuasa” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Persediaan Temu Duga Pengaturcaraan, tingkat taraf kepada CoddyKit PRO. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Subset dan Set Kuasa”?
Jana semua subset bagi suatu set menggunakan pengunduran dan topeng bit, serta kendalikan pendua dengan mengisih dan melangkau unsur berulang. Anda berlatih Persediaan Temu Duga Pengaturcaraan menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Persediaan Temu Duga Pengaturcaraan?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Persediaan Temu Duga Pengaturcaraan di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 2 daripada 4.
Berapa lamakah pelajaran “Subset dan Set Kuasa” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Persediaan Temu Duga Pengaturcaraan ini?
Ya. Setiap pelajaran Persediaan Temu Duga Pengaturcaraan menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Templat Jejak Balik: Pilih, Teroka, Nyahpilih
- Subset dan Set Kuasa
- Permutasi dan Kombinasi
- N-Queens dan Penyebaran Kekangan