DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ
पहचानिए कि brute-force पुनरावृत्ति कब एक ही उपसमस्या फिर हल करती है, Fibonacci का पुनरावृत्ति-वृक्ष बनाइए और घातीय विस्तार देखिए।
DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ, CoddyKit पर कोडिंग साक्षात्कार की तैयारी का एक निःशुल्क पाठ है। यह 4 में से 1वाँ पाठ है। आप नीचे पूरा पाठ निःशुल्क पढ़ सकते हैं—फिर अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर के साथ ब्राउज़र में इसका व्यावहारिक अभ्यास कर सकते हैं। यह कोडिंग साक्षात्कार की तैयारी सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
डायनेमिक प्रोग्रामिंग क्या है
डायनेमिक प्रोग्रामिंग (DP) जटिल समस्याओं को सरल, एक-दूसरे से जुड़ी उप-समस्याओं में बाँटकर, प्रत्येक उप-समस्या को एक बार हल करके और परिणाम को संग्रहीत करके हल करता है, ताकि अनावश्यक गणना से बचा जा सके। DP तब लागू होती है जब किसी समस्या में दो तत्व हों: एक-दूसरे से जुड़ी उप-समस्याएँ (सरल पुनरावृत्ति में एक ही उप-समस्या कई बार हल होती है) और सर्वोत्तम उप-संरचना (सर्वोत्तम समाधान उप-समस्याओं के सर्वोत्तम समाधानों से बनाया जा सकता है)। इन दोनों तत्वों के बिना DP उपयोगी नहीं होती।
# Two ingredients of DP:
# 1. Overlapping sub-problems:
# fib(5) -> fib(4) + fib(3)
# fib(4) -> fib(3) + fib(2) <- fib(3) computed twice!
# Without caching: O(2^n) calls for Fibonacci
# 2. Optimal substructure:
# Shortest path from A to C through B:
# shortest(A,C) = shortest(A,B) + shortest(B,C)
# The sub-path A->B must itself be the shortest
# Contrast with greedy: greedy makes one locally optimal
# choice; DP tries all choices and picks the best.
print('DP = overlapping sub-problems + optimal substructure')फिबोनाची: DP की शुरुआत का मानक उदाहरण
फिबोनाची अनुक्रम (fib(n) = fib(n-1) + fib(n-2)) एक-दूसरे से जुड़ी उप-समस्याओं का मानक उदाहरण है। सरल पुनरावृत्ति में घातांकीय समय O(2^n) लगता है, क्योंकि वही मान बार-बार फिर से निकाले जाते हैं। fib(6) का पुनरावृत्ति वृक्ष दिखाता है कि fib(3) की गणना 3 बार, fib(2) की 5 बार और इसी तरह अन्य मानों की गणना होती है। परिणामों को संग्रहीत करके DP इसी घातांकीय विस्तार को समाप्त करती है।
import time
def fib_naive(n):
if n <= 1:
return n
return fib_naive(n-1) + fib_naive(n-2)
# Count the calls:
call_count = [0]
def fib_count(n):
call_count[0] += 1
if n <= 1: return n
return fib_count(n-1) + fib_count(n-2)
fib_count(10)
print(f'Calls for fib(10): {call_count[0]}') # 177 calls for n=10!
call_count[0] = 0
fib_count(20)
print(f'Calls for fib(20): {call_count[0]}') # 21891 calls
# n=30 -> ~2.7 million calls: exponential growthपुनरावृत्ति वृक्ष को देखना
fib(5) का पुनरावृत्ति वृक्ष बनाने पर होने वाली अनावश्यक गणना स्पष्ट हो जाती है: प्रत्येक नोड दो बाल नोड बनाता है और समान उप-वृक्ष बार-बार दिखाई देते हैं। वृक्ष में नोड्स की कुल संख्या O(2^n) होती है। जब आपको यह प्रतिरूप दिखाई दे — समान तर्कों के साथ समान फ़ंक्शन कॉल वृक्ष में बार-बार दिखाई दें — तो यह संकेत है कि परिणामों को कैश करके DP उपयोगी हो सकती है। यह दृश्य-विश्लेषण कौशल बहुत महत्वपूर्ण है: यदि आप दोहराए गए उप-वृक्ष पहचान सकते हैं, तो आप जान जाते हैं कि DP लागू की जा सकती है।
# fib(5) recursion tree (simplified):
# fib(5)
# / \
# fib(4) fib(3)
# / \ / \
# fib(3) fib(2) fib(2) fib(1)
# / \ \
# fib(2) fib(1) fib(1)
# / \
# fib(1) fib(0)
# fib(3) appears TWICE
# fib(2) appears THREE TIMES
# Each redundant call wastes exponential time
# Key insight: fib(n) only has O(n) DISTINCT sub-problems
# (fib(0), fib(1), ..., fib(n))
# DP computes each ONCE -> O(n) total
print('Distinct sub-problems: O(n) but naive calls: O(2^n)')एक-दूसरे से जुड़ी उप-समस्याओं की पहचान
एक-दूसरे से जुड़ी उप-समस्याओं को पहचानने के लिए बलपूर्वक पुनरावृत्ति लिखें, फिर पूछें: 'क्या समान तर्कों के साथ कई पुनरावर्ती कॉल हैं?' यदि हाँ, तो DP मदद कर सकती है। समस्या के विवरण में मिलने वाले सामान्य संकेत हैं: 'X की न्यूनतम/अधिकतम संख्या', 'Y करने के कितने तरीके हैं', 'क्या हम Z प्राप्त कर सकते हैं?' ऐसे वाक्य-विन्यास लगभग हमेशा सर्वोत्तम उप-संरचना वाली समस्या का संकेत देते हैं, जिसमें स्थान i का उत्तर पहले के स्थानों के उत्तरों पर निर्भर करता है।
# DP signal phrases in problem statements:
# 'minimum number of coins to make amount X'
# 'maximum profit from stock trades'
# 'number of ways to climb n stairs'
# 'can you reach the last index?'
# 'longest common subsequence'
# 'edit distance between two strings'
# All have this shape:
# solve(input) = f(solve(smaller_input_1), solve(smaller_input_2), ...)
# And multiple branches end up calling solve with the same argument.
# If the recursion tree has repeated nodes: DP
# If subproblems are all independent: divide-and-conquer (no DP needed)
print('Repeated arguments in recursion tree -> DP')सर्वोत्तम उप-संरचना की व्याख्या
सर्वोत्तम उप-संरचना का अर्थ है कि समस्या का सर्वोत्तम समाधान उसकी उप-समस्याओं के सर्वोत्तम समाधानों से बनाया जा सकता है। उदाहरण के लिए, B से होकर A से C तक का सबसे छोटा पथ तभी सर्वोत्तम होगा जब A→B और B→C दोनों उप-पथ अलग-अलग सर्वोत्तम हों। यदि यह गुण मौजूद हो, तो आप स्थानीय सर्वोत्तम समाधानों से नीचे से ऊपर की ओर समग्र सर्वोत्तम समाधान बना सकते हैं। जिन समस्याओं में सर्वोत्तम उप-संरचना नहीं होती (जैसे चक्रों वाले सामान्य ग्राफ़ में सबसे लंबा पथ), उन्हें DP से हल नहीं किया जा सकता।
# Optimal substructure examples:
# SHORTEST PATH: shortest(A,C) = min over all B: shortest(A,B) + w(B,C)
# -> Sub-paths must be optimal: YES, has optimal substructure
# LONGEST PATH (no cycles, DAG): can also use DP
# -> Longer path through node B means sub-path A->B must be longest
# LONGEST PATH (with cycles): NO optimal substructure
# -> Best path from A to C might reuse nodes: sub-problems not independent
# COIN CHANGE: min coins for amount n = 1 + min(min coins for n-coin_i)
# -> YES: optimal for n-coin_i is needed for optimal n
print('Optimal substructure: build global optimum from local optima')सीढ़ियाँ चढ़ना: आपकी पहली DP
सीढ़ियाँ चढ़ना (LeetCode #70): एक बार में 1 या 2 सीढ़ियाँ चढ़कर n सीढ़ियों तक पहुँचने के कितने अलग-अलग तरीके हैं? dp[i] = सीढ़ी i तक पहुँचने के तरीकों की संख्या मानें। आप सीढ़ी i तक सीढ़ी i-1 से (एक कदम) या सीढ़ी i-2 से (दो कदम) पहुँच सकते हैं, इसलिए dp[i] = dp[i-1] + dp[i-2]। यह फिबोनाची है! आधार स्थितियाँ: dp[1] = 1, dp[2] = 2। यह पहचानना कि 'सीढ़ियाँ चढ़ना' फिबोनाची में बदल जाता है, साक्षात्कार की एक महत्वपूर्ण अंतर्दृष्टि है।
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1 # 1 way to reach step 1
dp[2] = 2 # 2 ways to reach step 2: (1+1) or (2)
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2] # come from i-1 or i-2
return dp[n]
for n in range(1, 8):
print(f'climb_stairs({n}) = {climb_stairs(n)}')
# 1, 2, 3, 5, 8, 13, 21 -- Fibonacci sequence!DP ढाँचा: परिभाषित करें, पुनरावृत्ति लिखें, क्रम तय करें
एक विश्वसनीय 3-चरणीय DP ढाँचा: 1. अवस्था परिभाषित करें — dp[i] (या dp[i][j]) किसका प्रतिनिधित्व करता है? इसे अंग्रेज़ी में लिखें। 2. पुनरावृत्ति सूत्र लिखें — dp[i] को छोटी उप-समस्याओं के संदर्भ में व्यक्त करें। सभी मामलों को शामिल करें। 3. भरने का क्रम निर्धारित करें — सुनिश्चित करें कि dp[i-1] और अन्य निर्भरताओं की गणना dp[i] से पहले हो जाए। आधार मामले सीमा को प्रारंभ करते हैं। यह ढाँचा अस्पष्ट DP समझ को एक ठोस कार्यान्वयन योजना में बदल देता है।
# Framework applied to climbing stairs:
# Step 1 - Define state:
# dp[i] = number of distinct ways to reach step i
# Step 2 - Recurrence:
# dp[i] = dp[i-1] + dp[i-2] (come from step i-1 or i-2)
# Step 3 - Fill order:
# Compute dp[1], dp[2], dp[3], ..., dp[n] in order
# Because dp[i] depends on dp[i-1] and dp[i-2] (smaller)
# Base cases: dp[1]=1, dp[2]=2
# Framework applied to coin change:
# Step 1: dp[amount] = minimum coins to make that amount
# Step 2: dp[i] = 1 + min(dp[i-coin] for coin in coins if i >= coin)
# Step 3: Fill i from 1 to amount
# Base: dp[0] = 0 (zero coins for zero amount)
print('DP framework: define state -> recurrence -> fill order')DP का उपयोग कब NOT करें
DP हमेशा सही उत्तर नहीं होता। लालची विधि का उपयोग तब करें, जब एक स्थानीय रूप से सर्वोत्तम चुनाव हमेशा वैश्विक रूप से सर्वोत्तम समाधान तक पहुँचाता हो (गतिविधि चयन, कूद खेल I)। विभाजित करें और विजय पाएँ विधि का उपयोग तब करें, जब उप-समस्याएँ एक-दूसरे पर निर्भर न हों (मर्ज सॉर्ट, द्विआधारी खोज)। जब समस्या भाररहित ग्राफ में सबसे छोटे पथ की हो, तब BFS का उपयोग करें। जब लालची या कोई सरल तरीका उपलब्ध हो, तब DP सही होते हुए भी अक्सर अनावश्यक रूप से जटिल होता है। साक्षात्कारों में चर्चा करें कि आपने अन्य विकल्पों के बजाय DP क्यों चुना।
# DP vs alternatives:
# Problem: can you jump to the end of the array?
# Greedy: track max reachable index -> O(n) O(1) BETTER than DP
# Problem: shortest path unweighted graph?
# BFS: O(V+E) BETTER than DP on general graph
# Problem: sort an array?
# Comparison sort: O(n log n), no DP needed
# DP IS the right choice when:
# - Greedy fails (choices interact)
# - Need to count/enumerate all possibilities
# - Problem has 'how many ways' or 'minimum/maximum' flavor
# - Recursion tree clearly shows overlapping sub-problems
print('Ask: does greedy fail? If yes, consider DP.')अलग-अलग उप-समस्याओं की गिनती
अलग-अलग उप-समस्याओं की संख्या DP की समय और स्थान जटिलता निर्धारित करती है। n आकार के आगत पर 1D DP में O(n) उप-समस्याएँ होती हैं। m और n आकार के दो आगतों पर 2D DP में O(mn) उप-समस्याएँ होती हैं। प्रत्येक उप-समस्या को O(k) समय में हल किया जाता है (हर चरण पर k विकल्पों के लिए), इसलिए कुल समय O(n*k) या O(mn*k) होता है। हमेशा पहले अलग-अलग उप-समस्याओं की गिनती करें — इससे कोड लिखने से पहले ही DP की समय जटिलता पता चल जाती है।
# Sub-problem count examples:
# Problem | Sub-problems | Each costs | Total
# Fibonacci | O(n) | O(1) | O(n)
# Coin change | O(amount) | O(coins) | O(amount * coins)
# LCS (m,n chars) | O(m*n) | O(1) | O(m*n)
# Edit distance | O(m*n) | O(1) | O(m*n)
# 0/1 Knapsack | O(n*W) | O(1) | O(n*W)
# Matrix chain | O(n^2) | O(n) | O(n^3)
# Rule: DP time = (# distinct sub-problems) * (time per sub-problem)
print('Time = subproblems * work-per-subproblem')घर लूटने की समस्या: ओवरलैप होती पसंदें
घर लूटने की समस्या (LeetCode #198) में एक पंक्ति में स्थित घरों से, आस-पास के घरों में चोरी किए बिना, अधिकतम राशि चुरानी होती है। प्रत्येक घर पर आपके पास दो विकल्प होते हैं: उस घर में चोरी करें (उसका मान जोड़ें और पिछले घर को छोड़ दें) या उसे छोड़ दें (पिछले घर तक का सर्वोत्तम परिणाम लें)। dp[i] = max(dp[i-1], dp[i-2] + nums[i])। प्रत्येक चरण पर विकल्प चुनने का यह पैटर्न सबसे सरल 1D DP पुनरावृत्ति सूत्र है और दर्जनों साक्षात्कार-समस्याओं में दिखाई देता है।
def rob(nums):
if not nums: return 0
if len(nums) == 1: return nums[0]
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], # skip house i
dp[i-2] + nums[i]) # rob house i
return dp[-1]
print(rob([1, 2, 3, 1])) # 4: rob house 0 and 2 (1+3)
print(rob([2, 7, 9, 3, 1]))# 12: rob house 0, 2, 4 (2+9+1)
print(rob([2, 1, 1, 2])) # 4: rob house 0 and 3तर्कसंगति जाँच: बलपूर्वक विधि बनाम DP
छोटे आगतों पर अपनी DP की जाँच हमेशा बलपूर्वक समाधान से करें। बलपूर्वक समाधान आपका सही आधार होता है। जब DP सभी परीक्षण मामलों पर बलपूर्वक समाधान से मेल खाए, तब आप जानते हैं कि पुनरावृत्ति सूत्र सही है। स्थान को अनुकूलित करने का काम केवल उसके बाद करें। परीक्षण-आधारित यह तरीका — बलपूर्वक विधि → ऊपर-से-नीचे DP → नीचे-से-ऊपर DP → स्थान-अनुकूलित DP — साक्षात्कार के दौरान DP समाधानों को विकसित और सत्यापित करने का पेशेवर तरीका है।
# Brute-force for house robber (exponential)
def rob_brute(nums, i=0):
if i >= len(nums):
return 0
# Option 1: rob house i
rob_it = nums[i] + rob_brute(nums, i + 2)
# Option 2: skip house i
skip_it = rob_brute(nums, i + 1)
return max(rob_it, skip_it)
# Verify on small inputs:
test_cases = [[1,2,3,1], [2,7,9,3,1], [2,1,1,2]]
for tc in test_cases:
bf = rob_brute(tc)
dp = rob(tc)
print(f'{tc}: brute={bf}, dp={dp}, match={bf==dp}')त्वरित जाँच
इस पाठ में दिए गए डेटा संरचनाओं & एल्गोरिदम — कोडिंग साक्षात्कार की तैयारी से जुड़े सिद्धांतों की अपनी समझ की जाँच करें।
पाठ का पुनरावलोकन
इस पाठ में आपने सीखा: DP के दो अवयव (एक-दूसरे पर निर्भर उप-समस्याएँ और सर्वोत्तम उप-संरचना), दोहराए जाने वाले आह्वानों की पहचान करने के लिए पुनरावर्तन वृक्ष का दृश्य बनाना, तीन-चरणीय DP ढाँचा (अवस्था, पुनरावृत्ति सूत्र और भरने का क्रम परिभाषित करना), तथा फिबोनाची, सीढ़ियाँ चढ़ने और घर लूटने की समस्या के शुरुआती उदाहरण। अगले भाग में हम मेमोइज़ेशन के साथ ऊपर-से-नीचे DP लागू करेंगे।
एआई शिक्षक के साथ कोडिंग साक्षात्कार की तैयारी सीखें — निःशुल्क
अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।
- पाठ्यक्रम
- 90
- पाठ
- 360
अक्सर पूछे जाने वाले प्रश्न
क्या “DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ” पाठ निःशुल्क है?
हाँ—“DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ” का पूरा पाठ यहाँ वेब पर निःशुल्क पढ़ा जा सकता है। इंटरैक्टिव अभ्यास (अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर) करने और कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम का बाकी हिस्सा अनलॉक करने के लिए CoddyKit PRO लें। कोडिंग साक्षात्कार की तैयारी पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
“DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ” में मैं क्या सीखूँगा?
पहचानिए कि brute-force पुनरावृत्ति कब एक ही उपसमस्या फिर हल करती है, Fibonacci का पुनरावृत्ति-वृक्ष बनाइए और घातीय विस्तार देखिए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ कोडिंग साक्षात्कार की तैयारी का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।
क्या कोडिंग साक्षात्कार की तैयारी शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?
पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर कोडिंग साक्षात्कार की तैयारी शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 1वाँ पाठ है।
“DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ” पाठ पूरा करने में कितना समय लगता है?
CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।
क्या मैं इस कोडिंग साक्षात्कार की तैयारी पाठ में कोड लिख और चला सकता हूँ?
हाँ। हर कोडिंग साक्षात्कार की तैयारी पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।
इस पाठ्यक्रम के सभी पाठ
- DP पहचानना: परस्पर दोहराई जाने वाली उपसमस्याएँ
- Memoisation के साथ Top-Down DP
- Tabulation के साथ Bottom-Up DP
- Coin Change और न्यूनतम-लागत सीढ़ी