动态规划解析
动态规划是解决问题的一种算法思想,它将原问题分解成若干个子问题,通过求解子问题的最优解来求解原问题的最优解,
- 爬楼梯问题假设有 n 级台阶,每次可以跳 1 级或 2 级台阶,问到达第 n 级台阶有多少种不同的跳法?
function climbStairs(n) {
if (n === 0 || n === 1) return 1;
const dp = new Array(n + 1);
dp[0] = 1;
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 示例
const n = 5;
console.log(`到达第 ${n} 级台阶的不同跳法数量为:${climbStairs(n)}`);
- 最小路径和问题 假设我们有一个二维数组 grid,其中 grid[i][j] 表示第 i 行第 j
列的元素。我们的目标是找到一个特定路径,这个路径满足一些限制条件(例如,只能向右或向下移动),并且路径上的元素和(或其他特定性质)达到最小值
function minPathSum(grid) {
const m = grid.length;
const n = grid[0].length;
// 初始化dp数组,dp[i][j]表示从起点到达第i行第j列的最小路径和
const dp = Array.from(Array(m), () => Array(n).fill(0));
// 初始化dp数组第一行和第一列
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];
}
// 递推填充dp数组
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
- 股票购买的最佳时间问题目标是在一段时间内获取最大利润,给定一组股票的价格,你可以通过买卖来获取利润,但你不能同时拥有多于一支股票,也不能在卖出股票前再次购买。
javascript
function maxProfit(prices) {
const n = prices.length;
if (n <= 1) {
return 0;
}
// 定义动态规划数组
const dp = Array.from(Array(n), () => Array(2).fill(0));
// 初始化第一天的状态
dp[0][0] = 0; // 第一天未持有股票 //全部卖出
dp[0][1] = -prices[0]; // 第一天持有股票 //手上还有
// 状态转移方程
for (let i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);//昨天持有股票加上今天卖出 和 昨天买完剩余对比
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);//今天购买股票后剩余的等于 昨天没卖的钱 和 今天购买剩余最大的值
}
// 返回最后一天未持有股票的最大利润
return dp[n - 1][0];
}
// 示例
const prices = [7, 1, 5, 3, 6, 4];
console.log("最大利润为:", maxProfit(prices)); // 输出:7
- 打家劫舍问题给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。连续偷取相邻房间会触发警报
function rob(nums) {
if (!nums || nums.length === 0) {
return 0;
}
const n = nums.length;
if (n === 1) {
return nums[0];
}
const dp = new Array(n).fill(0);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (let i = 2; i < n; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
// 示例
const nums = [2, 7, 9, 3, 1];
console.log("能够偷窃到的最高金额为:", rob(nums)); // 输出:12
贪心算法
贪心算法:是一种在每一步选择中都采取在当前状态下最好或最优(即局部最优解)的选择,从而希望最终能够达到全局最优解的算法。
- 分饼干问题有一群孩子和一些饼干,每个孩子有一个满足度,每个饼干都有一个尺寸。每个孩子最多只能给一块饼干。求解如何分配饼干,能让尽可能多的孩子满足,并输出最多满足孩子的数量。
function findContentChildren(g, s) {
g.sort((a, b) => a - b); // 按照孩子的满足度排序
s.sort((a, b) => a - b); // 按照饼干的尺寸排序
let child = 0;
let cookie = 0;
while (child < g.length && cookie < s.length) {
if (g[child] <= s[cookie]) {
child++; // 孩子满足,继续下一个孩子
}
cookie++; // 继续下一个饼干
}
return child; // 返回满足的孩子数量
}
// 示例
const children = [1, 2, 3];
const cookies = [1, 1];
console.log("最多能满足的孩子数量为:", findContentChildren(children, cookies)); // 输出:1
- 股票购买的最佳时间 贪心算法。
求解如何设计一个算法来获取最大利润。贪心策略是:在每一步选择中,都选择能获得正收益的操作,即当天的价格比前一天的价格高时,就进行买入卖出操作,及这就是这个数组的最大利润
function maxProfit(prices) {
let maxProfit = 0;
for (let i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
// 示例
const prices = [7, 1, 5, 3, 6, 4];
console.log("最大利润为:", maxProfit(prices)); // 输出:7
回溯算法
回溯算法:是一种穷举搜索的算法,常用于解决组合优化问题、排列问题、子集问题等。回溯算法通过尝试所有可能的候选解,并在搜索过程中不断地深入候选解空间,直到找到一个满足条件的解或者搜索完整个空间后未找到满足条件的解为止。
- 全排列问题 。
javascript
function permute(nums) {
const result = [];
function backtrack(tempList) {
// 如果当前排列的长度等于数组的长度,则将当前排列加入结果集
if (tempList.length === nums.length) {
result.push([...tempList]);
return;
}
for (let i = 0; i < nums.length; i++) {
// 如果当前元素已经在排列中,则跳过
if (tempList.includes(nums[i])) continue;
// 将当前元素加入排列
tempList.push(nums[i]);
// 继续向下递归
backtrack(tempList);
// 回溯,将当前元素从排列中移除
tempList.pop();
}
}
backtrack([]);
return result;
}
// 示例
const nums = [1, 2, 3];
const permutations = permute(nums);
console.log(permutations);
- ** N 皇后问题**
function solveNQueens(n) {
const result = [];
const board = Array.from({ length: n }, () => Array(n).fill('.'));
function isSafe(row, col) {
// 检查同一列是否有皇后
for (let i = 0; i < row; i++) {
if (board[i][col] === 'Q') {
return false;
}
}
// 检查左上方斜线是否有皇后
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] === 'Q') {
return false;
}
}
// 检查右上方斜线是否有皇后
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] === 'Q') {
return false;
}
}
return true;
}
function backtrack(row) {
if (row === n) {
const solution = board.map(row => row.join(''));
result.push(solution);
return;
}
for (let col = 0; col < n; col++) {
if (isSafe(row, col)) {
board[row][col] = 'Q';
backtrack(row + 1);
board[row][col] = '.';
}
}
}
backtrack(0);
return result;
}
// 示例
const n = 4;
const solutions = solveNQueens(n);
console.log(solutions);
其他
- 最长公共子序列
function diffAlgorithm(oldArr, newArr) {
// 初始化动态规划表
const dp = Array.from({ length: oldArr.length + 1 }, () => Array(newArr.length + 1).fill(0));
// 填充动态规划表
for (let i = 1; i <= oldArr.length; i++) {
for (let j = 1; j <= newArr.length; j++) {
if (oldArr[i - 1] === newArr[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]);
}
}
}
// 回溯找到匹配的元素集合
const matchedElements = [];
let i = oldArr.length, j = newArr.length;
while (i > 0 && j > 0) {
if (oldArr[i - 1] === newArr[j - 1]) {
matchedElements.push(oldArr[i - 1]);
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}
// 反转匹配的元素集合,因为回溯时是从后向前的
matchedElements.reverse();
return matchedElements;
}
// 示例用法
const oldArr = [1, 2, 3, 4, 5];
const newArr = [1, 3, 5, 7];
const matchedElements = diffAlgorithm(oldArr, newArr);
console.log(matchedElements); // 输出 [1, 3, 5]