0Pricing
Coding Interview Prep · Pelajaran

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: 16

Pembuatan 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 differ

Pembuatan 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 subsets

Mengapa 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')  # 6

Subhimpunan 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) = 120

Penerapan 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))  # 20

Pemeriksaan 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))  # True

Kompleksitas 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 8

Uji 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

  1. Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
  2. Himpunan Bagian dan Himpunan Kuasa
  3. Permutasi dan Kombinasi
  4. N-Queens dan Propagasi Kendala
← Kembali ke Coding Interview Prep