Himpunan Bagian dan Himpunan Kuasa
Hasilkan semua himpunan bagian dari suatu himpunan menggunakan penelusuran mundur dan masker bit, dengan menangani duplikasi melalui pengurutan dan melewati elemen yang berulang
Himpunan Bagian dan Himpunan Kuasa adalah pelajaran Coding Interview Prep gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Himpunan Bagian dan Himpunan Kuasa
Himpunan kuasa dari suatu himpunan S adalah kumpulan semua himpunan bagian S yang mungkin, termasuk himpunan kosong dan S itu sendiri. Himpunan yang memiliki n elemen mempunyai tepat 2ⁿ himpunan bagian. Untuk [1, 2, 3], 8 himpunan bagiannya adalah: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]. Ini adalah masalah kombinatorika dasar yang muncul dalam pertanyaan wawancara tentang menemukan semua kombinasi, partisi, 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: 16Pembuatan Himpunan Bagian dengan Penelusuran Mundur
Gunakan templat pilih-jelajahi-batalkan pilihan. Keputusan desain utama: pada setiap pemanggilan rekursif, tambahkan jalur parsial saat ini ke hasil segera (sebelum memilih elemen lainnya). Dengan cara ini, setiap keadaan—kosong, parsial, maupun lengkap—tercatat sebagai himpunan bagian yang valid. Majukan indeks start agar hanya mempertimbangkan elemen di sebelah kanan elemen terakhir yang dipilih, sehingga tidak ada duplikat dan urutan tetap terjaga.
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 Masker Bit
Alternatif untuk penelusuran mundur adalah penyamaran bit: setiap himpunan bagian berhubungan dengan bilangan n-bit, dengan bit i bernilai 1 yang berarti elemen i disertakan. Iterasikan dari 0 hingga 2ⁿ - 1, lalu untuk setiap bilangan ekstrak bit-bitnya untuk membentuk himpunan bagian. Pendekatan ini bersifat iteratif, sering kali lebih cepat dalam praktik, dan sangat mudah ditulis. Namun, pendekatan ini tidak tergeneralisasi dengan baik untuk masalah yang memiliki batasan, seperti batas 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 differPembuatan Himpunan Bagian Iteratif
Pendekatan iteratif membangun himpunan kuasa elemen demi elemen. Mulailah dengan [[] ] (himpunan kosong). Untuk setiap elemen baru, duplikasi semua himpunan bagian yang ada, lalu tambahkan elemen baru ke setiap duplikat. Setelah n elemen diproses, hasilnya berisi semua 2ⁿ himpunan bagian. Pendekatan ini setara dengan penyamaran bit, tetapi lebih mudah dibaca bagi mereka yang belum terbiasa 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]]Himpunan Bagian II: Menangani Duplikat
Saat masukan berisi duplikat, pendekatan naif menghasilkan himpunan bagian duplikat. Untuk [1, 2, 2], kedua kemunculan 2 akan menghasilkan [1, 2] secara terpisah. Perbaikannya: gunakan sort untuk mengurutkan larik terlebih dahulu, lalu lewati kandidat pada tingkat saat ini jika sama dengan kandidat sebelumnya pada tingkat yang sama. Secara khusus, dalam perulangan: 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 Pelewatan Duplikat Berhasil
Kondisi i > start and nums[i] == nums[i-1] melewati duplikat hanya pada tingkat rekursi yang sama (nilai start yang sama). Kondisi ini tidak mencegah pemilihan nilai yang sama pada kedalaman yang berbeda. Untuk [1, 2, 2]: pada tingkat 0, kita menyertakan angka 2 pertama (indeks 1), lalu pada tingkat berikutnya (awal=2), menyertakan angka 2 kedua untuk membentuk [2, 2]. Namun, jika kita mencoba menyertakan angka 2 kedua lagi pada tingkat 0, kondisi tersebut mendeteksi dan melewatinya.
# 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 Berukuran Tetap (Kombinasi k)
Menghasilkan hanya subhimpunan yang berukuran tepat k (LeetCode 77: Kombinasi) menambahkan kondisi penghentian dini: jika elemen yang tersisa tidak dapat mengisi jalur hingga berukuran k, lakukan pemangkasan. Kondisi yang dapat dipangkas adalah i > n - (k - len(path)): jika elemen yang tersisa tidak cukup, berhentilah lebih awal. Cara ini secara signifikan mengurangi ruang pencarian dibandingkan menghasilkan semua subhimpunan lalu menyaringnya.
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) = 120Penerapan Himpunan Kuasa
Pola himpunan kuasa muncul dalam banyak variasi soal wawancara: (1) Membagi menjadi dua subhimpunan yang sama besar — periksa apakah ada subhimpunan yang jumlahnya sama dengan total/2. (2) XOR maksimum dari dua subhimpunan — coba semua pasangan subhimpunan. (3) Biaya minimum untuk memilih k elemen — enumerasikan subhimpunan berukuran k. Meskipun enumerasi langsung bersifat eksponensial, banyak soal ini dapat diselesaikan dengan DP setelah Anda mengenali strukturnya. Kerangka himpunan kuasa membantu Anda mengidentifikasi ruang keadaannya, bahkan saat Anda akan mengoptimalkannya.
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)) # 20Pemeriksaan Jumlah Subhimpunan
Jumlah Subhimpunan menanyakan: apakah ada subhimpunan dari larik yang jumlahnya sama dengan sasaran? Ini dapat diselesaikan dengan penelusuran mundur (eksponensial) atau DP (polinomial). Versi penelusuran mundur mudah dipahami, tetapi menjadi tidak praktis untuk masukan berukuran besar. Versi DP (tabel Boolean dp[target+1]) adalah pendekatan yang lebih disarankan dalam wawancara. Memahami keduanya membantu Anda menjelaskan komprominya: penelusuran mundur menghasilkan semua solusi, sedangkan DP menjawab masalah keputusan secara efisien.
# 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)) # TrueKompleksitas Enumerasi Subhimpunan
Menghasilkan semua subhimpunan memiliki kompleksitas waktu O(n × 2ⁿ) yang tidak dapat dihindari — terdapat 2ⁿ subhimpunan, masing-masing berukuran rata-rata n/2. Tidak ada algoritme yang dapat bekerja lebih baik ketika semua subhimpunan diminta. Untuk soal yang meminta satu subhimpunan dengan sifat tertentu (seperti jumlah maksimum), DP atau pendekatan rakus sebaiknya dipilih. Wawasan penting dalam wawancara: selalu tanyakan apakah Anda perlu mengenumerasi semua subhimpunan atau hanya mencari apakah ada subhimpunan yang memenuhi suatu kondisi — jawabannya menentukan apakah waktu eksponensial atau polinomial dapat 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 Pendekatan
Untuk menghasilkan semua subhimpunan: Penelusuran mundur adalah pendekatan yang paling mudah digeneralisasi — mudah disesuaikan dengan duplikat dan berbagai batasan. Pemaskaan bit ringkas dan cepat, tetapi terbatas pada n ≤ 30 karena ukuran bilangan bulat. Pendekatan iteratif intuitif dan menghindari beban tambahan rekursi. Ketiganya menghasilkan keluaran berukuran O(n × 2ⁿ). Dalam wawancara, penelusuran mundur menunjukkan pemahaman tentang proses pengambilan keputusan secara rekursif, yang dapat digeneralisasi ke soal yang lebih sulit. Sebutkan ketiganya saat membahas berbagai pendekatan.
# 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 8Uji Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritme — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: penelusuran mundur menghasilkan semua subhimpunan dengan menambahkan setiap jalur parsial ke hasil sebelum menjelajah lebih lanjut, duplikat ditangani dengan mengurutkan dan melewati nilai berulang pada kedalaman rekursi yang sama menggunakan kondisi i > start dan nums[i] == nums[i-1], serta pemaskaan bit menyediakan alternatif iteratif yang ringkas, di mana setiap subhimpunan dipetakan ke masker bit yang unik. Berikutnya kita akan membahas Permutasi dan Kombinasi — masalah enumerasi terkait dengan batasan yang berbeda.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Himpunan Bagian dan Himpunan Kuasa” gratis?
Ya — teks lengkap “Himpunan Bagian dan Himpunan Kuasa” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Himpunan Bagian dan Himpunan Kuasa”?
Hasilkan semua himpunan bagian dari suatu himpunan menggunakan penelusuran mundur dan masker bit, dengan menangani duplikasi melalui pengurutan dan melewati elemen yang berulang Kamu berlatih Coding Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Himpunan Bagian dan Himpunan Kuasa” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran Coding Interview Prep ini?
Ya. Setiap pelajaran Coding Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
- Himpunan Bagian dan Himpunan Kuasa
- Permutasi dan Kombinasi
- N-Queens dan Propagasi Kendala