0Pricing
Coding Interview Prep · درس

التباديل الخرسانية وخرائط تكرار المحارف

حل مسائل group-anagrams وvalid-anagram وpermutation-in-string باستخدام مصفوفات التكرار وخرائط التجزئة لتحقيق حلول O(n)

التباديل الخرسانية وخرائط تكرار المحارف درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ما هو الأناجرام؟

تكون سلسلتان نصيتان أناجرامًا إذا احتوتا على المحارف نفسها بالتكرارات نفسها، ولكن بترتيب مختلف. السلسلتان 'listen' و'silent' أناجرامان. وأبسط اختبار للصحة هو فرز السلسلتين ثم مقارنتهما، بتكلفة O(n log n). وللحصول على حلول بتكلفة O(n)، قارن خرائط تكرارات المحارف. وتُعد مسائل الأناجرام من أساسيات مقابلات السلاسل النصية لأنها تختبر عدة تقنيات: التجزئة، والفرز، ومصفوفات التكرارات.

def is_anagram_sort(s, t):
    return sorted(s) == sorted(t)  # O(n log n)

def is_anagram_counter(s, t):
    from collections import Counter
    return Counter(s) == Counter(t)  # O(n)

def is_anagram_array(s, t):
    if len(s) != len(t): return False
    freq = [0] * 26
    for a, b in zip(s, t):
        freq[ord(a) - ord('a')] += 1
        freq[ord(b) - ord('a')] -= 1
    return all(f == 0 for f in freq)  # O(n)

print(is_anagram_array('anagram', 'nagaram'))  # True
print(is_anagram_array('rat', 'car'))           # False

مصفوفة تكرارات للأحرف الصغيرة

عندما تكون مجموعة المحارف محدودة (مثلًا، الأحرف الصغيرة a-z فقط)، استبدل خريطة التجزئة بـمصفوفة تكرارات حجمها 26. تؤدي الفهرسة باستخدام ord(c) - ord('a') إلى ربط 'a'→0 و'b'→1 و... و'z'→25. تكون المصفوفات أسرع من dicts عمليًا بفضل الاستفادة من ذاكرة التخزين المؤقت وعدم وجود تكلفة إضافية للتجزئة. تظهر هذه الحيلة في valid-anagram وanagram-permutation-in-string وpalindrome-permutation.

def build_freq(s):
    freq = [0] * 26
    for c in s:
        freq[ord(c) - ord('a')] += 1
    return freq

def is_anagram_fast(s, t):
    return len(s) == len(t) and build_freq(s) == build_freq(t)

# Palindrome permutation: at most one odd-count character
def can_form_palindrome(s):
    freq = build_freq(s)
    odd_count = sum(1 for f in freq if f % 2 == 1)
    return odd_count <= 1

print(can_form_palindrome('carerace'))  # True ('racecar')
print(can_form_palindrome('hello'))     # False

تجميع الأناجرامات

جمّع قائمة من السلاسل النصية بحيث تظهر جميع الأناجرامات معًا. يستخدم الحل القياسي بتكلفة O(n×m log m) السلسلة المرتبة كمفتاح لخريطة تجزئة. تنتج جميع الأناجرامات المفتاح المرتب نفسه، ولذلك تقع في الحاوية نفسها. ويستخدم البديل بتكلفة O(n×m) صفيفًا من أعداد المحارف كمفتاح — وهو أبطأ في الحساب، لكنه يتجنب الفرز تمامًا. ويُفضّل نهج المفتاح المرتب دائمًا تقريبًا لوضوحه.

from collections import defaultdict

def group_anagrams(strs):
    groups = defaultdict(list)
    for s in strs:
        key = tuple(sorted(s))  # or ''.join(sorted(s))
        groups[key].append(s)
    return list(groups.values())

words = ['eat','tea','tan','ate','nat','bat']
result = group_anagrams(words)
for g in sorted(result, key=len, reverse=True):
    print(sorted(g))
# ['ate', 'eat', 'tea']
# ['nat', 'tan']
# ['bat']

مفتاح الأناجرام باستخدام صفيف الأعداد

في البديل الخاص بتجميع الأناجرامات بتكلفة O(n×m)، مثّل تكرارات كل سلسلة نصية على هيئة صفيف يضم 26 عددًا: tuple(freq_array). يتجنب هذا الفرز، لكنه يتطلب O(26×n×m) من العمل لبناء جميع المفاتيح. وتدعم Python استخدام الصفائف كمفاتيح قابلة للتجزئة، مما يجعلها مفاتيح صالحة لـ dict. يجدر ذكر هذا البديل عندما يسأل المحاور عن «أي حل بتكلفة O(n×m)» — فهو يوضح فهمك للمفاضلات المختلفة.

from collections import defaultdict

def group_anagrams_count(strs):
    groups = defaultdict(list)
    for s in strs:
        freq = [0] * 26
        for c in s:
            freq[ord(c) - ord('a')] += 1
        key = tuple(freq)  # tuple is hashable
        groups[key].append(s)
    return list(groups.values())

print(group_anagrams_count(['eat','tea','tan','ate','nat','bat']))

العناصر K الأعلى تكرارًا

اعثر على k من العناصر الأكثر تكرارًا في مصفوفة. باستخدام Counter + heap: ابنِ خريطة تكرارات بتكلفة O(n)، ثم استخرج أكبر k من التكرارات باستخدام كومة دنيا حجمها k أو Counter.most_common(k). أما نهج فرز الحاويات بتكلفة O(n)، فينشئ حاويات مفهرسة حسب التكرار (من 0 إلى n) ويجمع العناصر بترتيب التكرار العكسي — وهو نهج أنيق عندما تكون قيمة k كبيرة.

from collections import Counter
import heapq

def top_k_frequent_heap(nums, k):
    freq = Counter(nums)
    return heapq.nlargest(k, freq, key=freq.get)

def top_k_frequent_bucket(nums, k):
    freq = Counter(nums)
    buckets = [[] for _ in range(len(nums) + 1)]
    for num, cnt in freq.items():
        buckets[cnt].append(num)
    result = []
    for i in range(len(buckets)-1, -1, -1):
        result.extend(buckets[i])
        if len(result) >= k: break
    return result[:k]

print(top_k_frequent_heap([1,1,1,2,2,3], 2))   # [1, 2]
print(top_k_frequent_bucket([1,1,1,2,2,3], 2)) # [1, 2]

خريطة التكرارات للتبديل داخل سلسلة

حدّد ما إذا كان أي تبديل لمحارف السلسلة p موجودًا كسلسلة فرعية في s. يجب أن تساوي خريطة تكرارات نافذة طولها |p| خريطة تكرارات p. ومع انزلاق النافذة، زِد عدد المحرف الداخل وأنقص عدد المحرف الخارج. تكلف مقارنة كائني Counter مقدار O(26) في كل مرة، مما يعطي O(n×26) = O(n) إجمالًا. تتبع عداد 'formed' للتحقق من التساوي بتكلفة O(1).

def check_inclusion_fast(p, s):
    if len(p) > len(s): return False
    need = [0] * 26
    have = [0] * 26
    for c in p:
        need[ord(c)-ord('a')] += 1
    for i in range(len(p)):
        have[ord(s[i])-ord('a')] += 1
    if need == have: return True
    for i in range(len(p), len(s)):
        have[ord(s[i])-ord('a')]         += 1
        have[ord(s[i-len(p)])-ord('a')] -= 1
        if need == have: return True
    return False

print(check_inclusion_fast('ab', 'eidbaooo'))  # True
print(check_inclusion_fast('ab', 'eidboaoo'))  # False

الحد الأدنى من المحارف لجعل سلسلتين أناجرامًا

بالنظر إلى سلسلتين نصيتين، اعثر على الحد الأدنى لعدد عمليات حذف المحارف اللازمة لجعل إحداهما أناجرامًا للأخرى. احسب خريطة تكرارات لكلتا السلسلتين؛ فالجواب هو مجموع الفروق المطلقة بين التكرارات. يجب حذف جميع المحارف الموجودة في إحداهما وغير الموجودة في الأخرى. يستخدم هذا الحل بتكلفة O(n) نمط الدمج وحساب الفروق على خرائط التكرارات.

from collections import Counter

def min_steps_to_anagram(s, t):
    freq_s = Counter(s)
    freq_t = Counter(t)
    steps = 0
    # For each unique char across both strings:
    all_chars = set(freq_s) | set(freq_t)
    for c in all_chars:
        steps += abs(freq_s.get(c, 0) - freq_t.get(c, 0))
    return steps

# Or more concisely:
def min_steps_counter(s, t):
    diff = Counter(s) - Counter(t)
    return sum(diff.values())

print(min_steps_to_anagram('leetcode', 'practice'))  # 5
print(min_steps_counter('leetcode', 'practice'))      # 5

خريطة التكرارات لمذكرة الفدية

تحقق مما إذا كان يمكن توفير جميع المحارف الموجودة في note باستخدام المحارف الموجودة في magazine (إذ لا يمكن استخدام محرف من المجلة أكثر من مرة). ابنِ خريطة تكرارات لمحارف المجلة، ثم أنقص العدد لكل محرف في المذكرة. إذا أصبح أي عدد سالبًا، فأعد False. يستغرق ذلك O(n + m) من الزمن وO(1) من المساحة للمدخلات المقيدة بالأحرف الصغيرة، وذلك باستخدام مصفوفة من 26 عنصرًا بدلًا من dict.

def can_construct(note, magazine):
    freq = [0] * 26
    for c in magazine:
        freq[ord(c) - ord('a')] += 1
    for c in note:
        freq[ord(c) - ord('a')] -= 1
        if freq[ord(c) - ord('a')] < 0:
            return False  # insufficient supply
    return True

print(can_construct('aa', 'aab'))    # True
print(can_construct('aa', 'ab'))     # False
print(can_construct('bg', 'efjbdfbdgbjjbghiklgdch'))  # True

تجزئة أطول سلسلة فرعية أناجرامية

للتحقق مما إذا كانت سلسلتان فرعيتان من السلسلة نفسها أناجرامًا، استخدم تجزئة متعددة الحدود لتكرارات المحارف، على أن تكون تبديلية (غير معتمدة على الترتيب). إن XOR تبديلية وتُحدّث بتكلفة O(1)، لكنها ذات احتمال مرتفع للتصادم. أما النهج الأفضل فيستخدم تجزئة حاصل ضرب الأعداد الأولية (يُربط كل محرف بعدد أولي مميز، ويكون حاصل الضرب غير معتمد على الترتيب). هذه تقنية متخصصة للمقابلات المتقدمة.

# Prime product hash: each char maps to a prime
PRIMES = [2,3,5,7,11,13,17,19,23,29,31,37,41,
          43,47,53,59,61,67,71,73,79,83,89,97,101]

def char_hash(s):
    h = 1
    for c in s:
        h *= PRIMES[ord(c) - ord('a')]
    return h

# Two windows with equal hash are likely anagrams
print(char_hash('listen'))  # same as:
print(char_hash('silent'))  # should match

قائمة التحقق من أنماط خرائط التكرارات

تعرّف على أنماط المقابلات التالية المتعلقة بخرائط التكرارات:

  • الأناجرام الصحيح: الطول نفسه + التكرارات نفسها → تساوي Counter أو مقارنة المصفوفات
  • تجميع الأناجرامات: السلسلة المرتبة أو صفيف التكرارات كمفتاح لـ dict
  • العناصر الأعلى تكرارًا: Counter + heap أو فرز الحاويات
  • التبديل داخل سلسلة: نافذة منزلقة + مقارنة التكرارات
  • مذكرة الفدية: خريطة تكرارات للمصدر، مع إنقاصها لتلبية الطلب
  • تبديل محارف متناظر: محرف واحد بتكرار فردي على الأكثر
وتُختزل جميعها إلى الفكرة الجوهرية نفسها: التكرار بمثابة بصمة.

from collections import Counter

# Palindrome permutation
def palindrome_permutation(s):
    return sum(v % 2 for v in Counter(s).values()) <= 1

# First unique character
def first_unique(s):
    freq = Counter(s)
    for i, c in enumerate(s):
        if freq[c] == 1:
            return i
    return -1

# Character replacement for longest repeat
def char_replacement(s, k):
    freq = Counter()
    left = best = max_freq = 0
    for right, c in enumerate(s):
        freq[c] += 1
        max_freq = max(max_freq, freq[c])
        if (right - left + 1) - max_freq > k:
            freq[s[left]] -= 1
            left += 1
        best = max(best, right - left + 1)
    return best

print(palindrome_permutation('carerace'))  # True
print(first_unique('leetcode'))             # 0
print(char_replacement('AABABBA', 1))      # 4

العنصر المختلف: XOR للتكرارات

يُعد XOR أداة قوية لمسائل التكرارات عندما يظهر عنصر واحد بالعدد الفردي. يؤدي XOR لعدد مع نفسه إلى إلغاء النتيجة لتصبح 0: a XOR a = 0. وعند تطبيق XOR على جميع العناصر، حيث تظهر كل قيمة بعدد زوجي من المرات باستثناء قيمة واحدة، لا يبقى سوى العنصر ذي التكرار الفردي. يحقق ذلك زمنًا قدره O(n) ومساحة قدرها O(1)، من دون الحاجة إلى خريطة تجزئة. ويمكن تعميمه للعثور على عددين يظهران بعدد فردي، باستخدام خصائص XOR.

def single_number(nums):
    result = 0
    for n in nums:
        result ^= n  # XOR cancels pairs
    return result

print(single_number([4,1,2,1,2]))   # 4
print(single_number([2,2,1]))       # 1

# Find the unique character in an anagram check:
def find_difference(s, t):
    result = 0
    for c in s + t:
        result ^= ord(c)
    return chr(result)

print(find_difference('abcd', 'abcde'))  # 'e'

اختبار سريع

اختبر مدى استيعابك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.

مراجعة الدرس

تعلمت في هذا الدرس ما يلي: خرائط تكرارات المحارف هي الأداة الأساسية لاكتشاف الأناجرامات — إذ يمكن استخدام مصفوفة من 26 عنصرًا للأبجديات المحدودة أو Counter للمحارف العشوائية، وتجمع مفاتيح dict المعتمدة على السلسلة المرتبة أو صفيف التكرارات جميع الأناجرامات معًا بتكلفة O(n × m log m) أو O(n × m) على التوالي، ويلغي XOR الأزواج بوضوح في مسائل العنصر الوحيد ذي التكرار الفردي، موفرًا زمنًا قدره O(n) ومساحة قدرها O(1) عندما لا تكون هناك حاجة إلى dict. سنتناول بعد ذلك ترميز السلاسل النصية وعكسها وتقنيات السلاسل المتناظرة.

الأسئلة الشائعة

هل درس «التباديل الخرسانية وخرائط تكرار المحارف» مجاني؟

نعم — نص درس «التباديل الخرسانية وخرائط تكرار المحارف» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «التباديل الخرسانية وخرائط تكرار المحارف»؟

حل مسائل group-anagrams وvalid-anagram وpermutation-in-string باستخدام مصفوفات التكرار وخرائط التجزئة لتحقيق حلول O(n) تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «التباديل الخرسانية وخرائط تكرار المحارف»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. واجهة Python البرمجية للسلاسل النصية في المقابلات
  2. النافذة المنزلقة للسلاسل الجزئية
  3. التباديل الخرسانية وخرائط تكرار المحارف
  4. ترميز السلاسل النصية وعكسها ومتلازمات التناظر
← العودة إلى Coding Interview Prep