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

पथ योग और न्यूनतम साझा पूर्वज

पुनरावर्ती अवरोह का उपयोग करके सामान्य द्विआधारी वृक्ष में root-to-leaf path sum, all-paths-sum और lowest-common-ancestor हल कीजिए।

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

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

मूल से पत्ती तक पथ का योग

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

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

def has_path_sum(root, target):
    if not root:
        return False
    if not root.left and not root.right:  # leaf
        return root.val == target
    remain = target - root.val
    return (has_path_sum(root.left, remain) or
            has_path_sum(root.right, remain))

root = TreeNode(5)
root.left = TreeNode(4)
root.right = TreeNode(8)
root.left.left = TreeNode(11)
root.left.left.left = TreeNode(7)
root.left.left.right = TreeNode(2)
print(has_path_sum(root, 22))  # True: 5->4->11->2

मूल से पत्ती तक सभी पथ

सभी पथों को सूचीबद्ध करने के लिए अब तक के पथ की सूची बनाए रखिए। प्रत्येक पुनरावर्ती आह्वान पर वर्तमान नोड का मान जोड़ने के लिए append कीजिए, संतानों पर पुनरावृत्ति कीजिए, फिर लौटते समय pop कीजिए (पीछे लौटिए)। पत्ती पर वर्तमान पथ का एक स्नैपशॉट (list(path)) दर्ज कीजिए। यह प्रतिरूप — चुनना, पुनरावृत्ति करना, चयन रद्द करना — वृक्षों पर पीछे लौटकर खोज करने का आधार है।

def all_path_sums(root, target):
    results = []

    def dfs(node, path, remaining):
        if not node:
            return
        path.append(node.val)
        if not node.left and not node.right and remaining == node.val:
            results.append(list(path))  # snapshot
        else:
            dfs(node.left, path, remaining - node.val)
            dfs(node.right, path, remaining - node.val)
        path.pop()  # backtrack

    dfs(root, [], target)
    return results

root = TreeNode(5)
root.left = TreeNode(4)
root.right = TreeNode(8)
root.left.left = TreeNode(11)
root.left.left.right = TreeNode(2)
root.right.right = TreeNode(5)
print(all_path_sums(root, 22))  # [[5,4,11,2]]

पथ योग III: कोई भी पथ, कोई भी नोड

पथ योग III (LeetCode #437) उन पथों की गिनती करता है जिनका योग किसी लक्ष्य के बराबर होता है, जहाँ पथ कहीं से भी शुरू और समाप्त हो सकता है (सिर्फ मूल से पत्ती तक नहीं)। सीधे हर नोड से DFS चलाने वाला तरीका O(n²) का है। सर्वोत्तम O(n) वाला तरीका संचयी योग हैश मानचित्र का उपयोग करता है: संचयी योग पर नज़र रखिए और गिनिए कि current_sum - target पहले कितनी बार आया था। यह उप-सरणी योग वाली विधि के समान है।

def path_sum_iii(root, target):
    prefix_counts = {0: 1}

    def dfs(node, running_sum):
        if not node:
            return 0
        running_sum += node.val
        count = prefix_counts.get(running_sum - target, 0)
        prefix_counts[running_sum] = prefix_counts.get(running_sum, 0) + 1
        count += dfs(node.left, running_sum)
        count += dfs(node.right, running_sum)
        prefix_counts[running_sum] -= 1  # backtrack
        return count

    return dfs(root, 0)

root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(-3)
root.left.left = TreeNode(3)
root.left.right = TreeNode(2)
root.right.right = TreeNode(11)
root.left.left.left = TreeNode(3)
root.left.left.right = TreeNode(-2)
root.left.right.right = TreeNode(1)
print(path_sum_iii(root, 8))  # 3

निकटतम साझा पूर्वज क्या होता है

द्विआधारी वृक्ष में दो नोड p और q का निकटतम साझा पूर्वज (LCA) वह सबसे गहरा नोड होता है जिसके वंशज p और q दोनों होते हैं (कोई नोड स्वयं का भी वंशज हो सकता है)। LCA ‘दो नोडों के बीच दूरी’, ‘दो नोडों के बीच पथ’ और BST परास-प्रश्न जैसी समस्याओं में दिखाई देता है। मध्यवर्ती स्तर की वृक्ष समस्याओं के लिए LCA को समझना आवश्यक है।

#       3
#      / \
#     5   1
#    / \ / \
#   6  2 0  8
#     / \
#    7   4
# LCA(5, 1) = 3  (root)
# LCA(5, 4) = 5  (p itself is ancestor of q)
# LCA(6, 4) = 5
# LCA(7, 4) = 2
# Key insight: the LCA is the node where p and q
# first 'split' into different subtrees.
print('LCA: deepest node that is ancestor of both p and q')

LCA का पुनरावर्ती एल्गोरिदम

सुघड़ पुनरावर्ती LCA समाधान पहला ऐसा नोड लौटाता है जो या तो p या q हो, या जिसके उपवृक्षों में दोनों मौजूद हों। यदि वर्तमान नोड p या q है, तो उसे लौटाइए। अन्यथा बाएँ और दाएँ, दोनों ओर पुनरावृत्ति कीजिए। यदि दोनों ओर से अशून्य परिणाम मिले, तो वर्तमान नोड LCA है। यदि केवल एक ओर से अशून्य परिणाम मिले, तो उस परिणाम को ऊपर भेज दीजिए। इसका समय O(n) और स्थान O(h) होता है।

def lowest_common_ancestor(root, p, q):
    # Base case: empty or found one of the targets
    if not root or root == p or root == q:
        return root
    # Search both subtrees
    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)
    # If both sides found something, this node is the LCA
    if left and right:
        return root
    # Otherwise, return whichever side found something
    return left if left else right

root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left.left = TreeNode(6)
root.left.right = TreeNode(2)
p, q = root.left, root.right  # 5 and 1
lca = lowest_common_ancestor(root, p, q)
print(lca.val)  # 3

जब कोई नोड स्वयं का पूर्वज हो सकता है तब LCA

एक महत्वपूर्ण विशेष स्थिति यह है: यदि p, q का पूर्वज है (या इसके विपरीत), तो LCA स्वयं p होता है। पुनरावर्ती एल्गोरिदम इसे अपने-आप संभाल लेता है — p तक पहुँचते ही वह p लौटा देता है और बिना p के उपवृक्षों में जाए रुक जाता है। अभिभावक देखता है कि एक ओर से p और दूसरी ओर से रिक्त लौटा है, इसलिए वह LCA के रूप में p को ऊपर भेज देता है। LCA का कोड लिखते समय अपने परीक्षण में इस स्थिति की हमेशा जाँच कीजिए।

# Test case: p is ancestor of q
# Tree: 3 -> left=5 -> left=6
# LCA(5, 6) should be 5
root = TreeNode(3)
root.left = TreeNode(5)
root.left.left = TreeNode(6)

p = root.left     # node 5
q = root.left.left  # node 6

lca = lowest_common_ancestor(root, p, q)
print(lca.val)  # 5 (p itself is the LCA)

अभिभावक संकेतकों के साथ LCA

यदि प्रत्येक नोड में अभिभावक संकेतक हो, तो LCA की समस्या ‘दो लिंक्ड सूचियों के प्रतिच्छेदन’ की समस्या में बदल जाती है। p के पूर्वजों को एक समुच्चय में एकत्रित कीजिए, फिर q से ऊपर की ओर चलते हुए उस समुच्चय में मौजूद पहला नोड खोजिए। O(h) समय और O(h) स्थान वाला यह तरीका उन तंत्र-रचना साक्षात्कारों में आम है जहाँ आप नोड संरचना नियंत्रित करते हैं और अभिभावक संदर्भ संग्रहीत कर सकते हैं।

class NodeWithParent:
    def __init__(self, val, parent=None):
        self.val = val
        self.parent = parent
        self.left = None
        self.right = None

def lca_with_parent(p, q):
    ancestors = set()
    # Collect all ancestors of p
    node = p
    while node:
        ancestors.add(node)
        node = node.parent
    # Walk up from q until we hit a known ancestor
    node = q
    while node:
        if node in ancestors:
            return node
        node = node.parent
    return None

print('With parent pointers: O(h) time and space')

द्विआधारी खोज वृक्ष में LCA

BST में LCA सरल होता है, क्योंकि क्रम-व्यवस्था का गुणधर्म बताता है कि प्रत्येक नोड किस उपवृक्ष में है। यदि p और q दोनों वर्तमान नोड से छोटे हैं, तो LCA बाएँ उपवृक्ष में है। यदि दोनों बड़े हैं, तो LCA दाएँ उपवृक्ष में है। अन्यथा वर्तमान नोड उन्हें अलग करता है, इसलिए वही LCA है। संतुलित BST में इससे समस्या O(log n) की रह जाती है।

def lca_bst(root, p, q):
    if not root:
        return None
    if p.val < root.val and q.val < root.val:
        return lca_bst(root.left, p, q)  # both in left
    if p.val > root.val and q.val > root.val:
        return lca_bst(root.right, p, q)  # both in right
    return root  # split point = LCA

# Iterative BST LCA (no recursion overhead):
def lca_bst_iter(root, p, q):
    while root:
        if p.val < root.val and q.val < root.val:
            root = root.left
        elif p.val > root.val and q.val > root.val:
            root = root.right
        else:
            return root
    return None

print('BST LCA: O(log n) for balanced trees')

दो नोडों के बीच दूरी

वृक्ष में दो नोडों के बीच दूरी उन्हें जोड़ने वाले पथ पर मौजूद किनारों की संख्या के बराबर होती है। इसे सीधे LCA से निकाला जा सकता है: distance(p, q) = depth(p) + depth(q) - 2 * depth(LCA(p,q))। पहले LCA खोजिए, फिर प्रत्येक नोड की गहराई गिनिए। उचित सहायक के साथ इसका समय O(n) और स्थान O(h) होता है।

def find_depth(root, target, depth=0):
    if not root:
        return -1
    if root == target:
        return depth
    left = find_depth(root.left, target, depth + 1)
    if left != -1:
        return left
    return find_depth(root.right, target, depth + 1)

def node_distance(root, p, q):
    lca = lowest_common_ancestor(root, p, q)
    # depth from LCA to p and q
    dp = find_depth(lca, p)
    dq = find_depth(lca, q)
    return dp + dq

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(node_distance(root, root.left.left, root.left.right))  # 2

मूल से पत्ती तक अधिकतम योग वाला पथ

मूल से पत्ती तक अधिकतम योग वाला पथ मूल से वर्तमान नोड तक के संचयी योग पर नज़र रखता है। पत्तियों पर इसकी तुलना वैश्विक अधिकतम से कीजिए। यह पूर्व-क्रम DFS है, जिसमें वर्तमान पथ का योग प्राचल के रूप में आगे भेजा जाता है। सामान्य अधिकतम पथ योग के विपरीत, यह रूप केवल मूल से पत्ती तक के पथों तक सीमित है, इसलिए सरल है — मनमाने नोड से नोड तक के पथों पर विचार करने की आवश्यकता नहीं होती।

def max_root_to_leaf_sum(root):
    if not root:
        return float('-inf')
    best = [float('-inf')]

    def dfs(node, running):
        running += node.val
        if not node.left and not node.right:  # leaf
            best[0] = max(best[0], running)
            return
        if node.left:
            dfs(node.left, running)
        if node.right:
            dfs(node.right, running)

    dfs(root, 0)
    return best[0]

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(max_root_to_leaf_sum(root))  # 1+2+5 = 8

मूल से पत्ती तक संख्याओं का योग

मूल से पत्ती तक संख्याओं का योग (LeetCode #129) प्रत्येक मूल से पत्ती तक के पथ को एक दशमलव संख्या मानता है (जैसे, 1→2→3 पथ संख्या 123 को दर्शाता है) और उन सभी का योग पूछता है। पुनरावृत्ति के दौरान current_number * 10 + node.val भेजकर संख्या बनाइए। प्रत्येक पत्ती पर पूरी हुई संख्या को कुल योग में जोड़िए। यह संचित स्थिति को नीचे की ओर भेजने वाले पूर्व-क्रम DFS का स्पष्ट उदाहरण है।

def sum_numbers(root):
    def dfs(node, num):
        if not node:
            return 0
        num = num * 10 + node.val
        if not node.left and not node.right:  # leaf
            return num
        return dfs(node.left, num) + dfs(node.right, num)

    return dfs(root, 0)

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(sum_numbers(root))  # 12 + 13 = 25

root2 = TreeNode(4)
root2.left = TreeNode(9)
root2.right = TreeNode(0)
root2.left.left = TreeNode(5)
root2.left.right = TreeNode(1)
print(sum_numbers(root2))  # 495 + 491 + 40 = 1026

त्वरित जाँच

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

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

इस पाठ में आपने सीखा: पथ योग के विभिन्न रूप (मूल से पत्ती तक, सभी पथ, संचयी योगों के साथ पथ योग III), सुघड़ पुनरावर्ती विभाजन का उपयोग करके निकटतम साझा पूर्वज, और क्रम-व्यवस्था के गुणधर्म का उपयोग करके O(log n) में BST LCA। आगे हम प्रविष्टि और खोज संचालन के साथ द्विआधारी खोज वृक्ष शुरू करेंगे।

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

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

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

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

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

क्या “पथ योग और न्यूनतम साझा पूर्वज” पाठ निःशुल्क है?

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

“पथ योग और न्यूनतम साझा पूर्वज” में मैं क्या सीखूँगा?

पुनरावर्ती अवरोह का उपयोग करके सामान्य द्विआधारी वृक्ष में root-to-leaf path sum, all-paths-sum और lowest-common-ancestor हल कीजिए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ कोडिंग साक्षात्कार की तैयारी का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

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

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

“पथ योग और न्यूनतम साझा पूर्वज” पाठ पूरा करने में कितना समय लगता है?

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

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

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

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

  1. TreeNode क्लास और स्तर-क्रम BFS
  2. In-Order, Pre-Order, Post-Order DFS
  3. व्यास, ऊँचाई और संतुलित वृक्ष
  4. पथ योग और न्यूनतम साझा पूर्वज
← कोडिंग साक्षात्कार की तैयारी पर वापस जाएँ