جاري التحميل...
جاري التحميل...
حل المشاكل باستخدام تقنية البرمجة الديناميكية
حل البرمجة الديناميكية (DP) المشكلات المعقدة عن طريق تقسيمها إلى مشكلات فرعية متراكبة وتخزين النتائج لتجنب الحساب التكراري.
💡 ما سنتعلمه:
أساسيات DP، تعريف الحالة، معادلات الانتقال، Memorization مقابل Tabulation، ومشاكل DP الكلاسيكية.
DP هي تقنية تحسين لمشاكل تحتوي على مشكلات فرعية متراكبة وبنية فرعية مثالية.
💡 شرطان:
1. مشكلات فرعية متراكبة (نفس المشاكل تُحل بشكل متكرر). 2. بنية فرعية مثالية (الحل المثالي يحتوي على حلول فرعية مثالية).
تستخدم Memorization التكرار الذاتي مع ذاكرة مؤقتة لتخزين النتائج المحسوبة، مما يتجنب الحسابات التكرارية.
💡 النمط:
تحقق من الذاكرة المؤقتة قبل الحساب. إذا كانت مخزنة، أعد. وإلا احسب واحفظ وأعد.
تملأ Tabulation جدول DP بشكل تكراري من الحالات الأساسية إلى الإجابة النهائية.
💡 النمط:
بادر بالحالات الأساسية. لكل حالة، احسب من الحالات المحسوبة مسبقًا. أعد dp[target].
مشاكل مثل Climbing Stairs و House Robber و Coin Change تستخدم جدول DP أحادي البعد.
💡 أمثلة:
Climbing Stairs: dp[i] = dp[i-1] + dp[i-2]. House Robber: dp[i] = max(dp[i-1], dp[i-2] + nums[i]).
مشاكل مثل Edit Distance و Regular Expression Matching تستخدم جدول DP ثنائي البعد.
💡 أمثلة:
Edit Distance: dp[i][j] بناءً على تطابق/ عدم تطابق الأحرف. LCS: dp[i][j] من الحالات السابقة.
أنت تصعد سلماً يحتوي على n درجة. في كل مرة يمكنك التقدم بدفعة واحدة أو اثنتين. كم من الطرق المختلفة يمكنك بها الصعود للدرجة الأخيرة؟
📋 أمثلة:
n = 2
2
n = 3
3
💡 تلميح:
function climbStairs(n) {
if (n <= 2) return n;
let dp = new Array(n + 1);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}أنت لص يخطط للسطو على بيوت مجاورة. كل بيت يحتوي على مبلغ مالي معين، لكن إذا سرقت بيتين متجاورين فسيتم إخطار الشرطة. ما هو الحد الأقصى للمبلغ الذي يمكنك سرقته؟
📋 أمثلة:
nums = [1,2,3,1]
4
nums = [2,7,9,3,1]
12
💡 تلميح:
function rob(nums) {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];
let dp = new Array(nums.length);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (let i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[nums.length - 1];
}أعطى مجموعة من العملات بـ различных القيم وقيمة مستهدفة، اكتب دالة تحدد الحد الأدنى لعدد العملات المطلوبة للوصول للقيمة المستهدفة. يمكنك استخدام كل عملة بلا حدود.
📋 أمثلة:
coins = [1,5,10,25], amount = 30
2
coins = [2], amount = 3
-1
💡 تلميح:
function coinChange(coins, amount) {
let dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (let coin of coins) {
if (coin <= i && dp[i - coin] !== Infinity) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}أعطى مصفوفة أرقام، اكتب دالة تحدد أطول متتالية متصاعدة (غير متتالية بالضرورة) في المصفوفة.
📋 أمثلة:
nums = [10,9,2,5,3,7,101,18]
4
nums = [0,1,0,3,2,3]
4
💡 تلميح:
function lengthOfLIS(nums) {
let n = nums.length;
let dp = new Array(n).fill(1);
let maxLen = 1;
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}أعطى سلسلة نصية s وقائمة من الكلمات dictionary، حدد ما إذا كان يمكن تقسيم s إلى مجموعة من الكلمات الموجودة في القاموس. يمكن استخدام كل كلمة عدة مرات.
📋 أمثلة:
s = "leetcode", wordDict = ["leet","code"]
true
s = "catsandog", wordDict = ["cats","dog","sand","and","cat"]
false
💡 تلميح:
function wordBreak(s, wordDict) {
let n = s.length;
let dp = new Array(n + 1).fill(false);
dp[0] = true;
let wordSet = new Set(wordDict);
for (let i = 1; i <= n; i++) {
for (let j = 0; j < i; j++) {
if (dp[j] && wordSet.has(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[n];
}أعطى كلمتان word1 و word2، اكتب دالة تحدد الحد الأدنى لعدد العمليات المطلوبة لتحويل word1 إلى word2. العمليات المسموح بها: إدراج حرف، حذف حرف، أو استبدال حرف.
📋 أمثلة:
word1 = "horse", word2 = "ros"
3
word1 = "intention", word2 = "execution"
5
💡 تلميح:
function minDistance(word1, word2) {
let m = word1.length;
let n = word2.length;
let dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(
dp[i - 1][j],
dp[i][j - 1],
dp[i - 1][j - 1]
);
}
}
}
return dp[m][n];
}أنت تلعب لعبة فقاعات. لديك n فقاعة مرقمة من 1 إلى n. عند فقاعة الفقاعة i، ستفقد nums[i-1] * nums[i] * nums[i+1] نقطة. إذا انفجرت الفقاعة على الحافة، تُعامل nums[-1] و nums[n] كـ 1. احسب الحد الأقصى للنقاط.
📋 أمثلة:
nums = [3,1,5,8]
167
nums = [1,5]
10
💡 تلميح:
function maxCoins(nums) {
let n = nums.length;
let newNums = [1, ...nums, 1];
let dp = Array.from({ length: n + 2 }, () => new Array(n + 2).fill(0));
for (let len = 1; len <= n; len++) {
for (let i = 1; i <= n - len + 1; i++) {
let j = i + len - 1;
for (let k = i; k <= j; k++) {
dp[i][j] = Math.max(
dp[i][j],
dp[i][k - 1] + dp[k + 1][j] + newNums[i - 1] * newNums[k] * newNums[j + 1]
);
}
}
}
return dp[1][n];
}تنفيذ تطابق regex يدعم '.' و '*'. '.' يطابق أي حرف واحد، و '*' يطابق صفر أو أكثر من الحرف السابق له. التطابق يجب أن يغطي السلسلة كاملة.
📋 أمثلة:
s = "aa", p = "a"
false
s = "aa", p = "a*"
true
s = "ab", p = ".*"
true
💡 تلميح:
function isMatch(s, p) {
let m = s.length;
let n = p.length;
let dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(false));
dp[0][0] = true;
for (let j = 2; j <= n; j++) {
if (p[j - 1] === '*') dp[0][j] = dp[0][j - 2];
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (p[j - 1] === '*') {
dp[i][j] = dp[i][j - 2];
if (p[j - 2] === '.' || p[j - 2] === s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (p[j - 1] === '.' || p[j - 1] === s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}أعطى سلسلة تحتوي فقط على الأقواس '(' و ')', أوجد أطول أقواس صحيحة متتالية في السلسلة.
📋 أمثلة:
s = "(()"
2
s = ")()())"
4
💡 تلميح:
function longestValidParentheses(s) {
let n = s.length;
let dp = new Array(n).fill(0);
let maxLen = 0;
for (let i = 1; i < n; i++) {
if (s[i] === ')') {
if (s[i - 1] === '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (dp[i - 1] > 0) {
let matchIndex = i - dp[i - 1] - 1;
if (matchIndex >= 0 && s[matchIndex] === '(') {
dp[i] = dp[i - 1] + 2 + (matchIndex >= 1 ? dp[matchIndex - 1] : 0);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
}
return maxLen;
}أعطى مصفوفة prices حيث prices[i] سعر السهم في اليوم i، وأقصى عدد k من المعاملات المسموح بها، احدد الحد الأقصى للربح الذي يمكنك تحقيقه. يجب أن تبيع قبل أن تشتري مرة أخرى.
📋 أمثلة:
k = 2, prices = [3,2,6,5,0,3]
7
k = 2, prices = [2,4,1]
2
💡 تلميح:
function maxProfit(k, prices) {
let n = prices.length;
if (k >= Math.floor(n / 2)) {
let profit = 0;
for (let i = 1; i < n; i++) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
let dp = Array.from({ length: n }, () =>
Array.from({ length: k + 1 }, () => [0, 0])
);
for (let j = 1; j <= k; j++) {
dp[0][j][0] = 0;
dp[0][j][1] = -prices[0];
}
for (let i = 1; i < n; i++) {
for (let j = 1; j <= k; j++) {
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
return dp[n - 1][k][0];
}