分治算法
动态规划
- 斐波那契数列:计算第 n 项的斐波那契数。
- 最大子数组和:找到数组中和最大的连续子数组。
- 最小路径和:在网格中找到从左上角到右下角的最小路径和。
- 背包问题:
- 0-1 背包问题:给定每个物品的重量和价值,背包的最大容量,目标是选择物品使得总价值最大,同时不超过背包的容量。
- 完全背包问题:可以选择多个相同的物品。
- 最长公共子序列:找到两个序列的最长子序列。
- 编辑距离:计算将一个字符串转换成另一个字符串所需的最少操作次数。
贪心算法
- 活动选择问题:给定的开始时间和结束时间中选择最多的互不重叠的活动
- 零钱兑换:用最少的硬币数兑换特定金额。
- 霍夫曼编码:为字符生成最优的二进制编码。
- 最小生成树:
- Prim 算法:在加权图中找到最小生成树。
- Kruskal 算法:通过边的权重选择边构造最小生成树。
- 区间调度问题:选择互不重叠的区间,使得选择的区间数最大。
广度优先搜索
- 编写国际跳棋AI:计算最少走多少步获胜
- 编写拼写检查器:计算最少该多少个地方可以将错拼的单词改成正确的。
//最大子数组和---动态规划
function maxSubArray(nums) {
let res = nums[0]; //全局最大和
let maxCur = nums[0]; //当前最大和
for (let i = 1; i < nums.length; i++) {
maxCur = Math.max(nums[i], maxCur + nums[i]);
res = Math.max(res, maxCur);
}
return res;
}
// 示例
console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]));
//最小路径和------动态规划
function minPathSum(grid) {
const m = grid.length;
const n = grid[0].length;
const dp = Array.from({ length: m }, () => Array(n).fill(0));
dp[0][0] = grid[0][0];
// 将原始grid的值复制到dp数组的第一行和第一列
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];
}
// 示例
const grid = [
[1, 3, 1],
[1, 5, 1],
[4, 2, 1]
];
console.log(minPathSum(grid)); // 输出: 7
//初始化二维数组
const matrix = new Array(m).fill(0)
.map(() => new Array(n).fill(0));
0-1 背包问题-----动态规划
// 0-1 背包问题-----动态规划
function knapsack(weights, values, capacity) {
const n = weights.length; //物品数量
//dp[i][w] 表示前 i 个物品在容量为 w 的情况下的最大价值
//const dp = Array(n + 1).fill(0).map(() => Array(capacity + 1).fill(0));
const dp = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let w = 0; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][capacity];
}
// 示例
console.log(knapsack([1, 2, 3], [10, 15, 40], 6)); // 55
完全背包问题-----动态规划
//完全背包问题-----动态规划
function completeKnapsack(weights, values, capacity) {
const dp = Array(capacity + 1).fill(0); // dp[i] 表示容量为 i 时的最大价值
for (let i = 0; i < weights.length; i++) {
for (let w = weights[i]; w <= capacity; w++) {
//对于每一种物品,都会遍历从它的重量到最大容量,以确保可以选择该物品多次
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[capacity];
}
// 示例
const weights = [1, 3, 4]; // 物品的重量
const values = [15, 20, 30]; // 物品的价值
const capacity = 4; // 背包容量
console.log(completeKnapsack(weights, values, capacity)); // 输出: 45
最长公共子串
最长公共子序列
//最长公共子序列-------动态规划
function longestCommonSubsequence(text1, text2) {
const m = text1.length;
const n = text2.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
//dp[i][j] 表示 text1 的前 i 个字符与 text2 的前 j 个字符的最长公共子序列长度
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; // 字符匹配,长度加1
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); // 取最大值
}
}
}
return dp[m][n];
}
// 示例用法
const text1 = "abcde";
const text2 = "ace";
const result = longestCommonSubsequence(text1, text2);
console.log(result); // 输出: 3
//活动选择---------贪心
function activitySelection(start, finish) {
const n = start.length;
//activities 将是一个包含多个对象的数组,每个对象表示一个活动的开始和结束时间
//finish[i]: 用索引 i 从 finish 数组中获取对应的结束时间
const activities = start.map((s, i) => ({ start: s, finish: finish[i] }));
//按活动结束时间(升序),确保优先选择最早结束的活动
activities.sort((a, b) => a.finish - b.finish);
const selected = [activities[0]];
let lastFinishTime = activities[0].finish;
for (let i = 1; i < n; i++) {
if (activities[i].start >= lastFinishTime) {
selected.push(activities[i]);
lastFinishTime = activities[i].finish;
}
}
return selected;
}
// 示例
console.log(activitySelection([1, 3, 0, 5, 8, 5], [2, 4, 6, 7, 9, 9]));
1.给一个字符串 s
,找到 s
中最长的 回文子串。
中心扩展法
function longestPalindrome(s) {
if (s.length < 2) return s;
let start = 0, maxLength = 1;
// 辅助函数,用于通过中心向两边扩展检查回文
const expandAroundCenter = (left, right) => {
while (left >= 0 && right < s.length && s[left] === s[right]) {
// 更新最长回文的边界
if (right - left + 1 > maxLength) {
start = left;
maxLength = right - left + 1;
}
// 向两边扩展
left--;
right++;
}
};
// 遍历字符串的每一个字符作为中心
for (let i = 0; i < s.length - 1; i++) {
// 奇数长度的回文
expandAroundCenter(i, i);
// 偶数长度的回文
expandAroundCenter(i, i + 1);
}
return s.substring(start, start + maxLength);
}
console.log(longestPalindrome("babad")); // 输出: "bab"
console.log(longestPalindrome("cbbd")); // 输出: "bb"
动态规划法 dp[i][j]
用于表示子串 s[i...j]
是否是回文串
function longestPalindrome(s) {
if (s.length < 2) return s;
let start = 0, maxLength = 1;
const n = s.length;
// 初始化dp数组,长度为n的字符串需要n*n的二维数组
const dp = Array.from({ length: n }, () => Array(n).fill(false));
// 所有长度为1的子串都是回文
for (let i = 0; i < n; i++) {
dp[i][i] = true;
}
// s.length = 2
for (let i = 0; i < n - 1; i++) {
if (s[i] === s[i + 1]) {
dp[i][i + 1] = true;
start = i;
maxLength = 2;
}
}
// s.length > 2
for (let len = 3; len <= n; len++) {
for (let i = 0; i <= n - len; i++) {
const j = i + len - 1;
if (s[i] === s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true;
start = i;
maxLength = len;
}
}
}
return s.substring(start, start + maxLength);
}