Skip to content

动态规划

  • 重复子问题:这些子问题不应该被重复计算
  • 最优子结构:原问题的最优解可以分解为若干个子问题的最优解
  • 无后效性:后面阶段的状态只依赖于前面阶段的状态

比特位计数

最高位

ts
function countBits(n: number): number[] {
  const res = new Array<number>(n + 1);
  res[0] = 0;
  let msb = 1;
  for (let i = 1; i <= n; ++i) {
    if ((i & (i - 1)) === 0) {
      msb = i;
    }
    res[i] = res[i - msb] + 1;
  }
  return res;
}

最低位

ts
function countBits(n: number): number[] {
  const res = new Array<number>(n + 1);
  res[0] = 0;
  for (let i = 1; i <= n; ++i) {
    res[i] = res[i >> 1] + (i & 1);
  }
  return res;
}

最低设置位

ts
function countBits(n: number): number[] {
  const res = new Array<number>(n + 1);
  res[0] = 0;
  for (let i = 1; i <= n; ++i) {
    res[i] = res[i & (i - 1)] + 1;
  }
  return res;
}

最长回文子串

ts
function longestPalindrome(s: string): string {
  const n = s.length;
  const dp: boolean[][] = new Array(n).fill(0).map(() => new Array(n));
  let maxLen = 0;
  let maxIdx = 0;
  for (let len = 1; len <= n; ++len) {
    for (let i = 0; i + len - 1 < n; ++i) {
      const j = i + len - 1;
      if (s[i] !== s[j]) {
        dp[i][j] = false;
      } else if (len <= 3) {
        dp[i][j] = true;
      } else {
        dp[i][j] = dp[i + 1][j - 1];
      }
      if (dp[i][j] && len > maxLen) {
        maxLen = len;
        maxIdx = i;
      }
    }
  }
  return s.substring(maxIdx, maxIdx + maxLen);
}

最大子数组和

ts
function maxSubArray(nums: number[]): number {
  let res = -Infinity,
    sum = 0;
  for (const num of nums) {
    if (sum <= 0) {
      sum = num;
    } else {
      sum += num;
    }
    res = Math.max(res, sum);
  }
  return res;
}

不同路径

组合数:C(m-1, m+n-2)

ts
function uniquePaths(m: number, n: number): number {
  const dp = new Array(m).fill(0).map(() => new Array(n));
  for (let i = 0; i < m; ++i) {
    dp[i][0] = 1;
  }
  for (let j = 1; j < n; ++j) {
    dp[0][j] = 1;
  }
  for (let i = 1; i < m; ++i) {
    for (let j = 1; j < n; ++j) {
      dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    }
  }
  return dp[m - 1][n - 1];
}

最小路径和

ts
function minPathSum(grid: number[][]): number {
  const m = grid.length;
  const n = grid[0].length;
  const dp = new Array(m).fill(0).map(() => new Array(n));
  dp[0][0] = grid[0][0];
  for (let i = 1; i < m; ++i) {
    dp[i][0] = dp[i - 1][0] + grid[i][0];
  }
  for (let j = 1; j < n; ++j) {
    dp[0][j] = dp[0][j - 1] + grid[0][j];
  }
  for (let i = 1; i < m; ++i) {
    for (let j = 1; j < n; ++j) {
      dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
    }
  }
  return dp[m - 1][n - 1];
}

单词拆分

ts
function wordBreak(s: string, wordDict: string[]): boolean {
  const set = new Set(wordDict);
  const n = s.length;
  const dp = new Array<boolean>(n + 1).fill(false);
  dp[0] = true;
  for (let i = 1; i <= n; ++i) {
    for (let j = 0; j < i; ++j) {
      if (dp[j] && set.has(s.substring(j, i))) {
        dp[i] = true;
        break;
      }
    }
  }
  return dp[n];
}

乘积最大子数组

ts
function maxProduct(nums: number[]): number {
  let prevMin = 1;
  let prevMax = 1;
  let currMin = 1;
  let currMax = 1;
  let res = -Infinity;
  for (const num of nums) {
    currMin = Math.min(prevMin * num, prevMax * num, num);
    currMax = Math.max(prevMin * num, prevMax * num, num);
    res = Math.max(res, currMax);
    [prevMin, prevMax] = [currMin, currMax];
  }
  return res;
}

最大正方形

ts
function maximalSquare(matrix: string[][]): number {
  const m = matrix.length;
  const n = matrix[0].length;
  const dp: number[][] = new Array(m).fill(0).map(() => new Array(n));
  let maxSide = 0;
  for (let i = 0; i < m; ++i) {
    dp[i][0] = matrix[i][0] === '1' ? 1 : 0;
    maxSide = Math.max(maxSide, dp[i][0]);
  }
  for (let j = 1; j < n; ++j) {
    dp[0][j] = matrix[0][j] === '1' ? 1 : 0;
    maxSide = Math.max(maxSide, dp[0][j]);
  }
  for (let i = 1; i < m; ++i) {
    for (let j = 1; j < n; ++j) {
      if (matrix[i][j] === '1') {
        dp[i][j] =
          Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1;
      } else {
        dp[i][j] = 0;
      }
      maxSide = Math.max(maxSide, dp[i][j]);
    }
  }
  return maxSide * maxSide;
}

完全平方数

ts
function numSquares(n: number): number {
  const dp = new Array<number>(n + 1);
  dp[0] = 0;
  for (let i = 1; i <= n; ++i) {
    dp[i] = Infinity;
    for (let j = 1; j * j <= i; ++j) {
      dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
    }
  }
  return dp[n];
}

最长递增子序列

ts
function lengthOfLIS(nums: number[]): number {
  const n = nums.length;
  const dp = new Array(n).fill(1);
  let res = 0;
  for (let i = 0; i < n; ++i) {
    for (let j = i - 1; j >= 0; --j) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
      }
    }
    res = Math.max(res, dp[i]);
  }
  return res;
}

零钱兑换

ts
function coinChange(coins: number[], amount: number): number {
  const n = coins.length;
  coins.sort((a, b) => a - b);
  const dp = new Array<number>(amount + 1);
  dp[0] = 0;
  for (let i = 1; i <= amount; ++i) {
    dp[i] = Infinity;
    for (let j = 0; j < n && coins[j] <= i; ++j) {
      dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

回文子串

ts
function countSubstrings(s: string): number {
  const n = s.length;
  const dp: boolean[][] = new Array(n).fill(0).map(() => new Array(n));
  let res = 0;
  for (let len = 1; len <= n; ++len) {
    for (let i = 0; i + len <= n; ++i) {
      const j = i + len - 1;
      if (s[i] !== s[j]) {
        dp[i][j] = false;
      } else if (len <= 2) {
        dp[i][j] = true;
      } else {
        dp[i][j] = dp[i + 1][j - 1];
      }
      res += dp[i][j] ? 1 : 0;
    }
  }
  return res;
}

正则表达式匹配

ts
function isMatch(s: string, p: string): boolean {
  const m = s.length;
  const n = p.length;
  const dp: boolean[][] = new Array(m + 1)
    .fill(0)
    .map(() => new Array(n + 1).fill(false));
  dp[0][0] = true;
  const canMatch = (i: number, j: number): boolean => {
    if (i === 0) {
      return false;
    }
    const c1 = s[i - 1];
    const c2 = p[j - 1];
    return c1 === c2 || c2 === '.';
  };
  for (let i = 0; i <= m; ++i) {
    for (let j = 1; j <= n; ++j) {
      if (p[j - 1] === '*') {
        dp[i][j] = dp[i][j - 2];
        if (canMatch(i, j - 1)) {
          dp[i][j] ||= dp[i - 1][j];
        }
      } else {
        if (canMatch(i, j)) {
          dp[i][j] = dp[i - 1][j - 1];
        }
      }
    }
  }
  return dp[m][n];
}

最长有效括号

ts
function longestValidParentheses(s: string): number {
  const n = s.length;
  const dp: number[] = new Array(n).fill(0);
  let res = 0;
  for (let i = 1; i < n; ++i) {
    if (s[i] === ')') {
      if (s[i - 1] === '(') {
        dp[i] = (dp[i - 2] ?? 0) + 2;
      } else if (s[i - dp[i - 1] - 1] === '(') {
        dp[i] = dp[i - 1] + 2;
        dp[i] += i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0;
      }
      res = Math.max(res, dp[i]);
    }
  }
  return res;
}

编辑距离

ts
function minDistance(word1: string, word2: string): number {
  const m = word1.length;
  const n = word2.length;
  const dp: number[][] = new Array(m + 1)
    .fill(0)
    .map(() => new Array(n + 1));
  for (let i = 0; i <= m; ++i) {
    dp[i][0] = i;
  }
  for (let j = 1; j <= n; ++j) {
    dp[0][j] = j;
  }
  for (let i = 1; i <= m; ++i) {
    for (let j = 1; j <= n; ++j) {
      const top = dp[i - 1][j] + 1;
      const left = dp[i][j - 1] + 1;
      const topLeft =
        dp[i - 1][j - 1] + (word1[i - 1] !== word2[j - 1] ? 1 : 0);
      dp[i][j] = Math.min(top, left, topLeft);
    }
  }
  return dp[m][n];
}

戳气球

ts
function maxCoins(nums: number[]): number {
  const n = nums.length;
  nums.unshift(1);
  nums.push(1);
  const dp: number[][] = new Array(n + 2)
    .fill(0)
    .map(() => new Array(n + 2).fill(0));
  for (let i = n - 1; i >= 0; --i) {
    for (let j = i + 1; j < n + 2; ++j) {
      for (let k = i + 1; k < j; ++k) {
        const prod = nums[i] * nums[k] * nums[j];
        dp[i][j] = Math.max(dp[i][j], prod + dp[i][k] + dp[k][j]);
      }
    }
  }
  return dp[0][n + 1];
}

剪绳子

ts
function cuttingRope(n: number): number {
  const dp = new Array(n + 1).fill(0);
  dp[0] = dp[1] = 1;
  for (let i = 2; i <= n; ++i) {
    for (let j = 1; j < i; ++j) {
      dp[i] = Math.max(dp[i], Math.max(dp[i - j], i - j) * j);
    }
  }
  return dp[n];
}

最长公共子序列

ts
function longestCommonSubsequence(text1: string, text2: string): number {
  const m = text1.length;
  const n = text2.length;
  const dp: number[][] = new Array(m + 1)
    .fill(0)
    .map(() => new Array(n + 1).fill(0));
  for (let i = 1; i <= m; ++i) {
    for (let j = 1; j <= n; ++j) {
      if (text1[i - 1] === text2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }
  return dp[m][n];
}