0Pricing
Coding Interview Prep · Ders

Ev Soyguncusu: Al veya Atla Bağıntısı

Soy/atla kararını bir DP bağıntısı olarak modelleyin, alanı iki değişkene indirin ve çözümü dairesel evlere genişletin.

Ev Soyguncusu: Al veya Atla Bağıntısı, CoddyKit'te ücretsiz bir Coding Interview Prep dersidir. Bu, 4 dersinin 1. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Coding Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Coding Interview Prep kursu toplamda 4 dersten oluşur.

Ev Soyguncusu Problemi

Ev Soyguncusu problemi şunu sorar: Her evdeki para miktarını temsil eden negatif olmayan tam sayılardan oluşan bir dizi verildiğinde, birbirine komşu iki evi soymadan çalabileceğiniz maksimum para miktarını bulun. Örneğin [2, 7, 9, 3, 1] dizisi 12 sonucunu verir (0, 2 ve 4 numaralı evleri soyun). Bu, her adımda ikili bir karar verdiğiniz klasik bir 1B DP problemidir.

nums = [2, 7, 9, 3, 1]
# Can't rob adjacent houses
# Options: rob index 0 and 2 and 4 → 2+9+1=12
# or rob index 1 and 3 → 7+3=10
print('Max profit:', 12)  # answer is 12

Bağıntıyı Tanımlama

dp[i], ilk i+1 evden çalınabilecek maksimum para miktarı olsun. Her i. evde iki seçeneğiniz vardır: onu atlamak (dp[i-1] değerini almak) veya onu soymak (nums[i] + dp[i-2] değerini almak). Bağıntı dp[i] = max(dp[i-1], nums[i] + dp[i-2]) şeklindedir. Bu, birçok DP probleminde görülen temel take-or-skip örüntüsüdür.

# Recurrence: dp[i] = max(dp[i-1], nums[i] + dp[i-2])
# Base cases:
# dp[0] = nums[0]  (only one house, rob it)
# dp[1] = max(nums[0], nums[1])  (take the richer of the two)
def rob(nums):
    n = len(nums)
    if n == 1: return nums[0]
    dp = [0] * n
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i in range(2, n):
        dp[i] = max(dp[i-1], nums[i] + dp[i-2])
    return dp[-1]

print(rob([2, 7, 9, 3, 1]))  # 12

DP Tablosunu Adım Adım İzleme

[2, 7, 9, 3, 1] için tabloyu adım adım izleyelim: dp[0] = 2, dp[1] = max(2, 7) = 7, dp[2] = max(7, 9+2) = 11, dp[3] = max(11, 3+7) = 11, dp[4] = max(11, 1+11) = 12. Son yanıt dp[4] = 12 olur. Tabloyu elle adım adım izlemek, bağıntının her konumda hem alma hem de atlama seçeneklerini doğru şekilde ele aldığını doğrular.

nums = [2, 7, 9, 3, 1]
dp = [0] * len(nums)
dp[0] = 2
dp[1] = max(2, 7)  # 7
for i in range(2, len(nums)):
    skip = dp[i-1]
    take = nums[i] + dp[i-2]
    dp[i] = max(skip, take)
    print(f'dp[{i}] = max({skip}, {nums[i]}+{dp[i-2]}) = {dp[i]}')
print('Answer:', dp[-1])

Bellek Kullanımını O(1)'e İndirme

DP tablosu yalnızca iki konum geriye bakar; bu nedenle dizinin tamamını iki değişkenle değiştirebiliriz: prev2 (iki adım gerideki değer) ve prev1 (bir adım gerideki değer). Her yinelemeden sonra kaydırma yaparız: prev2 = prev1 ve prev1 = current. Bu, time karmaşıklığını O(n) olarak korurken bellek kullanımını O(n)'den O(1)'e düşürür.

def rob_optimised(nums):
    if not nums: return 0
    if len(nums) == 1: return nums[0]
    prev2 = nums[0]
    prev1 = max(nums[0], nums[1])
    for i in range(2, len(nums)):
        curr = max(prev1, nums[i] + prev2)
        prev2 = prev1
        prev1 = curr
    return prev1

print(rob_optimised([2, 7, 9, 3, 1]))   # 12
print(rob_optimised([1, 2, 3, 1]))       # 4

Ele Alınması Gereken Sınır Durumları

Çözümünüzü her zaman sınır durumlarına karşı sınayın: boş bir dizi (0 döndürür), tek öğeli bir dizi (o öğeyi döndürür) ve iki öğeli bir dizi (ikisinin maksimumunu döndürür). Mülakatlarda bu durumlardan söz etmek ve bunları ele almak, titiz olduğunuzu gösterir. if n == 1 koşulu, dp[1] için nums[1]'e erişirken dizi sınırları dışına çıkılmasını önler.

def rob(nums):
    if not nums: return 0
    if len(nums) == 1: return nums[0]
    prev2 = nums[0]
    prev1 = max(nums[0], nums[1])
    for i in range(2, len(nums)):
        curr = max(prev1, nums[i] + prev2)
        prev2, prev1 = prev1, curr
    return prev1

print(rob([]))         # 0
print(rob([5]))        # 5
print(rob([3, 10]))    # 10
print(rob([10, 3]))    # 10

Ev Soyguncusu II: Dairesel Evler

Dairesel çeşitte (LeetCode 213) evler bir daire şeklinde yerleştirilir; bu nedenle ilk ve son ev birbirine komşudur. Doğrusal bağıntıyı doğrudan uygulayamazsınız. Temel fikir şudur: ya ilk evi soyup son evi dışarıda bırakırsınız ya da ilk evi dışarıda bırakıp son evi dahil edersiniz. Doğrusal ev soyguncusu çözümünü her iki alt dizi üzerinde çalıştırın ve maksimumu alın.

def rob_linear(nums):
    prev2, prev1 = 0, 0
    for n in nums:
        prev2, prev1 = prev1, max(prev1, n + prev2)
    return prev1

def rob_circular(nums):
    if len(nums) == 1: return nums[0]
    # Either include first (exclude last) or include last (exclude first)
    return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))

print(rob_circular([2, 3, 2]))   # 3
print(rob_circular([1, 2, 3, 1]))  # 4

Açgözlü Yaklaşım Burada Neden Başarısız Olur

Saf bir açgözlü yaklaşım, her zaman mevcut en değerli evi soymaya çalışabilir. Ancak bu yaklaşım [2, 1, 1, 2] gibi girdilerde başarısız olur: açgözlü yaklaşım 0. evi (değeri 2), ardından 3. evi (değeri 2) seçerek toplam 4 elde eder; ancak 0. ve 2. evleri soymak da 3 verir. Bekleyin — bu durumda açgözlü yaklaşım işe yarıyor! Ancak [1, 3, 1, 3, 100] dizisini deneyin: açgözlü yaklaşım 3 ve 3'ü (1 ve 3 indekslerini) seçerek 6 elde eder ve en iyi çözüm olan 1+1+100=102'yi kaçırır. Yerel olarak en iyi seçimler küresel olarak en iyi sonucu garanti etmediği için DP gereklidir.

# Greedy failure example
nums = [1, 3, 1, 3, 100]
# Greedy: pick max each step
# picks 3 (index 1), then 3 (index 3) → total 6
# DP optimal: pick 1 (index 0) + 1 (index 2) + 100 (index 4) → 102

def rob(nums):
    prev2, prev1 = 0, 0
    for n in nums:
        prev2, prev1 = prev1, max(prev1, n + prev2)
    return prev1

print(rob(nums))  # 102

Take-or-Skip Örüntüsünü Tanıma

Take-or-skip örüntüsü, ev soyguncusunun ötesinde de genellenebilir. Bir diziyi taradığınız ve her konumda mevcut öğeyi dahil etmek (önceki öğeyi atlamak) veya onu hariç tutmak (önceki sonucu korumak) arasında seçim yaptığınız her durumda bir take-or-skip DP'si söz konusudur. Bu örüntüyü uygulamanız gerektiğini gösteren işaretler olarak birbirine komşu iki öğenin seçilememesi veya örtüşen aralıkların bulunmaması gibi kısıtları arayın.

# General take-or-skip template
def take_or_skip(values, gap=1):
    '''Max sum where selected elements must be at least gap+1 apart.'''
    n = len(values)
    if n == 0: return 0
    # dp[i] = best up to index i
    dp = [0] * (n + gap)
    for i in range(n):
        take = values[i] + (dp[i - 1] if i >= 1 else 0)
        skip = dp[i + gap - 1] if i + gap - 1 < len(dp) else 0
        dp[i + gap] = max(skip, take)
    return dp[-1]

print(take_or_skip([2, 7, 9, 3, 1]))  # house robber-like

Sil ve Kazan Çeşidi

Sil ve Kazan (LeetCode 740) şunu sorar: Seçtiğiniz her sayı için num × count(num) kazanırsınız; ancak num-1 ve num+1 değerlerinin tüm oluşumlarını silmeniz gerekir. Bu, doğrudan ev soyguncusu problemine indirgenir: tüm değerler için earn[v] = v × count(v) dizisini oluşturun, ardından bu dizi üzerinde ev soyguncusu çözümünü çalıştırın. İndirgemeleri tanımak, mülakatlarda önemli bir beceridir.

from collections import Counter

def delete_and_earn(nums):
    if not nums: return 0
    count = Counter(nums)
    max_val = max(nums)
    # earn[v] = total points from taking all v's
    earn = [v * count[v] for v in range(max_val + 1)]
    # Now run house robber on earn
    prev2, prev1 = 0, 0
    for e in earn:
        prev2, prev1 = prev1, max(prev1, e + prev2)
    return prev1

print(delete_and_earn([3, 4, 2]))    # 6 (take 3+3=no, take 4+2=6)
print(delete_and_earn([2, 2, 3, 3, 3, 4]))  # 9 (take all 3s)

Ev Soyguncusu III: İkili Ağaç

Ev Soyguncusu III probleminde evler ikili ağaç şeklinde düzenlenmiştir. Bir düğümü ve doğrudan üst düğümünü aynı anda soyamazsınız. İki değer döndüren bir yardımcı tanımlayın: rob(node) → (rob_root, skip_root). Kökü soyarsanız her iki çocuğun atlama değerlerini toplarsınız. Kökü atlarsanız her çocuk için en iyi değeri toplarsınız. Bu, her düğümde take-or-skip kararı verilen bir ardıl sıralı DFS işlemidir.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def rob_tree(root):
    def dfs(node):
        if not node: return (0, 0)  # (rob, skip)
        l_rob, l_skip = dfs(node.left)
        r_rob, r_skip = dfs(node.right)
        rob = node.val + l_skip + r_skip
        skip = max(l_rob, l_skip) + max(r_rob, r_skip)
        return (rob, skip)
    return max(dfs(root))

# Tree: 3 -> 2,3 -> None,3,None,1
root = TreeNode(3, TreeNode(2, None, TreeNode(3)), TreeNode(3, None, TreeNode(1)))
print(rob_tree(root))  # 7

Karmaşıklık ve Mülakat Tartışması

Doğrusal ev soyguncusu, iki değişkenli optimizasyonla O(n) time ve O(1) bellek kullanır. Dairesel çeşidi de doğrusal sürümü iki kez çağırdığı için O(n) time içinde çalışır. Ağaç çeşidi O(n) time ve h'nin ağaç yüksekliği olduğu O(h) bellek kullanır. Mülakatta kodlamadan sonra her zaman karmaşıklığı belirtin ve bellek optimizasyonundan söz edin — bu, ilk çalışan çözümün ötesini düşündüğünüzü gösterir.

# Summary of complexities
# Linear House Robber:
#   Time: O(n), Space: O(1) with two-variable trick
# Circular House Robber:
#   Time: O(n), Space: O(1) (two passes)
# Tree House Robber:
#   Time: O(n), Space: O(h) call stack

# Quick benchmark
import time
import random
nums = [random.randint(0, 100) for _ in range(10**6)]
start = time.time()
prev2 = prev1 = 0
for n in nums:
    prev2, prev1 = prev1, max(prev1, n + prev2)
print(f'1M elements in {time.time()-start:.3f}s, result={prev1}')

Kısa Kontrol

Bu dersteki Veri Yapıları ve Algoritmalar — Kodlama Mülakatına Hazırlık kavramlarını ne ölçüde anladığınızı sınayın.

Ders Özeti

Bu derste şunları öğrendiniz: al ya da atla bağıntısı dp[i] = max(dp[i-1], nums[i] + dp[i-2]), iki kayan değişkenle O(n) alan kullanımını O(1)'e düşürme ve bu örüntüyü dairesel dizilere ve ikili ağaçlara genişletme. Sırada, Kadane algoritmasını kullanarak Maksimum Alt Dizi ve Maksimum Çarpım Alt Dizisi problemlerini inceleyeceğiz.

Sıkça Sorulan Sorular

“Ev Soyguncusu: Al veya Atla Bağıntısı” dersi ücretsiz mi?

Evet — “Ev Soyguncusu: Al veya Atla Bağıntısı” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Coding Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Coding Interview Prep kursu toplamda 4 dersten oluşur.

“Ev Soyguncusu: Al veya Atla Bağıntısı” dersinde ne öğreneceğim?

Soy/atla kararını bir DP bağıntısı olarak modelleyin, alanı iki değişkene indirin ve çözümü dairesel evlere genişletin. Coding Interview Prep ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Coding Interview Prep öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Coding Interview Prep, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 1. dersidir.

“Ev Soyguncusu: Al veya Atla Bağıntısı” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Coding Interview Prep dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Coding Interview Prep dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Ev Soyguncusu: Al veya Atla Bağıntısı
  2. Maksimum Alt Dizi ve Maksimum Çarpımlı Alt Dizi
  3. Sözcük Bölme ve Dizeyi Parçalara Ayırma
  4. Yolları Çözümleme ve Yolları Sayma
← Coding Interview Prep Sayfasına Dön