代码随想录算法训练营第三十九天|Day39 动态规划
198.打家劫舍
视频讲解:https://www.bilibili.com/video/BV1Te411N7SX
https://programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html
思路
#define max(a, b) ((a) > (b) ? (a) : (b))
int rob(int* nums, int numsSize) {
if(numsSize == 0){
return 0;
}
if(numsSize == 1){
return nums[0];
}
int dp[numsSize];
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for(int i = 2; i < numsSize; i++){
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[numsSize - 1];
}
学习反思
使用动态规划的思想来解决打家劫舍问题。首先定义了一个max宏来返回两个数中较大的一个。然后定义了一个dp数组,dp[i]表示在前i个房屋中能够偷到的最大金额。初始情况下,当只有一个房屋时,最大金额就是该房屋的金额。当有两个房屋时,最大金额为两个房屋金额的较大值。然后通过一个循环来计算每个房屋能够偷到的最大金额。对于第i个房屋,有两种选择:偷或者不偷。如果偷第i个房屋,则最大金额为dp[i-2]加上第i个房屋的金额;如果不偷第i个房屋,则最大金额为dp[i-1]。取这两个值中的较大值作为dp[i]的值。最后返回dp[numsSize-1],即在前numsSize个房屋中能够偷到的最大金额。该方法的时间复杂度为O(n)。
213.打家劫舍II
视频讲解:https://www.bilibili.com/video/BV1oM411B7xq
https://programmercarl.com/0213.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DII.html
思路
#define max(a, b) ((a) > (b) ? (a) : (b))
int robRange(int* nums, int start, int end, int numsSize) {
if (end == start) return nums[start];
int dp[numsSize];
dp[start] = nums[start];
dp[start + 1] = max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
int rob(int* nums, int numsSize) {
if (numsSize == 0) return 0;
if (numsSize == 1) return nums[0];
int result1 = robRange(nums, 0, numsSize - 2, numsSize);
int result2 = robRange(nums, 1, numsSize - 1, numsSize);
return max(result1, result2);
}
学习反思
首先定义了一个私有函数robRange,它的功能是求解在某个范围内房屋能够偷到的最大金额。robRange的参数包括nums数组、起始位置start、结束位置end以及数组长度numsSize。它的返回值是在[start, end]范围内能够偷到的最大金额。robRange的实现基本和原来的动态规划解法相同,只是将数组dp的大小改为了numsSize,并且只计算了在[start, end]范围内的结果。然后,在主函数rob中,首先处理特殊情况,当numsSize为0或1时,直接返回结果。然后调用robRange函数分别计算从第0个房屋到倒数第二个房屋和从第1个房屋到最后一个房屋能够偷到的最大金额。最后取这两个结果中的较大值作为最终的结果。这种分段处理的方式能够解决循环数组的问题,时间复杂度仍然是O(n)
337.打家劫舍III
视频讲解:https://www.bilibili.com/video/BV1H24y1Q7sY
https://programmercarl.com/0337.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DIII.html
思路
int *robTree(struct TreeNode *node) {
int* amounts = (int*) malloc(sizeof(int) * 2);
memset(amounts, 0, sizeof(int) * 2);
if(node == NULL){
return amounts;
}
int * left = robTree(node->left);
int * right = robTree(node->right);
amounts[1] = node->val + left[0] + right[0];
amounts[0] = max(left[0], left[1]) + max(right[0], right[1]);
return amounts;
}
int rob(struct TreeNode* root) {
int * dp = robTree(root);
return max(dp[0], dp[1]);
}
学习反思
函数robTree
是递归函数,它接受一个树节点作为参数,返回一个包含两个元素的数组amounts
,其中amounts[0]
表示不打劫当前节点能获得的最大金额,amounts[1]
表示打劫当前节点能获得的最大金额。首先,函数动态为amounts
分配了一个包含两个元素的数组,并使用memset
函数将数组初始化为0。接下来,如果当前节点node
为空,则直接返回amounts
。否则,函数递归地调用robTree
函数,分别对左子树和右子树进行打劫操作,并将返回的结果保存在left
和right
指针中。然后,计算打劫当前节点的最大金额。根据题目要求,打劫当前节点时,不能打劫其直接相连的子节点。因此,打劫当前节点的最大金额等于当前节点的值加上左子树不打劫根节点的最大金额加上右子树不打劫根节点的最大金额。将这个值保存在amounts[1]
中。接着,计算不打劫当前节点的最大金额。根据题目要求,不打劫当前节点时,可以选择打劫其直接相连的子节点。因此,不打劫当前节点的最大金额等于左子树的两种选择中的最大金额加上右子树的两种选择中的最大金额。将这个值保存在amounts[0]
中。最后,返回amounts
数组。函数rob
是主函数,它调用robTree
函数来计算二叉树中打劫的最大金额。首先,调用robTree
函数得到一个包含两个元素的数组dp
。然后,返回dp[0]
和dp[1]
中的较大值作为结果。
总结
今天是打家劫舍的一天,这个系列不算难,加油!!!