جاري التحميل...
جاري التحميل...
تقنيات مؤشرات مزدوجة ونافذة منزلقة
Two Pointers و Sliding Window هما تقنيتان ضروريةان لحل مشاكل المصفوفات والنصوص بكفاءة، مما يقلل تعقيد الوقت من O(n²) إلى O(n).
💡 ما سنتعلمه:
Two Pointers على المصفوفات المرتبة، Sliding Window لمشاكل المجموعات الفرعية/النصوص الفرعية، ومتى تطبق كل تقنية.
يستخدم Two Pointers فهرسين يتحركان نحو بعضهما البعض أو في نفس الاتجاه لإيجاد الأزواج أو حل المشكلات في وقت خطي.
💡 الأنماط:
الطرفان المتقابلان: التناظر، مشاكل الحاوية. نفس الاتجاه: إزالة التكرارات، دمج المصفوفات المرتبة.
يحافظ Sliding Window على نافذة من العناصر ويتحرك عبر المصفوفة، يوسّع ويقلّص بناءً على الشروط.
💡 النافذة الثابتة مقابل المتغيرة:
الثابتة: حجم النافذة ثابت (مثل: k). المتغيرة: النافذة تنمو/تقلص بناءً على شرط.
وسّع النافذة عن طريق تحريك المؤشر الأيمن، قلّص من اليسار عند انتهاك الشرط، وتابع النتيجة المثالية.
💡 النمط:
for right in range: add[right]. while condition_violated: remove[left]، left++. update result.
استخدم نافذة بحجم ثابت عن طريق إضافة العنصر الجديد وإزالة العنصر القديم مع تحرك النافذة.
💡 النمط:
for right in range: add[right]. if right >= k: remove[right - k]. update result.
Two Pointers: المصفوفات المرتبة، إيجاد الأزواج، التناظر. Sliding Window: تحسين المجموعات الفرعية/النصوص الفرعية، العناصر المتتالية.
💡 دليل القرار:
تحتاج زوجًا من مصفوفة مرتبة؟ Two Pointers. تحتاج أفضل/أسوأ مجموعة فرعية؟ Sliding Window.
معطى نص s، أعد true إذا كان palindrome (يُقرأ بنفس الطريقة من الأمام والخلف). يُسمح بتجاهل الأحرف غير الأبجدية والأرقام والمسافات.
📋 أمثلة:
s = "A man, a plan, a canal: Panama"
true
s = "race a car"
false
s = " "
true
💡 تلميح:
function isPalindrome(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
while (left < right && !isAlphaNum(s[left])) left++;
while (left < right && !isAlphaNum(s[right])) right--;
if (s[left].toLowerCase() !== s[right].toLowerCase()) {
return false;
}
left++;
right--;
}
return true;
}
function isAlphaNum(c) {
return (
(c >= "a" && c <= "z") ||
(c >= "A" && c <= "Z") ||
(c >= "0" && c <= "9")
);
}أعد دمج مصفوفتين مرتبتين nums1 و nums2 في مصفوفة واحدة مرتبة داخل nums1. مصفوفة nums1 لها حجم m + n حيث m هو عدد العناصر الأصلية و n عدد عناصر nums2.
📋 أمثلة:
nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
[1,2,2,3,5,6]
nums1 = [1], m = 1, nums2 = [], n = 0
[1]
💡 تلميح:
function merge(nums1, m, nums2, n) {
let i = m - 1;
let j = n - 1;
let k = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[k] = nums1[i];
i--;
} else {
nums1[k] = nums2[j];
j--;
}
k--;
}
while (j >= 0) {
nums1[k] = nums2[j];
j--;
k--;
}
}أعد عدد العناصر الفريدة في المصفوفة المرتبة nums. يجب تعديل المصفوفة لتكون العناصر الفريدة في أولية فقط. لا تستخدم مساحة إضافية O(n).
📋 أمثلة:
nums = [1,1,2]
2, nums1 = [1,2,_]
nums = [0,0,1,1,1,2,2,3,3,4]
5, nums1 = [0,1,2,3,4,_]
💡 تلميح:
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let write = 0;
for (let read = 1; read < nums.length; read++) {
if (nums[read] !== nums[write]) {
write++;
nums[write] = nums[read];
}
}
return write + 1;
}أعد فهرسي العنصرين في مصفوفة مرتبة numbers بحيث يكون مجموعهما يساوي target. يجب أن يكون الحل بشكل 1-indexed. لا تستخدم نفس العنصر مرتين.
📋 أمثلة:
numbers = [2,7,11,15], target = 9
[1,2]
numbers = [2,3,4], target = 6
[1,3]
numbers = [-1,0], target = -1
[1,2]
💡 تلميح:
function twoSum(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left < right) {
const sum = numbers[left] + numbers[right];
if (sum === target) {
return [left + 1, right + 1];
} else if (sum < target) {
left++;
} else {
right--;
}
}
return [];
}أعد مجموع ثلاثة عناصر في nums يكون أقرب إلى target. يوجد حل واحد فقط للسؤال.
📋 أمثلة:
nums = [-1,2,1,-4], target = 1
2
nums = [0,1,2], target = 3
3
💡 تلميح:
function threeSumClosest(nums, target) {
nums.sort((a, b) => a - b);
let closest = nums[0] + nums[1] + nums[2];
for (let i = 0; i < nums.length - 2; i++) {
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (Math.abs(sum - target) < Math.abs(closest - target)) {
closest = sum;
}
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
return sum;
}
}
}
return closest;
}أوجد أكبر مساحة يمكن أن يحتويها الماء مع خطوط ارتفاعات height. المساحة محسوبة كـ min(height[left], height[right]) * (right - left).
📋 أمثلة:
height = [1,8,6,2,5,4,8,3,7]
49
height = [1,1]
1
💡 تلميح:
function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxWater = 0;
while (left < right) {
const currentWater =
Math.min(height[left], height[right]) * (right - left);
maxWater = Math.max(maxWater, currentWater);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}أوجد طول أطول نص فرعي (substring) لا يحتوي على أحرف متكررة.
📋 أمثلة:
s = "abcabcbb"
3
s = "bbbbb"
1
s = "pwwkew"
3
💡 تلميح:
function lengthOfLongestSubstring(s) {
const charSet = new Set();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.length; right++) {
while (charSet.has(s[right])) {
charSet.delete(s[left]);
left++;
}
charSet.add(s[right]);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}أعد true إذا كان s2 يحتوي على ترتيب (permutation) لأي نص فرعي بطول s1 في أي مكان منه.
📋 أمثلة:
s1 = "ab", s2 = "eidbaooo"
true
s1 = "ab", s2 = "eidboaoo"
false
💡 تلميح:
function checkInclusion(s1, s2) {
if (s1.length > s2.length) return false;
const count1 = new Array(26).fill(0);
const count2 = new Array(26).fill(0);
for (let i = 0; i < s1.length; i++) {
count1[s1.charCodeAt(i) - 97]++;
count2[s2.charCodeAt(i) - 97]++;
}
if (count1.every((c, i) => c === count2[i])) return true;
for (let i = s1.length; i < s2.length; i++) {
count2[s2.charCodeAt(i) - 97]++;
count2[s2.charCodeAt(i - s1.length) - 97]--;
if (count1.every((c, j) => c === count2[j])) return true;
}
return false;
}أوجد أطول نص فرعي يحتوي على حرف مكرر واحد فقط يمكن تحقيقه بـ k استبدال حرفي.
📋 أمثلة:
s = "ABAB", k = 2
4
s = "AABABBA", k = 1
4
💡 تلميح:
function characterReplacement(s, k) {
const count = new Array(26).fill(0);
let left = 0;
let maxLength = 0;
let maxCount = 0;
for (let right = 0; right < s.length; right++) {
const idx = s.charCodeAt(right) - 65;
count[idx]++;
maxCount = Math.max(maxCount, count[idx]);
while (right - left + 1 - maxCount > k) {
count[s.charCodeAt(left) - 65]--;
left++;
}
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}أوجد أطول نافذة فرعية (subarray) مجموع عناصرها أكبر من أو يساوي target. إذا لم توجد، أعد 0.
📋 أمثلة:
target = 7, nums = [2,3,1,2,4,3]
2
target = 4, nums = [1,4,4]
1
target = 11, nums = [1,1,1,1,1,1,1,1]
0
💡 تلميح:
function minSubArrayLen(target, nums) {
let minLength = Infinity;
let windowSum = 0;
let left = 0;
for (let right = 0; right < nums.length; right++) {
windowSum += nums[right];
while (windowSum >= target) {
minLength = Math.min(minLength, right - left + 1);
windowSum -= nums[left];
left++;
}
}
return minLength === Infinity ? 0 : minLength;
}أعد مصفوفة من الحد الأقصى لكل نافذة بطول k تتحرك عبر المصفوفة nums من اليسار لليمين.
📋 أمثلة:
nums = [1,3,-1,-3,5,3,6,7], k = 3
[3,3,5,5,6,7]
nums = [1], k = 1
[1]
💡 تلميح:
function maxSlidingWindow(nums, k) {
const result = [];
const deque = [];
for (let i = 0; i < nums.length; i++) {
while (deque.length && deque[deque.length - 1] < nums[i]) {
deque.pop();
}
deque.push(nums[i]);
if (i >= k && nums[i - k] === deque[0]) {
deque.shift();
}
if (i >= k - 1) {
result.push(deque[0]);
}
}
return result;
}أوجد أقصر نص فرعي في s يحتوي على كل حروف t. إذا لم يوجد، أعد السلسلة الفارغة.
📋 أمثلة:
s = "ADOBECODEBANC", t = "ABC"
BANC
s = "a", t = "a"
a
s = "a", t = "aa"
💡 تلميح:
function minWindow(s, t) {
if (t.length > s.length) return "";
const map = new Map();
for (const c of t) {
map.set(c, (map.get(c) || 0) + 1);
}
let required = map.size;
let left = 0;
let formed = 0;
let windowCounts = new Map();
let minLen = Infinity;
let minStart = 0;
for (let right = 0; right < s.length; right++) {
const c = s[right];
windowCounts.set(c, (windowCounts.get(c) || 0) + 1);
if (map.has(c) && windowCounts.get(c) === map.get(c)) {
formed++;
}
while (formed === required) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
minStart = left;
}
const leftChar = s[left];
windowCounts.set(leftChar, windowCounts.get(leftChar) - 1);
if (map.has(leftChar) && windowCounts.get(leftChar) < map.get(leftChar)) {
formed--;
}
left++;
}
}
return minLen === Infinity ? "" : s.substring(minStart, minStart + minLen);
}Two Pointers:
النمط الأساسي
let left = 0, right = arr.length - 1;
while (left < right) {
// قارن وحرّك المؤشر
}متى نستخدمه؟
المصفوفة مرتبة ونسعى لزوج/ثلاثة عناصر
مقارنة عناصر من طرفي المصفوفة
التحقق من palindrome
Sliding Window:
النمط الأساسي
let left = 0;
for (let right = 0; right < n; right++) {
// أضف element[right]
while (شرط_التقلیص) {
// أزل element[left]
left++;
}
// حدّث النتيجة
}متى نستخدمه؟
أطول/أقصر نافذة满足 شرط
نافذة بطول ثابت (Fixed Window)
تتبع حقول في نافذة منزلقة