Combination Sum
Sana farklı pozitif tam sayılardan oluşan bir candidates listesi ve pozitif bir target tam sayısı veriliyor. Değerlerinin toplamı tam olarak target olan tüm aday kombinasyonlarını bul; her adayı istediğin kadar kullanabilirsin. İki kombinasyon, aynı değerleri aynı sayıda kullanıyorsa aynıdır; bu nedenle [2, 3, 3] ve [3, 2, 3] tek bir kombinasyon olarak sayılır.
Her kombinasyonu değerleri artan sırada olacak şekilde, kombinasyonları da sözlük sırasına göre döndür: İki kombinasyonu soldan başlayarak değer değer karşılaştır ve ilk farklılıkta daha küçük değere sahip olanı önce getir.
Fonksiyon
- candidatesinteger-array
- kullanabileceğin farklı değerler; istediğin sırayla ve her birini istediğin kadar kullanabilirsin
- targetinteger
- her kombinasyonun toplamı tam olarak ulaşmalıdır
- Döndürürinteger-2d-array
- hedefe eşit olan, her biri artan sırada sıralanmış ve sözlük sırasına göre listelenmiş tüm kombinasyonlar
Kısıtlar
1 ≤ candidates.length ≤ 502 ≤ candidates[i] ≤ 5002 ≤ target ≤ 500- Tüm
candidatesdeğerleri birbirinden farklıdır ve belirli bir sıraları yoktur. - En az bir kombinasyon
targetdeğerine ulaşır ve en fazla 150 kombinasyon ulaşır.
Örnekler
- Girdi
- candidates = [6, 2, 3]target = 8
- Çıktı
- [[2, 2, 2, 2], [2, 3, 3], [2, 6]]
- Açıklama
- Dört tane 2, 8 eder; 2 + 3 + 3 ve 2 + 6 da öyle. Üçü de 2 ile başladığından, sıralamayı ikinci değer belirler: 2, sonra 3, sonra 6. 2 olmadan yalnızca 3’ler ve 6’lar kalır ve bunların her türlü toplamı 3’ün katıdır; 8 ise öyle değildir.
- Girdi
- candidates = [5, 3, 4]target = 11
- Çıktı
- [[3, 3, 5], [3, 4, 4]]
- Açıklama
- 3 + 3 + 5 ve 3 + 4 + 4 toplamda 11 eder. İlk değerleri eşleşir ve ikinci değerde 3, 4'ten küçük olduğu için
[3, 3, 5]önce gelir. Yalnızca 4 ve 5'lerin hiçbir kombinasyonu 11 etmez.
- Girdi
- candidates = [4, 9]target = 9
- Çıktı
- [[9]]
- Açıklama
- 9 tek başına bir bileşimdir. 4'ün katları, 9'u geçerken yalnızca 4, 8 ve 12'yi verir; 4 + 9 ise zaten 13'tür, bu yüzden tek cevap
[9]olur.
Gönderirken +12 gizli test
Ek soru
Artık her aday en fazla bir kez kullanılabilir ve candidates tekrarlanan değerler içerebilir. Hiçbir kombinasyonun iki kez görünmemesi için aramayı nasıl değiştirirsin?
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
[2, 3, 3]ve[3, 2, 3]aynı kombinasyondur. Bir kombinasyonu yalnızca değerleri artan sıradayken oluşturursanız, her biri kaç farklı şekilde oluşturulabilir?Adayları sırala ve her seferinde bir değer ekleyerek bir kombinasyon oluştur.
nums[i]değerini ekledikten sonra, sıradaki değer yinenums[i]veya ondan sonraki herhangi bir değer olabilir; daha önceki bir değer olamaz.backtrack(start, remaining)yaz.remaining0 olduğunda, mevcut değerlerin bir kopyasını kaydet. Aksi takdirdestartdeğerinden başlayarak döngüye gir: bir değer ekle, aynı indeks ve daha küçük kalan değerle özyinelemeli çağrı yap, ardından değeri kaldır.remainingdeğerinden büyük olan ilk değere ulaştığında döngüden çık.
Çözüm
Her yanıt bir aday çoklu kümesidir ve tuzak, aynı çoklu kümeyi birden fazla kez oluşturmaktır: önce 2’yi, sonra 3’ü, ardından 3’ü seçmek ile önce 3’ü, sonra 2’yi, ardından 3’ü seçmek aynı kombinasyona ulaşır. Bu sorunu çözen fikir, her kombinasyonu artan sırada oluşturmaktır; böylece onu oluşturmanın tam olarak tek bir yolu olur. Ayrıca adayları sıralayarak bir dalın, sıradaki değer kalan miktardan büyük olduğu anda durmasını sağlarsın. Aynı artan sıralı ilerleyiş, sonradan sıralama yapmadan kombinasyonları sözlükbilimsel sırada verir.
Her adayın her sayısını dene
Doğru, ama en büyük testlerde bitmiyor
Sezgi
Bir kombinasyon, her adaydan kaç kopya kullandığıyla tamamen tanımlanır. [6, 2, 3] ve hedef 8 için [2, 3, 3] yanıtı bir tane 2, iki tane 3 ve hiç 6 içermez. Dolayısıyla her yanıtı bulmanın bir yolu, her aday için olası tüm adetleri denemek ve toplamı tam olarak target olan seçimleri tutmaktır. Bir aday c, en fazla target / c kez kullanılabilir; bu nedenle adedi 0'dan bu sınıra kadar değişir.
Adayları sıraladıktan sonra, her aday için bir seviye içeren bir karar ağacı düşünün. i. seviyede, i. değerden kaç kopya alacağınıza karar verirsiniz ve en alttaki her yaprak, eksiksiz bir adet seçimini temsil eder. Her çoklu kümenin tam olarak bir adet listesi olduğundan, hiçbir kombinasyon iki kez bulunmaz. En büyük adedi önce denemek, istenen sıralamayı da sağlar: iki yanıt bir değerin adedinde ilk kez farklılaştığında, daha fazla kopya içeren yanıt, diğer yanıt daha büyük bir değer içerirken o küçük değeri hâlâ içerir; bu nedenle önce gelir.
Sorun ağacın boyutudur. Yaprak sayısı, tüm adaylar için target / c + 1 değerlerinin çarpımıdır: sıralanmış [2, 3, 6] ve hedef 8 için 3 yanıt karşılığında 5 × 3 × 2 = 30 yaprak vardır. target / 2 değerinden büyük her aday, en fazla bir kez kullanılabilmesine rağmen yaprak sayısını ikiye katlar; dolayısıyla yalnızca bu türden 40 aday bile 2^40, yani yaklaşık 10^12 yaprak demektir. Büyük testler bu şekilde hazırlanmıştır ve bu yaklaşım onları tamamlayamaz.
Algoritma
- Adayları sıralayın ve her değer için bir tane olacak şekilde sayılardan oluşan bir dizi oluşturun.
choose(i, total)fonksiyonunu yazın; bu fonksiyoniindeksindeki değerin sayısını belirler.kiçintarget / nums[i]değerinden 0'a doğru ilerleyin, sayıyıkolarak ayarlayın vechoose(i + 1, total + k × nums[i])çağrısını yapın.- Her değerin bir sayısı olduğunda,
totaldeğeritargetdeğerine eşitse kombinasyonu saklayın ve her değeri sayısı kadar yazın. choose(0, 0)çağrısını yapın. Saklanan kombinasyonlar zaten sözlükbilimsel sıradadır.
def combinationSum(candidates, target):
nums = sorted(candidates)
counts = [0] * len(nums)
result = []
def choose(i, total):
if i == len(nums):
if total == target:
combo = []
for value, k in zip(nums, counts):
combo.extend([value] * k)
result.append(combo)
return
# Most copies first, so the combinations come out in lexicographic order.
for k in range(target // nums[i], -1, -1):
counts[i] = k
choose(i + 1, total + k * nums[i])
counts[i] = 0
choose(0, 0)
return resultArtan sırayla geri izleyin ve dalları budayın
Sezgi
Her kombinasyonu, onu yazacağın sırayla, her seferinde bir değer ekleyerek oluştur: artan sırada. Başlangıç indeksi bu sırayı zorunlu kılar. nums[i] değerini ekledikten sonra sıradaki değer yine nums[i] olabilir, çünkü bir aday tekrarlanabilir; sonraki herhangi bir değer de olabilir, ancak daha önceki bir değer olamaz. Bu yüzden i indeksini ekleyen çağrı yalnızca i indeksinden itibaren döngüye girer. Her kombinasyonun artan sırada tam olarak bir yazılışı vardır; dolayısıyla ağaçta tam olarak bir yolu bulunur ve [3, 2, 3] gibi bir tekrar asla oluşturulmaz.
Sıralanmış [2, 3, 6] ve hedef 8 için tüm ağaç şöyledir. Kökün kalan değeri 8'dir ve 2, 3 ve 6'yı dener. 2'nin altında 6 kalır. 2, 2'nin altında 4 kalır ve 2, 2, 2'den sonra 2 kalır; bir 2 daha eklendiğinde yanıt [2, 2, 2, 2] olur; 2, 2, 3'ten sonra 1 kalır ve bu yol sonuç vermez. 2, 3'ün altında 3 kalır ve yalnızca 3 ile 6 denenebilir; 3, [2, 3, 3] sonucunu verir. 2, 6'nın altında hiçbir şey kalmaz: [2, 6]. 3'ün altında yalnızca 3 ile 6 denenebilir ve 3, 3'ten sonra 2 kalır; bu da hiçbirini tamamlamaz. 6'nın altında 2 kalır ve yalnızca 6 denenebilir. Toplamda 12 çağrı; ilk yaklaşımın 30 yaprak düğümüne kıyasla.
Sıralama, çıkmaz bir yolu erken durmaya dönüştürür. nums[i] kalan değerden büyük olduğunda, sonraki tüm değerler de büyük olur; bu yüzden geri kalanını denemek yerine break ile döngüden çıkarsın. Yukarıdaki ağaçta, 1 kalan 2, 2, 3 düğümü 3'e bakar, sığmadığını görür ve 6'ya hiç bakmaz. Arama yalnızca toplamı hâlâ target değerinden küçük veya ona eşit olan önekleri ziyaret eder; bu yüzden ilk yaklaşımı başarısız kılan büyük testler burada birkaç bin çağrı alır.
Çıktı sırası da aynı yürüyüşten gelir. Her düzeyde döngü önce daha küçük değerleri dener ve her kombinasyon artan sırada yazılır. İki yanıt ilk kez yollarının ayrıldığı düzeyde farklılaşır ve orada daha küçük değeri içeren yol önce araştırıldığı için yanıtlar sözlük sırasıyla gelir. Değerler pozitif olduğundan ve her iki kombinasyon da aynı toplama ulaştığından, bir kombinasyon başka bir kombinasyonun öneki olamaz.
Algoritma
- Adayları artan sırada sırala.
backtrack(start, remaining)fonksiyonunu,pathadlı tek bir listeyi paylaşacak şekilde yaz.remaining0 isepath’in bir kopyasını kaydet.- Aksi takdirde,
ideğerinistart’tan sona kadar döngüye sok.nums[i] > remainingise dur: sonraki tüm değerler daha büyüktür. nums[i]değerini ekle, değer tekrarlanabilsin diyei + 1yerineiilebacktrack(i, remaining-nums[i])çağrısını yap, ardından değeri kaldır.backtrack(0, target)çağrısını yap ve zaten sözlükbilimsel sırada olan kaydedilmiş kombinasyonları döndür.
def combinationSum(candidates, target):
nums = sorted(candidates)
result = []
path = []
def backtrack(start, remaining):
if remaining == 0:
result.append(path[:])
return
for i in range(start, len(nums)):
if nums[i] > remaining:
break # sorted, so every later value is too big as well
path.append(nums[i])
backtrack(i, remaining - nums[i]) # i, not i + 1: nums[i] may repeat
path.pop()
backtrack(0, target)
return result
Tuzaklar ve uç durumlar
Yanlış yanıtların çoğu aritmetikten değil, aramanın nasıl sıralandığından kaynaklanır.
- Geçerli dizinden başlamak yerine her düzeyde tüm adaylar üzerinde döngü kurmak,
[2, 3, 3],[3, 2, 3]ve[3, 3, 2]dizilerini üç ayrı yanıt olarak oluşturur. Her yanıtı sıralayıp yinelenenleri sonradan kaldırmak doğru listeyi verir, ancak üstel olarak daha fazla işlem yapar. iyerinei + 1ile özyinelemeli çağrı yapmak, her değerin yalnızca bir kez görünmesini sağlar; bu yüzden[2, 2, 2, 2]eksik kalır.- Kopyası yerine doğrudan
pathdeğerini kaydetmek: Bu durumda kaydedilen her yanıt aynı listedir ve geri izleme işlemi sonunda bu listeyi boşaltır. - Sıralamadığınız adaylarda
breakkullanmak. Geriye 2 kalmışken[6, 2, 3]için döngü 6'da durur ve 2'yi hiç denemez. - Kombinasyonları sıralanmamış girdinin önerdiği sırayla döndürmek. Beklenen liste sözlükbilimsel sıradadır; sıralı arama, ek bir sıralama yapmadan bunu sağlar.
- Lua ve R'de diziler 1'den başladığından, ilk çağrı 1 dizininden başlar ve döngü dizinin uzunluğuna kadar çalışır.
Sıkça sorulan sorular4
Combination Sum'ın zaman karmaşıklığı nedir?
Geri izleme araması üsteldir. n aday, t hedefi ve m en küçük aday için bir birleşim en fazla t/m değer içerir ve her adımda en fazla n seçenek vardır; bu da iş miktarını O(n^(t/m)) ile sınırlar. Sıralanmış adaylar üzerinde budama yapmak, arama yalnızca toplamı hâlâ t değerinden küçük veya eşit olan önekleri ziyaret ettiği için gerçek çağrı sayısını bunun çok altında tutar. Ek alan, geçerli yol ve çağrı yığını için O(t/m) kadardır; çıktının kapladığı alan buna eklenir.
Combination Sum'da neden i + 1 yerine i ile özyineleme yapıyorsun?
i ile özyineleme yapmak, sonraki değerin yine aynı aday olmasını sağlar; bir değerin birden fazla kez kullanılmasının yolu budur. i + 1 ile özyineleme yapmak ise o değeri geçer ve problemi her adayın en fazla bir kez kullanıldığı çeşide dönüştürür. Kuralın diğer yarısı da aynı derecede önemlidir: i konumundan önceki bir indekse asla geri dönmemek, her kombinasyonu artan sırada tutar ve yinelenenleri önler.
Bir küme kullanmadan yinelenen kombinasyonlardan nasıl kaçınırsınız?
Artan sırada, tüm kombinasyonları tek bir sabit sırayla oluşturun. Başlangıç indeksi bunu sağlar: nums[i] yerleştirildikten sonra arama yalnızca nums[i] ve sonraki değerlere bakar. Böylece her kombinasyonun arama ağacında tam olarak bir yolu olur; bu nedenle her kombinasyon bir kez üretilir ve kümeye ya da sonradan yinelenenleri ayıklamaya gerek kalmaz.
Combination Sum dinamik programlamayla çözülebilir mi?
Evet. 0'dan hedefe kadar her toplam için o toplama ulaşan kombinasyonların listesini tutun ve her seferinde bir aday ekleyin; böylece her listedeki değerler artan sırada kalır. Bu, para üstü verme yollarını saymayla aynı fikirdir. Çıkmaza iki kez girmez, ancak her toplam için tüm kısmi kombinasyonları saklar; bu da geri izlemeye kıyasla çok daha fazla bellek kullanır ve son listenin sıralanması gerekebilir. Çıktının kendisi üstel büyüklükte olabileceğinden, genellikle geri izleme tercih edilir.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def combinationSum(candidates, target):
# Kodu buraya yazınDurum 1
Durum 2
Durum 3
Girdi
candidates = [6, 2, 3] target = 8
Beklenen
[[2, 2, 2, 2], [2, 3, 3], [2, 6]]