LeetCode:打家劫舍!~~ 动归整理
作者:互联网
【1、街边的房屋,快乐的小偷1号】
198. 打家劫舍(中等)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
/*
* 每一间房屋都有两种选择,一是偷,二是不偷
* 1、如果偷房屋 i,由于不能偷相邻的房屋,则该方案能偷到的最多的钱就是偷 i-2 房屋的最多金额 + 第 i 房屋的钱
* 2、如果不偷房屋 i,则意味着金额就是偷 i-1 房屋的最多金额
* 因此,可得出状态转移方程:dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
*/
public static int rob(int[] nums) {
int len = nums.length;
if( nums.length == 0 ) return 0;
else if( nums.length == 1 ) return nums[0];
else if( nums.length == 2 ) return Math.max(nums[0], nums[1]);
int[] dp = new int[len]; // 保存到房屋 i 时能偷到的最大金额数
/* 初始化 */
dp[0] = nums[0]; // 只有一间
dp[1] = Math.max(nums[0], nums[1]); // 取两间中金额最大的那间
for( int i = 2; i < len; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[len-1];
}
注:LeetCode题解中还有个利用滚动数组的方式来节省空间,此处附上
public static int rob(int[] nums) {
int len = nums.length;
if( nums.length == 0 ) return 0;
else if( nums.length == 1 ) return nums[0];
else if( nums.length == 2 ) return Math.max(nums[0], nums[1]);
/* 初始化 */
int first = nums[0];
int second = Math.max(nums[0], nums[1]);
for( int i = 2; i < len; i++) {
int t = second;
second = Math.max(first + nums[i], second);
first = t;
}
return second;
}
【2、成环下的房屋,快乐的小偷2号】
213. 打家劫舍 II(中等)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 3:
输入:nums = [0]
输出:0
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
/*
* 先放上状态转移方程:dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
* 因为成环了,所以分两种情况讨论:
* 1、偷房屋 1,则不能偷房屋 nums.length
* 2、偷房屋 nums.length,则不能偷房屋 1
*/
public static int rob(int[] nums) {
int len = nums.length;
if( nums.length == 0 ) return 0;
else if( nums.length == 1 ) return nums[0];
else if( nums.length == 2 ) return Math.max(nums[0], nums[1]);
/*
* 因为成环了,所以分两种情况讨论:
* 1、偷房屋 1,则不能偷房屋 nums.length
* 2、偷房屋 nums.length,则不能偷房屋 1
*/
return Math.max(Rob(nums, 0, nums.length - 1), Rob(nums, 1, nums.length));
}
public static int Rob(int[] nums, int start, int end) {
int first = nums[start];
int second = Math.max(nums[start], nums[start + 1]);
for( int i = start + 2; i < end; i++) {
int t = second;
second = Math.max(first + nums[i], second);
first = t;
}
return second;
}
【3、长在树上的房屋,我愿称这位小偷为最强!】
337. 打家劫舍 III(中等)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
通过次数101,306提交次数164,891
/*
* 【类比于前两道,只不过这次偷的道路变成了左右两条】
* 现在这个小偷爸爸(根)有了两个孩子(叶子),一个叫'左',一个叫'右',两个孩子分别朝左和朝右偷
* 最大金额即小偷爸爸与两个孩子总的金额中偷到的最大金额
* 但是注意:如果小偷爸爸偷了钱,则两个孩子都不能偷了,不然就会被发现;反之,小偷爸爸就不能偷了
*
* 大概描述如下:
* 小偷爸爸不偷:当前节点能偷到的最大金额 = '左'孩子能偷到的最大金额 + '右'孩子能偷到的最大金额
* 小偷爸爸偷了:当前节点能偷到的最大金额 = '左'孩子不偷时的能得到的钱 + '右'孩子不偷时能得到的钱 + 当前房屋的钱
*
* 状态转移方程:[0:不偷时的金额,1:偷后的金额]
* result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
* result[1] = left[0] + right[0] + root.val
*/
public static int rob(TreeNode root) {
int[] result = Rob(root);
return Math.max(result[0], result[1]);
}
public static int[] Rob(TreeNode root) {
if( root == null ) return new int[] {0,0};
int[] result = new int[2];
int[] left = Rob(root.left);
int[] right = Rob(root.right);
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = left[0] + right[0] + root.val;
return result;
}
【4、这莫非就是打家劫舍Ⅳ】
740. 删除并获得点数(中等)
给你一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除每个等于 nums[i] - 1 或 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
示例 1:
输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:
输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104
/*
* 可能有点奇怪,这玩意儿怎么和打家劫舍有关
* 注意,当选择 x 以后,x-1 和 x+1 都不能再选择。为了能尽可能多的获得点数,则 x 要全部选择
* 因此:
* 我们假设题目给出的数组为:
* nums = [2,2,3,3,3,4]
*
* 则构造成【打家劫舍】后:
* houses = [0,0,2,3,1]
*
* 是不是有那味了!
* 我们先贴上【打家劫舍】的状态转移方程:
* dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
* 给出本题目的状态转移方程:
* dp[i] = Math.max(dp[i - 1], dp[i - 2] + i * houses[i]);
* 注:i * houses[i] 代表该房屋有的钱财
*
* 在偷完街边、成环、树上的房屋后,看来这次又是一次完美偷窃
*/
public static int deleteAndEarn(int[] nums) {
if( nums.length == 0 ) return 0;
else if( nums.length == 1) return nums[0];
int max = 0;
for( int i = 0; i < nums.length; i++) { // 获得最大的房屋编号,以便构造【打家劫舍】
max = Math.max(max, nums[i]);
}
int[] houses = new int[max + 1];
for( int i = 0; i < nums.length; i++) {
houses[nums[i]] ++;
}
return rob(houses, max);
}
public static int rob(int[] nums, int max) {
int len = nums.length;
int[] dp = new int[max + 1];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for( int i = 2; i < len; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + i * nums[i]);
}
return dp[max];
}
附上滚动数组的方法
public static int deleteAndEarn(int[] nums) {
if( nums.length == 0 ) return 0;
else if( nums.length == 1) return nums[0];
int max = 0;
for( int i = 0; i < nums.length; i++) { // 获得最大的房屋编号,以便构造【打家劫舍】
max = Math.max(max, nums[i]);
}
int[] houses = new int[max + 1];
for( int i = 0; i < nums.length; i++) {
houses[nums[i]] ++;
}
return rob(houses);
}
public static int rob(int[] nums) {
int len = nums.length;
int first = nums[0];
int second = Math.max(nums[0], nums[1]);
for( int i = 2; i < len; i++) {
int t = second;
second = Math.max(first + i * nums[i], second);
first = t;
}
return second;
}
【如果天才的小偷还有更好的盗窃地点,还会来继续更新的】
标签:nums,动归,max,int,length,房屋,打家劫舍,LeetCode,dp 来源: https://blog.csdn.net/Giilian/article/details/116419702