جاري التحميل...
جاري التحميل...
أساسيات التعامل مع المصفوفات والنصوص
المصفوفات والنصوص هي هياكل بيانات أساسية تُستخدم في كل مشكلة برمجية تقريبًا. إتقان العمليات عليها هو مفتاح حل الكثير من تحديات البرمجة.
💡 ما سنتعلمه:
عمليات المصفوفات، التلاعب بالنصوص، Hash Maps للعد، تقنية Two Pointers، ونمط Sliding Window.
تخزن المصفوفات العناصر في ذاكرة متتالية، مما يتيح الوصول O(1) حسب الفهرس. تشمل العمليات الشائعة الأتمتة والإدراج والحذف والبحث.
💡 العمليات الرئيسية:
الوصول: O(1)، البحث: O(n)، الإدراج/الحذف في النهاية: O(1)، الإدراج/الحذف في الوسط: O(n)، الفرز: O(n log n).
النصوص هي سلاسل من الأحرف. في JavaScript، النصوص غير قابلة للتعديل، لذا العمليات التي تعدلها تنشئ نصوصًا جديدة.
💡 الطرق المفيدة:
split()، join()، substring()، includes()، indexOf()، charCodeAt()، toLowerCase().
توفر Hash Maps (Objects أو Maps في JS) بحثًا متوسطًا O(1 وهي ضرورية لعد التكرارات ومشاكل Two Sum.
💡 النمط:
استخدم Map/Object لعد التكرارات، ثم ا iterate لإيجاد المطابقات أو التحقق من الشروط.
يستخدم Two Pointers فهرسين لاجتياز هيكل البيانات، عادة من كلا الطرفين أو مؤشر بطيء ومؤشر سريع.
💡 متى تستخدمها:
المصفوفات المرتبة، مشاكل إيجاد الأزواج، التحقق من التناظر، مشاكل الحاوية.
يحافظ Sliding Window على نافذة من العناصر وينقلها عبر المصفوفة/النص لإيجاد المجموعات الفرعية أو النصوص الفرعية المثالية.
💡 النمط:
وسّع النافذة لليمين، قلّص من اليسار عند انتهاك الشرط، تتبع أفضل نتيجة.
معطى مصفوفة من الأعداد nums وعدد صحيح target، أرجع فهرسي عنصرَين مجموعهما يساوي target. يمكنك افتراض أن لكل سؤال إجابة واحدة فقط، ولن تستخدم نفس العنصر مرتين. الحل باستخدام Hash Map يحقق تعقيداً خطياً بدلاً من التعقيد المربّع للحل المزدوج.
📋 أمثلة:
nums = [2,7,11,15], target = 9
[0,1]
nums = [3,2,4], target = 6
[1,2]
nums = [3,3], target = 6
[0,1]
💡 تلميح:
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}معطى مصفوفة prices حيث prices[i] سعر السهم في اليوم i. تريد شراء سهم يوماً واحداً وبعده في يوم لاحق لتحقيق أقصى ربح. أرجع أقصى ربح يمكنك تحقيقه. إذا لم تحقق أي ربح، أرجع 0.
📋 أمثلة:
prices = [7,1,5,3,6,4]
5
prices = [7,6,4,3,1]
0
💡 تلميح:
function maxProfit(prices) {
let minPrice = Infinity;
let maxProfit = 0;
for (let i = 0; i < prices.length; i++) {
if (prices[i] < minPrice) {
minPrice = prices[i];
}
const profit = prices[i] - minPrice;
if (profit > maxProfit) {
maxProfit = profit;
}
}
return maxProfit;
}معطى مصفوفة nums، أرجع true إذا كان يوجد أي عنصر يظهر مرتين على الأقل في المصفوفة، و false إذا كان جميع العناصر فريدة.
📋 أمثلة:
nums = [1,2,3,1]
true
nums = [1,2,3,4]
false
nums = [1,1,1,3,3,4,3,2,4,2]
true
💡 تلميح:
function containsDuplicate(nums) {
const seen = new Set();
for (const num of nums) {
if (seen.has(num)) {
return true;
}
seen.add(num);
}
return false;
}معطى سلسلان s و t، أرجع true إذا كانت s anagram لـ t، و false إذا لم تكن كذلك. Anagram هي كلمة أو عبارة تُشكّل من ترتيب أحرف كلمة أخرى بنفس الأحرف وبنفس العدد.
📋 أمثلة:
s = "anagram", t = "nagaram"
true
s = "rat", t = "car"
false
💡 تلميح:
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const count = {};
for (let i = 0; i < s.length; i++) {
count[s[i]] = (count[s[i]] || 0) + 1;
count[t[i]] = (count[t[i]] || 0) - 1;
}
for (const char in count) {
if (count[char] !== 0) return false;
}
return true;
}معطى سلسلتا ransomNote و magazine، أرجع true إذا كان ransomNote يمكن تشكيله من أحرف magazine. كل حرف في magazine يمكن استخدامه مرة واحدة فقط في ransomNote. يمكنك افتراض أن كل الحروفLowerCase.
📋 أمثلة:
ransomNote = "a", magazine = "b"
false
ransomNote = "aa", magazine = "ab"
false
ransomNote = "aa", magazine = "aab"
true
💡 تلميح:
function canConstruct(ransomNote, magazine) {
const count = {};
for (const char of magazine) {
count[char] = (count[char] || 0) + 1;
}
for (const char of ransomNote) {
if (!count[char] || count[char] === 0) {
return false;
}
count[char]--;
}
return true;
}معطى مصفوفة من السلاسل النصية strs، قم بتجميع الـ Anagrams معاً. Anagram هي كلمة يمكن تشكيلها من ترتيب أحرف كلمة أخرى بنفس الأحرف. أرجع المصفوفة المجمّعة.
📋 أمثلة:
strs = ["eat","tea","tan","ate","nat","bat"]
[["bat"],["tan","ate"],["eat","tea"]]
strs = [""]
[[""]]
strs = ["a"]
[["a"]]
💡 تلميح:
function groupAnagrams(strs) {
const map = new Map();
for (const str of strs) {
const sorted = str.split('').sort().join('');
if (!map.has(sorted)) {
map.set(sorted, []);
}
map.get(sorted).push(str);
}
return Array.from(map.values());
}معطى سلسلة s، أرجع طول أطول نص فرعي (substring) بدون تكرار الأحرف. النص الفرعي هو سلسلة متتالية من الأحرف داخل السلسلة الأصلية.
📋 أمثلة:
s = "abcabcbb"
3
s = "bbbbb"
1
s = "pwwkew"
3
💡 تلميح:
function lengthOfLongestSubstring(s) {
const charSet = new Set();
let left = 0;
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
while (charSet.has(s[right])) {
charSet.delete(s[left]);
left++;
}
charSet.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}معطى مصفوفة nums من أعداد صحيحة، أرجع جميع المجموعات الثلاثية الفريدة التي مجموعها يساوي 0. لا تُدخل الحلول المتكررة في الإجابة.
📋 أمثلة:
nums = [-1,0,1,2,-1,-4]
[[-1,-1,2],[-1,0,1]]
nums = [0,1,1]
[]
nums = [0,0,0]
[[0,0,0]]
💡 تلميح:
function threeSum(nums) {
nums.sort((a, b) => a - b);
const result = [];
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}معطى مصفوفة height حيث height[i] ارتفاع خط على نقطة i، أوجد اثنين من الخطوط مع الأعمدة المائية الحاوية الأكبر مساحة. المنطقة التي تحتويها الحوض تُحسب بـ min(height[i], height[j]) × (j - i).
📋 أمثلة:
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 width = right - left;
const h = Math.min(height[left], height[right]);
const area = width * h;
maxWater = Math.max(maxWater, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}معطى مصفوفة nums، أرجع مصفوفة answer حيث answer[i] هو حاصل ضرب جميع عناصر nums ما عدا nums[i]. يجب حل المسألة بدون استخدام عملية القسمة وبتعقيد O(n).
📋 أمثلة:
nums = [1,2,3,4]
[24,12,8,6]
nums = [-1,1,0,-3,3]
[0,0,9,0,0]
💡 تلميح:
function productExceptSelf(nums) {
const n = nums.length;
const left = new Array(n).fill(1);
const right = new Array(n).fill(1);
const result = new Array(n);
for (let i = 1; i < n; i++) {
left[i] = left[i - 1] * nums[i - 1];
}
for (let i = n - 2; i >= 0; i--) {
right[i] = right[i + 1] * nums[i + 1];
}
for (let i = 0; i < n; i++) {
result[i] = left[i] * right[i];
}
return result;
}معطى مصفوفة nums، أوجد أكبر مجموع لمصفوفة فرعية متصلة. المصفوفة الفرعية هي جزء متتالي من المصفوفة الأصلية (تحتوي على عنصر واحد على الأقل).
📋 أمثلة:
nums = [-2,1,-3,4,-1,2,1,-5,4]
6
nums = [1]
1
nums = [5,4,-1,7,8]
23
💡 تلميح:
function maxSubArray(nums) {
let currentMax = nums[0];
let globalMax = nums[0];
for (let i = 1; i < nums.length; i++) {
currentMax = Math.max(nums[i], currentMax + nums[i]);
globalMax = Math.max(globalMax, currentMax);
}
return globalMax;
}معطى مصفوفة من الفترات intervals حيث intervals[i] = [start, end]، ادمج جميع الفترات المتعارضة (overlapping) في فترات أقصر وأعد المصفوفة الناتجة.
📋 أمثلة:
intervals = [[1,3],[2,6],[8,10],[15,18]]
[[1,6],[8,10],[15,18]]
intervals = [[1,4],[4,5]]
[[1,5]]
💡 تلميح:
function merge(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
const merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const last = merged[merged.length - 1];
const current = intervals[i];
if (current[0] <= last[1]) {
last[1] = Math.max(last[1], current[1]);
} else {
merged.push(current);
}
}
return merged;
}معطى مصفوفة nums مُرتبة ولكن دُرت (rotated) حول فهرس معين، وعدد صحيح target، أرجع فهرس target في nums أو -1 إذا لم يكن موجوداً. يجب أن يكون تعقيد الحل O(log n).
📋 أمثلة:
nums = [4,5,6,7,0,1,2], target = 0
4
nums = [4,5,6,7,0,1,2], target = 3
-1
nums = [1], target = 0
-1
💡 تلميح:
function search(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) return mid;
if (nums[left] <= nums[mid]) {
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}معطى سلسلتان s و t، أرجع أقصر نص فرعي في s يحتوي على جميع أحرف t (بما في ذلك التكرارات). إذا لم يوجد such نص، أرجع السلسلة الفارغة.
📋 أمثلة:
s = "ADOBECODEBANC", t = "ABC"
"BANC"
s = "a", t = "a"
"a"
s = "a", t = "aa"
""
💡 تلميح:
function minWindow(s, t) {
if (!s || !t || s.length < t.length) return "";
const tCount = {};
for (const char of t) {
tCount[char] = (tCount[char] || 0) + 1;
}
let required = Object.keys(tCount).length;
let left = 0;
let minLen = Infinity;
let minStart = 0;
let formed = 0;
const windowCount = {};
for (let right = 0; right < s.length; right++) {
const char = s[right];
windowCount[char] = (windowCount[char] || 0) + 1;
if (tCount[char] && windowCount[char] === tCount[char]) {
formed++;
}
while (formed === required) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
minStart = left;
}
const leftChar = s[left];
windowCount[leftChar]--;
if (tCount[leftChar] && windowCount[leftChar] < tCount[leftChar]) {
formed--;
}
left++;
}
}
return minLen === Infinity ? "" : s.substring(minStart, minStart + minLen);
}صمم خوارزمية لترميز (encode) مصفوفة من السلاسل إلى سلسلة واحدة، وخوارزمية لفك الترميز (decode) من السلسلة المُرمّزة إلى المصفوفة الأصلية. يجب أن يكون الترميز فعالاً ويدعم أي حروف.
📋 أمثلة:
Input: ["lint","code","love","you"]
encode: "4#lint4#code4#love3#you", decode: ["lint","code","love","you"]
Input: ["we","say",":","yes"]
encode: "2#we3#say1#:3#yes", decode: ["we","say",":","yes"]
💡 تلميح:
function encode(strs) {
let encoded = "";
for (const str of strs) {
encoded += str.length + "#" + str;
}
return encoded;
}
function decode(s) {
const result = [];
let i = 0;
while (i < s.length) {
let j = i;
while (s[j] !== "#") {
j++;
}
const len = parseInt(s.substring(i, j));
result.push(s.substring(j + 1, j + 1 + len));
i = j + 1 + len;
}
return result;
}المصفوفات (Arrays):
العمليات الأساسية
// الوصول arr[i]; // O(1) arr.length; // O(1) // الإضافة والحذف arr.push(val); // O(1) arr.pop(); // O(1) arr.unshift(val); // O(n) arr.shift(); // O(n) // الترتيب arr.sort((a,b) => a - b); // O(n log n)
النمط Two Pointers
// من الطرفين
let left = 0, right = arr.length - 1;
while (left < right) {
// معالجة
if (condition) left++;
else right--;
}
// من جهة واحدة
let slow = 0;
for (let fast = 0; fast < n; fast++) {
if (condition) arr[slow++] = arr[fast];
}Prefix Sum
const prefix = [0];
for (let i = 0; i < arr.length; i++) {
prefix.push(prefix[i] + arr[i]);
}
// مجموع العناصر من i إلى j
const sum = prefix[j+1] - prefix[i];النصوص (Strings):
العمليات الأساسية
// الوصول
str[i]; // O(1)
str.length; // O(1)
// التحويلات
str.split(''); // string → array
arr.join(''); // array → string
str.substring(i, j); // جزء من السلسلة
// البحث
str.includes('ab'); // true/false
str.indexOf('ab'); // الفهرس أو -1Sliding Window
let left = 0;
const window = new Set();
for (let right = 0; right < s.length; right++) {
// إضافة للنافذة
window.add(s[right]);
// تقليص من اليسار
while (condition) {
window.delete(s[left]);
left++;
}
// تحديث النتيجة
maxLen = Math.max(maxLen, right - left + 1);
}Kadane's Algorithm
let currentMax = nums[0];
let globalMax = nums[0];
for (let i = 1; i < nums.length; i++) {
currentMax = Math.max(nums[i], currentMax + nums[i]);
globalMax = Math.max(globalMax, currentMax);
}التعقيديات الزمنية المرجعية:
الوصول بالفهرس: O(1)
البحث الخطي: O(n)
البحث الثنائي: O(log n)
الإضافة والنهاية: O(1)
الإضافة من المنتصف: O(n)
الفرز: O(n log n)