返回
打家劫舍:运用动态规划的巧妙策略
前端
2024-01-24 10:49:01
算法视野下的“打家劫舍”:运用动态规划的妙方
在算法的领域中,动态规划是一种强大的技术,它能将复杂问题分解成较小的子问题,从而逐步求解。今天,我们就将聚焦于一个经典的动态规划问题——“打家劫舍”。
问题陈述:
假设你是一个窃贼,准备对一条街上的房屋进行抢劫。这些房屋成一排排列,每个房屋中都存放着一定数量的钱财。但是,有一个限制:相邻的房屋设有报警系统,一旦触发就会惊动警方。因此,你无法抢劫相邻的两间房屋。
你的目标是在不触发报警系统的情况下,抢劫尽可能多的钱财。
递归解法:
初探这个问题,我们可能首先想到递归解法。基本思路是分别计算抢劫第 i 间房屋和不抢劫第 i 间房屋所能获得的最大收益,然后取两者中的较大值作为第 i 间房屋的收益。
function rob(nums) {
if (nums === null || nums.length === 0) {
return 0;
}
// 初始化
const n = nums.length;
const dp = new Array(n).fill(-1);
// 求解
return Math.max(robHelper(nums, n - 1, dp), robHelper(nums, n - 2, dp));
}
function robHelper(nums, i, dp) {
// 备忘录优化
if (dp[i] !== -1) {
return dp[i];
}
// 边界条件
if (i < 0) {
return 0;
}
// 状态转移方程
dp[i] = Math.max(robHelper(nums, i - 2, dp) + nums[i], robHelper(nums, i - 1, dp));
return dp[i];
}
自顶向下备忘录法:
递归解法的效率并不理想,因为在求解过程中会重复计算相同的子问题。为了优化效率,我们可以采用自顶向下备忘录法。通过维护一个备忘录表 dp 来记录已经计算过的子问题的解,避免重复计算。
自底向上动态规划:
自顶向下的备忘录法虽然改善了递归解法的效率,但仍然需要遍历所有可能的子问题。为了进一步优化,我们可以采用自底向上的动态规划方法。这种方法从最小的子问题开始求解,逐步累积求解更大的子问题,最终得到整体问题的解。
function rob(nums) {
if (nums === null || nums.length === 0) {
return 0;
}
// 初始化
const n = nums.length;
const dp = new Array(n + 1).fill(0);
// 自底向上求解
for (let i = 1; i <= n; i++) {
// 状态转移方程
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
}
// 返回最大收益
return dp[n];
}
结论:
“打家劫舍”问题是一个经典的动态规划问题,通过递归、自顶向下备忘录法和自底向上动态规划这三种方法,我们可以逐步优化求解效率。动态规划是一种重要的算法技术,在求解复杂问题时具有广泛的应用场景。