कोडिंग साक्षात्कार की तैयारी · पाठ

Tabulation के साथ Bottom-Up DP

Top-down समाधानों को पुनरावृत्तीय DP तालिकाओं में बदलिए और जहाँ केवल पिछली कुछ प्रविष्टियाँ चाहिएँ, वहाँ स्थान O(n) से O(1) तक घटाइए।

पाठ 3, कुल 4 में से13 चरण

Tabulation के साथ Bottom-Up DP, CoddyKit पर कोडिंग साक्षात्कार की तैयारी का एक निःशुल्क पाठ है। यह 4 में से 3वाँ पाठ है। आप नीचे पूरा पाठ निःशुल्क पढ़ सकते हैं—फिर अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर के साथ ब्राउज़र में इसका व्यावहारिक अभ्यास कर सकते हैं। यह कोडिंग साक्षात्कार की तैयारी सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

नीचे-से-ऊपर DP: सारणीकरण का तरीका

नीचे-से-ऊपर DP (सारणीकरण) सबसे छोटी उप-समस्याओं से शुरू करके और धीरे-धीरे उत्तर तक पहुँचते हुए उप-समस्याओं के उत्तरों की एक सारणी भरता है। नीचे की ओर पुनरावर्तन करके ऊपर आते समय कैश करने के बजाय, आप नीचे से ऊपर की ओर पुनरावृत्तिमूलक तरीके से गणना करते हैं। सारणी आम तौर पर 1D या 2D ऐरे होती है, जिसमें प्रत्येक खाना पहले भरे गए खानों से निर्धारित होता है। इससे पुनरावर्तन पूरी तरह समाप्त हो जाता है — न फ़ंक्शन-आह्वान स्टैक, न पुनरावृत्ति सीमा, और बेहतर कैश स्थानीयता।

# Converting top-down to bottom-up:
# Top-down: start at fib(n), recurse to smaller, cache
# Bottom-up: start at fib(0), fill table to fib(n)

# Key question for bottom-up:
# 'In what order do I fill the table so that when I compute dp[i],
# all values dp[i] depends on are already filled?'
# For Fibonacci: dp[i] needs dp[i-1] and dp[i-2]
# Fill order: i = 2, 3, 4, ..., n (left to right)
print('Bottom-up: fill small sub-problems first, build to answer')

नीचे-से-ऊपर फिबोनाची

नीचे-से-ऊपर फिबोनाची dp[0..n] को बाएँ से दाएँ भरता है। i >= 2 के लिए dp[i] = dp[i-1] + dp[i-2]। आधार मामले dp[0] = 0 और dp[1] = 1 हैं, जिन्हें सीधे ऐरे में संग्रहीत किया जाता है। पूर्ण सारणी के लिए समय O(n) और स्थान O(n) है। जब आप देखते हैं कि dp[i] केवल पिछले दो मानों पर निर्भर है, तो दो चरों के साथ स्थान को O(1) तक घटा सकते हैं — यही स्थान अनुकूलन का चरण है।

def fib_bottom_up(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)
    dp[0] = 0  # base case
    dp[1] = 1  # base case
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

print([fib_bottom_up(i) for i in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# Space-optimised to O(1):
def fib_optimised(n):
    if n <= 1: return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

print(fib_optimised(50))  # 12586269025

नीचे-से-ऊपर सिक्का-परिवर्तन

सिक्का-परिवर्तन के लिए नीचे-से-ऊपर सारणी dp[0..amount] होती है, जहाँ dp[i] राशि i बनाने के लिए आवश्यक न्यूनतम सिक्कों की संख्या है। dp[0] = 0 प्रारंभ करें (शून्य राशि के लिए शून्य सिक्के) और dp[1..amount] = infinity रखें। 1 से target तक प्रत्येक राशि i के लिए हर सिक्के को आज़माएँ: यदि i >= coin हो, तो dp[i] = min(dp[i], 1 + dp[i - coin])। उत्तर dp[amount] है, या यदि मान अभी भी infinity हो तो -1 है।

def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # base case: 0 coins for amount 0
    for i in range(1, amount + 1):
        for coin in coins:
            if i >= coin:  # can use this coin
                dp[i] = min(dp[i], 1 + dp[i - coin])
    return dp[amount] if dp[amount] != float('inf') else -1

print(coin_change([1, 5, 6, 9], 11))  # 2: (5+6)
print(coin_change([2], 3))             # -1: impossible
print(coin_change([1, 2, 5], 11))      # 3: 5+5+1
print(coin_change([186, 419, 83, 408], 6249))  # 20

भरने का क्रम: महत्वपूर्ण अंतर्दृष्टि

भरने का क्रम नीचे-से-ऊपर DP का केंद्र है। किसी भी अवस्था dp[i] के लिए, जिन सभी अवस्थाओं पर वह निर्भर है, उनकी गणना पहले होनी चाहिए। 1D DP में यदि dp[i] dp[i-1] और dp[i-2] पर निर्भर है, तो बाएँ से दाएँ भरें। 2D DP में यदि dp[i][j] dp[i-1][j] और dp[i][j-1] पर निर्भर है, तो पंक्ति-दर-पंक्ति भरें (ऊपर से नीचे, बाएँ से दाएँ)। कोड लिखने से पहले निर्भरता के तीर हमेशा बनाएँ, ताकि भरने का क्रम निश्चित हो सके।

# Fill order examples:

# 1D: dp[i] = f(dp[i-1], dp[i-2])
# Arrows point LEFT: fill LEFT TO RIGHT
# i: 0 -> 1 -> 2 -> ... -> n

# 2D: dp[i][j] = f(dp[i-1][j], dp[i][j-1])
# Arrows point LEFT and UP: fill TOP-LEFT TO BOTTOM-RIGHT
# Fill row 0 first, then row 1, etc.

# 2D reversed: dp[i][j] = f(dp[i+1][j], dp[i][j+1])
# Arrows point RIGHT and DOWN: fill BOTTOM-RIGHT TO TOP-LEFT
# Used in interval DP and some string problems

print('Draw dependencies first, then determine fill order')

नीचे-से-ऊपर LCS: 2D सारणी

सबसे लंबा साझा उप-अनुक्रम की नीचे-से-ऊपर सारणी (m+1) × (n+1) आकार की होती है, जहाँ dp[i][j] s1[:i] और s2[:j] का LCS है। आधार मामले: dp[0][j] = dp[i][0] = 0 (रिक्त स्ट्रिंग का किसी भी स्ट्रिंग के साथ LCS 0 होता है)। पंक्ति-दर-पंक्ति भरें: यदि s1[i-1] == s2[j-1], तो dp[i][j] = 1 + dp[i-1][j-1]; अन्यथा dp[i][j] = max(dp[i-1][j], dp[i][j-1])। उत्तर dp[m][n] है।

def lcs_bottom_up(s1, s2):
    m, n = len(s1), len(s2)
    # (m+1) x (n+1) table, initialised to 0
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:         # characters match
                dp[i][j] = 1 + dp[i-1][j-1]
            else:                            # skip one character
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])

    return dp[m][n]

print(lcs_bottom_up('abcde', 'ace'))   # 3
print(lcs_bottom_up('ABCBDAB', 'BDCAB'))  # 4: 'BCAB' or 'BDAB'

स्थान अनुकूलन: चलायमान सारणी

कई 2D DP सारणियों को 1D (या 2 पंक्तियों) में बदला जा सकता है, क्योंकि dp[i][j] केवल वर्तमान पंक्ति और पिछली पंक्ति पर निर्भर होता है। दो सारणियाँ रखें: prev और curr, या एक ही सारणी को सही क्रम में अद्यतन करें। LCS के लिए dp[i][j] dp[i-1][j], dp[i][j-1] और dp[i-1][j-1] पर निर्भर होता है — इसलिए केवल पिछली पंक्ति रखना पर्याप्त है।

def lcs_space_optimised(s1, s2):
    m, n = len(s1), len(s2)
    # Keep only one row (previous row state)
    prev = [0] * (n + 1)
    for i in range(1, m + 1):
        curr = [0] * (n + 1)
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                curr[j] = 1 + prev[j-1]  # dp[i-1][j-1]
            else:
                curr[j] = max(prev[j], curr[j-1])  # dp[i-1][j] and dp[i][j-1]
        prev = curr
    return prev[n]

print(lcs_space_optimised('abcde', 'ace'))   # 3
# Space: O(n) instead of O(mn)

नीचे-से-ऊपर घर लूटने की समस्या

घर लूटने की समस्या का नीचे-से-ऊपर समाधान dp[0..n-1] भरता है, जहाँ dp[i] घर 0 से i तक चोरी करके मिलने वाला अधिकतम लाभ है। dp[0] = nums[0], dp[1] = max(nums[0], nums[1]), और i >= 2 के लिए: dp[i] = max(dp[i-1], dp[i-2] + nums[i])। चूँकि dp[i] केवल पिछले दो मानों पर निर्भर है, इसलिए दो चरों के साथ स्थान को तुरंत O(1) तक अनुकूलित किया जा सकता है — दो-चरणीय निर्भरताओं वाले 1D DP का यह एक सामान्य पैटर्न है।

def rob_bottom_up(nums):
    if not nums: return 0
    if len(nums) == 1: return nums[0]

    # Full table version: O(n) space
    dp = [0] * len(nums)
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i in range(2, len(nums)):
        dp[i] = max(dp[i-1], dp[i-2] + nums[i])
    return dp[-1]

def rob_optimised(nums):
    # O(1) space: only need last two values
    if not nums: return 0
    if len(nums) == 1: return nums[0]
    prev2, prev1 = nums[0], max(nums[0], nums[1])
    for i in range(2, len(nums)):
        prev2, prev1 = prev1, max(prev1, prev2 + nums[i])
    return prev1

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

ग्रिड में न्यूनतम पथ योग

न्यूनतम पथ योग (LeetCode #64): ऊपर-बाएँ से नीचे-दाएँ तक ऐसा पथ खोजें, जिसमें मानों का योग न्यूनतम हो (आप केवल दाएँ या नीचे जा सकते हैं)। 2D DP: dp[i][j] = खाने (i,j) तक पहुँचने का न्यूनतम योग। dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])। बाएँ से दाएँ और ऊपर से नीचे भरें। आधार मामला: dp[0][0] = grid[0][0], पहली पंक्ति केवल दाईं ओर जाकर भरी जाती है और पहला स्तंभ केवल नीचे जाकर।

def min_path_sum(grid):
    rows, cols = len(grid), len(grid[0])
    dp = [[0] * cols for _ in range(rows)]
    dp[0][0] = grid[0][0]
    # Fill first row (can only come from left)
    for c in range(1, cols):
        dp[0][c] = dp[0][c-1] + grid[0][c]
    # Fill first column (can only come from above)
    for r in range(1, rows):
        dp[r][0] = dp[r-1][0] + grid[r][0]
    # Fill rest of the table
    for r in range(1, rows):
        for c in range(1, cols):
            dp[r][c] = grid[r][c] + min(dp[r-1][c], dp[r][c-1])
    return dp[rows-1][cols-1]

grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum(grid))  # 7: 1+3+1+1+1

DP सारणी को उसी स्थान पर संशोधित करना

जब अतिरिक्त स्थान की अनुमति न हो, तो कभी-कभी आप आगत ग्रिड को ही DP सारणी के रूप में संशोधित कर सकते हैं। न्यूनतम पथ योग के लिए grid[i][j] को उस खाने तक पहुँचने की न्यूनतम लागत से अधिलेखित करें। इससे O(1) अतिरिक्त स्थान लगता है, लेकिन आगत नष्ट हो जाता है — साक्षात्कारकर्ता को इस समझौते के बारे में हमेशा बताएँ और पुष्टि करें कि यह स्वीकार्य है। यदि आगत को सुरक्षित रखना आवश्यक हो, तो चलायमान-सारणी तरीके का उपयोग करें।

def min_path_sum_inplace(grid):
    rows, cols = len(grid), len(grid[0])
    # Modify grid in-place (O(1) extra space, destroys input)
    for r in range(rows):
        for c in range(cols):
            if r == 0 and c == 0:
                continue  # starting cell
            elif r == 0:
                grid[r][c] += grid[r][c-1]  # first row
            elif c == 0:
                grid[r][c] += grid[r-1][c]  # first column
            else:
                grid[r][c] += min(grid[r-1][c], grid[r][c-1])
    return grid[rows-1][cols-1]

import copy
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_inplace(copy.deepcopy(grid)))  # 7

सिक्का-परिवर्तन में टॉप-डाउन और बॉटम-अप की तुलना

दोनों तरीके सिक्का-परिवर्तन की समस्या को सर्वोत्तम रूप से हल करते हैं, लेकिन व्यवहार में अलग-अलग हैं। टॉप-डाउन लिखने में अधिक साफ़ है और केवल उन्हीं उप-समस्याओं की गणना करता है, जिन तक वास्तव में पहुँचा जा सकता है। बॉटम-अप 0 से लक्ष्य तक की सभी राशियों की गणना करता है, यहाँ तक कि उन राशियों की भी, जिन्हें दिए गए सिक्कों से प्राप्त नहीं किया जा सकता और जो अनंत पर ही रहती हैं। विरल समस्याओं (जिनमें पहुँच योग्य अवस्थाएँ कम हों) के लिए टॉप-डाउन अधिक प्रभावी है; सघन समस्याओं के लिए बॉटम-अप में अतिरिक्त लागत कम होती है।

import functools

# Top-down: only computes reachable amounts
def coin_change_top(coins, amount):
    @functools.lru_cache(maxsize=None)
    def dp(rem):
        if rem == 0: return 0
        if rem < 0: return float('inf')
        return 1 + min(dp(rem - c) for c in coins)
    r = dp(amount)
    return r if r != float('inf') else -1

# Bottom-up: computes all amounts 0 to target
def coin_change_bottom(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for i in range(1, amount + 1):
        for c in coins:
            if i >= c: dp[i] = min(dp[i], 1 + dp[i-c])
    return dp[amount] if dp[amount] != float('inf') else -1

print(coin_change_top([1,5,6,9], 11))    # 2
print(coin_change_bottom([1,5,6,9], 11)) # 2

अद्वितीय पथ: पारंपरिक 2D DP

अद्वितीय पथ (LeetCode #62) में m×n ग्रिड के ऊपरी-बाएँ से निचले-दाएँ तक जाने वाले पथों की संख्या ज्ञात करनी होती है, जहाँ केवल दाएँ या नीचे की ओर चला जा सकता है। पुनरावृत्ति संबंध सीधा है: dp[i][j] = dp[i-1][j] + dp[i][j-1] — ऊपर से आने वाले पथ और बाएँ से आने वाले पथ। आधार अवस्थाएँ: पहली पूरी पंक्ति और पहला पूरा स्तंभ, दोनों में ठीक 1 पथ होता है (क्योंकि चलने के लिए केवल एक दिशा होती है)। यह 2D DP O(mn) समय में भरता है और रोलिंग पंक्ति की सहायता से स्थान को O(n) तक घटाया जा सकता है।

def unique_paths(m, n):
    # dp[i][j] = number of paths to reach cell (i,j)
    dp = [[1] * n for _ in range(m)]
    # Base: first row and first column are all 1
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
    return dp[m-1][n-1]

print(unique_paths(3, 7))   # 28
print(unique_paths(3, 2))   # 3

# O(n) space rolling row:
def unique_paths_opt(m, n):
    row = [1] * n
    for _ in range(1, m):
        for j in range(1, n):
            row[j] += row[j-1]
    return row[n-1]

print(unique_paths_opt(3, 7))  # 28

त्वरित जाँच

इस पाठ में सिखाई गई डेटा संरचनाएँ और एल्गोरिदम — कोडिंग साक्षात्कार की तैयारी से जुड़ी अवधारणाओं की अपनी समझ जाँचिए।

पाठ का पुनरावलोकन

इस पाठ में आपने सीखा: सारणीकरण वाले बॉटम-अप DP में निर्भरता-तीरों से भरने का क्रम निर्धारित करना, रोलिंग सरणियों (O(mn) से O(n)) और दो चरों के अनुश्रवण (O(n) से O(1)) से स्थान का अनुकूलन, तथा फिबोनाची, सिक्का-परिवर्तन, LCS, घर-लूट और न्यूनतम पथ योग के बॉटम-अप कार्यान्वयन। अब हम सिक्का-परिवर्तन और सीढ़ियों की न्यूनतम-लागत वाली समस्याओं को आरंभ से अंत तक हल करेंगे।

शुरुआत निःशुल्क

एआई शिक्षक के साथ कोडिंग साक्षात्कार की तैयारी सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
90
पाठ
360

अक्सर पूछे जाने वाले प्रश्न

क्या “Tabulation के साथ Bottom-Up DP” पाठ निःशुल्क है?

हाँ—“Tabulation के साथ Bottom-Up DP” का पूरा पाठ यहाँ वेब पर निःशुल्क पढ़ा जा सकता है। इंटरैक्टिव अभ्यास (अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर) करने और कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम का बाकी हिस्सा अनलॉक करने के लिए CoddyKit PRO लें। कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“Tabulation के साथ Bottom-Up DP” में मैं क्या सीखूँगा?

Top-down समाधानों को पुनरावृत्तीय DP तालिकाओं में बदलिए और जहाँ केवल पिछली कुछ प्रविष्टियाँ चाहिएँ, वहाँ स्थान O(n) से O(1) तक घटाइए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ कोडिंग साक्षात्कार की तैयारी का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या कोडिंग साक्षात्कार की तैयारी शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर कोडिंग साक्षात्कार की तैयारी शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 3वाँ पाठ है।

“Tabulation के साथ Bottom-Up DP” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस कोडिंग साक्षात्कार की तैयारी पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर कोडिंग साक्षात्कार की तैयारी पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ
  2. Memoisation के साथ Top-Down DP
  3. Tabulation के साथ Bottom-Up DP
  4. Coin Change और न्यूनतम-लागत सीढ़ी
← कोडिंग साक्षात्कार की तैयारी पर वापस जाएँ