العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة
استفد من الاجتياز الوسطي المرتب للعثور على العنصر الأصغر رقم k في O(k)، وجمع القيم ضمن نطاق في O(log n + k)
العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
العنصر رقم k الأصغر في BST
تُعد مسألة Kth Smallest Element in a BST (LeetCode #230) من المسائل الكلاسيكية التي تستفيد مباشرة من الاجتياز الوسطي المرتب. بما أن الاجتياز الوسطي يزور العقد بترتيب تصاعدي، فما علينا سوى عدّ العقد أثناء الاجتياز وإعادة قيمة العقدة عند وصول العدّ إلى k. الزمن هو O(h + k)، حيث h هو الارتفاع (للوصول إلى العقدة الأيسر) وk هو عدد الخطوات في مسار الاجتياز الوسطي.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def kth_smallest(root, k):
count = [0]
result = [None]
def inorder(node):
if not node or result[0] is not None:
return
inorder(node.left)
count[0] += 1
if count[0] == k:
result[0] = node.val
return
inorder(node.right)
inorder(root)
return result[0]
root = TreeNode(3)
root.left = TreeNode(1)
root.right = TreeNode(4)
root.left.right = TreeNode(2)
print(kth_smallest(root, 1)) # 1
print(kth_smallest(root, 2)) # 2العنصر رقم k الأصغر: باستخدام مكدس تكراري
يستخدم الإصدار التكراري نمط الاجتياز الوسطي بالمكدس الصريح. ادفع العقد اليسرى حتى الوصول إلى null، ثم أخرج العقدة وعدّها. عندما يصل العدّ إلى k، أعد قيمة العقدة الحالية. يتجنب هذا الإصدار حدّ الاستدعاء التكراري في Python عند التعامل مع الأشجار العميقة جدًا، ويعمل كذلك في زمن O(h + k) ومساحة O(h). غالبًا ما يطلب القائمون على المقابلات الإصدار التكراري بعد الإصدار التكراري.
def kth_smallest_iterative(root, k):
stack = []
curr = root
count = 0
while curr or stack:
while curr: # go as far left as possible
stack.append(curr)
curr = curr.left
curr = stack.pop() # process node
count += 1
if count == k:
return curr.val
curr = curr.right # move to right subtree
return -1 # k out of range
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(6)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.left.left.left = TreeNode(1)
print(kth_smallest_iterative(root, 3)) # 3العنصر رقم k الأكبر في BST
تستخدم مسألة Kth Largest الاجتياز الوسطي العكسي (right → root → left)، الذي يزور العقد بترتيب تنازلي. احسب k خطوات وأعد قيمة العقدة الحالية. هذا متماثل مع مسألة العنصر رقم k الأصغر، ويعمل في زمن O(h + k). وبدلًا من ذلك، احسب kth_smallest(root, total_count - k + 1) إذا كنت تعرف حجم الشجرة، لكن نهج الاجتياز الوسطي العكسي أكثر أناقة.
def kth_largest(root, k):
count = [0]
result = [None]
def reverse_inorder(node):
if not node or result[0] is not None:
return
reverse_inorder(node.right) # visit LARGER values first
count[0] += 1
if count[0] == k:
result[0] = node.val
return
reverse_inorder(node.left)
reverse_inorder(root)
return result[0]
root = TreeNode(3)
root.left = TreeNode(1)
root.right = TreeNode(4)
root.left.right = TreeNode(2)
print(kth_largest(root, 1)) # 4 (largest)
print(kth_largest(root, 2)) # 3 (2nd largest)مجموع نطاق في BST
تطلب مسألة Range Sum of BST (LeetCode #938) حساب مجموع جميع القيم في [low, high]. استفد من خاصية BST لاستبعاد الفروع: إذا كانت قيمة العقدة الحالية أصغر من low، فإن الشجرة الفرعية اليسرى بأكملها تقع أيضًا تحت low — فتجاوزها. وإذا كانت القيمة الحالية أكبر من high، فتجاوز الشجرة الفرعية اليمنى. يستبعد هذا عددًا كبيرًا من الفروع، ويكون أكثر كفاءة من الفحص الكامل بالاجتياز الوسطي.
def range_sum_bst(root, low, high):
if not root:
return 0
total = 0
if low <= root.val <= high:
total += root.val
if root.val > low: # left subtree might have values >= low
total += range_sum_bst(root.left, low, high)
if root.val < high: # right subtree might have values <= high
total += range_sum_bst(root.right, low, high)
return total
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
print(range_sum_bst(root, 7, 15)) # 7+10+15 = 32عدّ العقد ضمن نطاق
يتبع عدّ العقد ضمن النطاق [low, high] منطق الاستبعاد نفسه. ويستخدم بديلًا عن ذلك bisect_left/bisect_right على مصفوفة الاجتياز الوسطي — لكن الاجتياز المباشر لـ BST يعمل في O(log n + k)، بينما يستغرق التحويل إلى مصفوفة أولًا O(n) دائمًا. اختر الاجتياز المباشر، إلا إذا كنت تحتاج إلى الإجابة عن عدد كبير من استعلامات النطاقات؛ ففي هذه الحالة، يتيح إنشاء BST معززة تحتوي على أعداد العقد في الأشجار الفرعية تنفيذ كل استعلام في O(log n).
def count_range(root, low, high):
if not root:
return 0
count = 0
if low <= root.val <= high:
count += 1
if root.val > low:
count += count_range(root.left, low, high)
if root.val < high:
count += count_range(root.right, low, high)
return count
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
print(count_range(root, 6, 15)) # 7, 10, 15 = 3BST إلى مصفوفة مرتبة (الخوارزمية الكاملة)
يستغرق تحويل BST إلى مصفوفة مرتبة زمن O(n) ومساحة O(n). استخدم الاجتياز الوسطي وألحق كل قيمة بالمصفوفة. هذه نقطة البداية للمسائل متعددة الخطوات: 'دمج شجرتي BST'، أو 'العثور على وسيط BST'، أو 'التحقق مما إذا كانت لشجرتي BST تسلسل الاجتياز الوسطي نفسه'. تتيح المصفوفة الناتجة الوصول إلى العناصر حسب الفهرس في O(1)، والبحث الثنائي، وتقنيات المؤشرين التي لا توفرها BST نفسها مباشرة.
def bst_to_sorted(root):
result = []
def inorder(node):
if not node:
return
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return result
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(8)
root.left.left = TreeNode(1)
root.left.right = TreeNode(4)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)
print(bst_to_sorted(root)) # [1, 3, 4, 5, 6, 8, 9]
# Binary search on the resulting sorted array:
import bisect
arr = bst_to_sorted(root)
print(bisect.bisect_left(arr, 6)) # 4 (index of 6)شجرة BST معززة: أحجام الأشجار الفرعية
تخزّن شجرة BST معززة معلومات إضافية في كل عقدة، مثل حجم شجرتها الفرعية. وباستخدام أحجام الأشجار الفرعية، يصبح العثور على العنصر الأصغر رقم k بتعقيد O(log n): في كل عقدة، إذا كان حجم الشجرة الفرعية اليسرى يساوي k-1، تكون العقدة الحالية هي الإجابة؛ وإذا كان حجمها أكبر من أو يساوي k، نتابع البحث في اليسار؛ وإلا نطرح الحجم ونتابع البحث في اليمين. هذا هو هيكل البيانات الذي تقوم عليه أشجار إحصاءات الرتبة المستخدمة في البرمجة التنافسية.
class AugNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
self.size = 1 # subtree size
def get_size(node):
return node.size if node else 0
def update_size(node):
if node:
node.size = 1 + get_size(node.left) + get_size(node.right)
def kth_smallest_aug(root, k):
left_size = get_size(root.left)
if k == left_size + 1:
return root.val # current node is kth
elif k <= left_size:
return kth_smallest_aug(root.left, k)
else:
return kth_smallest_aug(root.right, k - left_size - 1)
print('Augmented BST: O(log n) kth smallest with subtree sizes')العثور على جميع القيم في BST بين عقدتين
لإرجاع جميع القيم الواقعة حصراً بين العقدتين p وq (حيث p.val < q.val)، ادمج الاجتياز الوسطي مع تقليم النطاق: ابدأ بجمع القيم بعد تجاوز p.val، وتوقّف بعد q.val. هذا تعميم لمجموع النطاق، ويمنحك التسلسل المرتب الواقع بين قيمتي الاستعلام في زمن O(h + k).
def values_between(root, low, high):
result = []
def inorder(node):
if not node:
return
if node.val > low: # might be values > low on left
inorder(node.left)
if low < node.val < high: # strictly between
result.append(node.val)
if node.val < high: # might be values < high on right
inorder(node.right)
inorder(root)
return result
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.left = TreeNode(12)
root.right.right = TreeNode(18)
print(values_between(root, 6, 15)) # [7, 10, 12]الوسيط في BST
وسيط BST هو القيمة الوسطى في الاجتياز الوسطي. بالنسبة إلى n من العقد، يوجد الوسيط في الفهرس n // 2 (بفهرسة تبدأ من الصفر). يمكنك إما جمع المصفوفة المرتبة كاملة ثم الوصول إلى هذا الفهرس، أو استخدام تمريرين: احسب أولاً عدد العقد n، ثم نفّذ اجتيازاً وسطياً ثانياً وتوقّف عند العقدة رقم n // 2. بدلاً من ذلك، استخدم طريقة العثور على العنصر الأصغر رقم k مع k = n // 2 + 1.
def count_nodes(root):
if not root:
return 0
return 1 + count_nodes(root.left) + count_nodes(root.right)
def median_of_bst(root):
n = count_nodes(root)
if n == 0:
return None
k = n // 2 + 1 # (n+1)/2-th element for odd, n/2+1-th for even
return kth_smallest(root, k)
def kth_smallest(root, k):
count = [0]; result = [None]
def inorder(node):
if not node or result[0] is not None: return
inorder(node.left)
count[0] += 1
if count[0] == k: result[0] = node.val; return
inorder(node.right)
inorder(root); return result[0]
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(8)
root.left.left = TreeNode(1)
root.left.right = TreeNode(4)
print(median_of_bst(root)) # 4 (middle of [1,3,4,5,8])أقرب k من القيم إلى الهدف
اعثر على k من القيم في BST الأقرب إلى هدف معين. تتمثل إحدى طرائق المؤشرين في تحويل الشجرة إلى مصفوفة مرتبة واستخدام نافذة منزلقة بحجم k. وبدلاً من ذلك، استخدم كومة قصوى بحجم k، تدفع إليها المسافات وتزيل منها عنصراً عندما يتجاوز حجمها k. تستغرق طريقة المصفوفة المرتبة O(n) من الزمن وهي بسيطة، أما طريقة الكومة فتستغرق O(n log k)، لكنها تعمل في سياق المعالجة المتدفقة.
import heapq
def closest_k_values(root, target, k):
# Collect sorted values
arr = []
def inorder(node):
if not node: return
inorder(node.left)
arr.append(node.val)
inorder(node.right)
inorder(root)
# Two-pointer sliding window of size k
left, right = 0, k - 1
while right < len(arr) - 1:
if abs(arr[left] - target) <= abs(arr[right + 1] - target):
break # left is closer, don't advance
left += 1
right += 1
return arr[left:right + 1]
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(5)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(closest_k_values(root, 3.7, 2)) # [3, 4]استغلال خاصية ترتيب اللاحق
تختزل كثير من مسائل BST إلى العثور على العنصر التالي أو السابق في الترتيب المرتب، وهي عمليات تستغرق O(log n) باستخدام التنقل في BST. يوفّر المكرّر الذي بنيناه سابقاً عملية next بزمن O(1) في المتوسط التراكمي. وبدمج معرفتك بطريقة العثور على العنصر الأصغر رقم k، ومجموع النطاق، والقيمة الأقرب، يمكنك حل معظم مسائل BST في المقابلات عبر طرح السؤال التالي: «كيف يبسّط الترتيب المرتب للاجتياز الوسطي هذه المسألة؟» هذا النمط العام هو بوصلتك لحل مسائل BST.
# Meta-pattern for BST problems:
# Step 1: What sorted-order property does this exploit?
# Step 2: Is in-order (ascending) or reverse in-order (descending) needed?
# Step 3: Can I prune using BST ordering to avoid O(n) scan?
# Quick reference:
# kth smallest -> in-order, stop at kth node
# kth largest -> reverse in-order, stop at kth node
# range sum -> in-order + BST pruning
# closest value -> walk toward target, track best
# median -> kth with k = n//2+1
# sorted array -> full in-order
# validate -> in-order prev check or min/max bounds
print('Sorted in-order is the universal BST problem tool')تحقق سريع
اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
تعلّمت في هذا الدرس: العثور على العنصر الأصغر والأكبر رقم k باستخدام الاجتياز الوسطي والاجتياز الوسطي العكسي في O(h+k)، وحساب مجموع النطاق مع تقليم BST لإجراء استعلامات نطاق فعّالة، وتحويل BST إلى مصفوفة مرتبة بوصفها أساساً للخوارزميات المعتمدة على المصفوفات. بعد ذلك سنستكشف الأكوام وطوابير الأولوية.
الأسئلة الشائعة
هل درس «العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة» مجاني؟
نعم — نص درس «العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة»؟
استفد من الاجتياز الوسطي المرتب للعثور على العنصر الأصغر رقم k في O(k)، وجمع القيم ضمن نطاق في O(log n + k) تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- إدراج BST والبحث فيه
- حذف BST: ثلاث حالات
- التحقق من BST وخصائص الترتيب الوسطي
- العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة