Swift على LeetCode: من أين تبدأ وكيف لا تستسلم
كل مطور iOS عاجلاً أم آجلاً يصطدم بجدار: تقوم ببناء المشاريع، وترسل التطبيقات إلى AppStore، ولكن في المقابلة يُعطونك مسألة عن شجرة ثنائية وتصاب بالذهول. هل هذا مألوف؟ LeetCode هو ذلك التمرين الذي يحول "أنا فقط أكتب واجهة المستخدم" إلى "أنا أفكر بالخوارزميات". لكن هناك فارق: معظم الحلول على الإنترنت مكتوبة بلغة Python أو Java، بينما Swift هي لغة ذات فلسفتها الخاصة. في هذه المقالة، سنحلل كيفية حل المسائل على LeetCode بكفاءة باستخدام Swift، وما هياكل البيانات التي يجب استخدامها، وما الذي يجب الانتباه إليه لتجنب إضاعة الوقت.
هذه المقالة مخصصة لمن يعرفون أساسيات Swift بالفعل، لكنهم يريدون تطوير التفكير الخوارزمي. لن نعلم القواعد النحوية - بل سنتعلم كيف نفكر.
لماذا Swift هو اختيار ممتاز لـ LeetCode
يعتقد الكثيرون أن Swift مخصص فقط لـ UIKit و SwiftUI، لكن في الواقع تمتلك اللغة إمكانيات قوية للمسائل الخوارزمية:
- الأنواع الاختيارية — التعامل الآمن مع nil دون أعطال غير متوقعة.
- أنواع القيمة (الهياكل) — نسخ متوقع، لا مفاجآت مع المراجع.
- الحسابات الكسولة والأساليب الوظيفية — map, filter, reduce تبسط الكود بشكل كبير.
- السرعة — يتم ترجمة Swift إلى كود أصلي، وغالباً ما يتفوق على Python بمقدار 2-3 مرات.
ومع ذلك، هناك بعض المزالق: على سبيل المثال، المصفوفات في Swift هي هياكل، ونسخ مصفوفة كبيرة قد يكون مكلفاً. لكننا سنتحدث عن هذا بشكل منفصل.
هياكل البيانات: ما تحتاج معرفته لـ Swift على LeetCode
قبل أن تندفع لحل المسائل، تأكد من أنك تعمل بثقة مع الهياكل الأساسية. إليك قائمة must-know لـ Swift:
المصفوفات والشرائح (Array, ArraySlice)
المصفوفات في Swift ليست مجرد قوائم، بل هياكل قوية مع وصول O(1) حسب الفهرس. لكن كن حذراً: الإدراج/الحذف في البداية هو O(n). استخدم ArraySlice لتجنب نسخ البيانات عند العمل مع المصفوفات الفرعية.
let numbers = [1, 2, 3, 4, 5]let slice = numbers[1..<4] // ArraySlice، بدون نسخprint(slice) // [2, 3, 4]القواميس والمجموعات (Dictionary, Set)
للمسائل المتعلقة بحساب التكرارات، البحث عن التكرارات، أو الخوارزميات ذات المسارين - هذه هي أفضل أصدقائك. تذكر: Dictionary هو جدول تجزئة، O(1) في المتوسط، لكنه قد يتدهور إلى O(n) مع دالة تجزئة سيئة.
func twoSum(_ nums: [Int], _ target: Int) -> [Int] { var dict = [Int: Int]() for (i, num) in nums.enumerated() { if let j = dict[target - num] { return [j, i] } dict[num] = i } return []}المكدس والطابور (Stack, Queue)
لا توجد في Swift هياكل مدمجة للمكدس والطابور، لكن من السهل محاكاتها باستخدام مصفوفة. للمكدس — append و popLast. للطابور استخدم ArraySlice أو قائمة مرتبطة مزدوجة لتجنب O(n) عند dequeue.
struct Stack<T> { private var array = [T]() mutating func push(_ element: T) { array.append(element) } mutating func pop() -> T? { array.popLast() } var isEmpty: Bool { array.isEmpty }}القوائم المرتبطة (Linked List)
يحب LeetCode المسائل المتعلقة بالقوائم أحادية الارتباط. لا توجد في Swift تطبيق مدمج، لكن يمكنك تعريف كلاس:
class ListNode { var val: Int var next: ListNode? init(_ val: Int) { self.val = val; self.next = nil }}هذا كلاسيكي للمسائل المتعلقة بعكس القائمة، اكتشاف الدورة، أو حذف العقدة.
أنماط الخوارزميات لمسائل Swift
لكي لا تعيد اختراع العجلة في كل مرة، احفظ بعض القوالب التي تغطي 80% من مسائل LeetCode.
المؤشران (Two Pointers)
إ