目前进度:leetcode刷题
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 = [1,2,3]
输出:3
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/house-robber-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
一开始打算拿二维数组来推导,F[i][j]代表从i→j房能偷的最大金额。则一共可以分四种情况。(是否偷i和是否偷j)。
但我们只是为了求出最后的最大值。所以可以固定起点为0,也就是将结果先分为是否偷0,然后分两种情况分别列状态转移方程。为了让0的周围有两个,且那两个对面至少还有一个,假设这里
n
≥
4
n\ge4
n≥4.(
n
≤
3
n\le3
n≤3的情况就是取nums的最大值。)
-
偷0:那0的周围两个房间(1和n-1)必不能偷。所以得看2→n-2间的房间随便偷的话能偷的最大值。转换为首尾不相连接的房间求解。
假设g(i)为从2偷到i的最大金额。 g ( 2 ) = n u m s [ 2 ] g ( i ) = m a x { g ( i − 2 ) + n u m s [ i ] , g ( i − 1 ) } g(2)=nums[2]\\g(i)=max\{g(i-2)+nums[i],g(i-1)\} g(2)=nums[2]g(i)=max{g(i−2)+nums[i],g(i−1)}为了计算方便令 g ( 0 ) = g ( 1 ) = 0 g(0)=g(1)=0 g(0)=g(1)=0,无实义,仅帮助g(2)从公式推出。
-
不偷0:则0周围两个房间(1和n-1)都有可能被偷。所以同上得看1→n-1间的房间随便偷的话能偷的最大值。转换为首尾不相连接的房间求解。
假设h(i)为从1偷到i的最大金额。 h ( i ) = m a x { h ( i − 2 ) + n u m s [ i ] , h ( i − 1 ) } h(i)=max\{h(i-2)+nums[i],h(i-1)\} h(i)=max{h(i−2)+nums[i],h(i−1)}
总金额最大值
=
m
a
x
{
n
u
m
s
[
0
]
+
g
(
n
−
2
)
,
h
(
n
−
1
)
}
=max\{nums[0]+g(n-2),h(n-1)\}
=max{nums[0]+g(n−2),h(n−1)}我们只需要分别算出g(n-2),h(n-1)即可。
用滚动数组。
(mark一下只有本人才看得懂的草稿)
代码如下
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n<=3){
int max=-1;
for(int i = 0;i<n;i++){
if(max<nums[i]){
max=nums[i];
}
}
return max;
}
else{
//g(n-2),n-2>=2; 2~n-2
//h(n-1),n-1>=3; 3~n-1
//等长
int g0=0;
int g1=0;
int g2=-1;//待算
int h1=nums[1];
int h2=Math.max(nums[1],nums[2]);
int h3=-1;//待算
for(int i = 2;i<n-1;i++){
g2 = Math.max(g0+nums[i],g1);
h3 = Math.max(h1+nums[i+1],h2);
g0=g1;
g1=g2;
h1=h2;
h2=h3;
}
return Math.max(nums[0]+g2,h3);
}
}
}
官方题解
跟我的思路大体上一样,分的情况不一样。首先分为 n ≤ 2 n\le2 n≤2和 n ≥ 3 n\ge3 n≥3。其中当 n ≥ 3 n\ge3 n≥3时,已知第一间房屋和最后一间房屋不能同时偷窃。所以分为
- 不偷窃0房,所以得看1→n-1间的房间随便偷的话能偷的最大值。
- 不偷窃 n − 1 n-1 n−1房,所以得看0→n-2间的房间随便偷的话能偷的最大值。
我分为【偷0房】和【不偷0房】。我在【偷0房】中计算的是2→n-2的最大值g(n-2),并在最后+nums[0]. 他的【不偷窃
n
−
1
n-1
n−1房】中的0可偷可不偷,但会包括我的这种情况。
状态转移方程列了个通项的,这是个可以学习的地方。
代码使用一个函数实现两次计算。我傻傻的用了两组变量
class Solution {
public int rob(int[] nums) {
int length = nums.length;
if (length == 1) {
return nums[0];
} else if (length == 2) {
return Math.max(nums[0], nums[1]);
}
return Math.max(robRange(nums, 0, length - 2), robRange(nums, 1, length - 1));
}
public int robRange(int[] nums, int start, int end) {
int first = nums[start], second = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/house-robber-ii/solution/da-jia-jie-she-ii-by-leetcode-solution-bwja/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
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
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/delete-and-earn
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
步骤分为
- 构成point数组
- 把point数组当成nums求偷东西问题。用滚动数组。
class Solution {
public int deleteAndEarn(int[] nums) {
Arrays.sort(nums);
//第一遍循环确定长度
int n = 1;
for(int i = 1 ;i<nums.length;i++){
if(nums[i]==nums[i-1]+1){//如果当前是前一个的相邻数
n+=1;
}
else if(nums[i]>nums[i-1]+1){//如果当前比前一个数大多了
n+=2;
}
}
//第二次遍历构造point数组
int[] point = new int[n];
point[0]=nums[0];
int j = 0;
for(int i = 1 ;i<nums.length;i++){
if(nums[i]==nums[i-1]){//如果当前数和前一个相等
point[j]+=nums[i];
}
else if(nums[i]==nums[i-1]+1){//如果当前是前一个的相邻数
j+=1;
point[j]=nums[i];
}
else{//如果当前比前一个数大多了
j+=1;
point[j]=0;
j+=1;
point[j]=nums[i];
}
}
//利用point数组,转化为偷东西问题,使用滚动数组
if(n==1){return point[0];}
// if(n==2){return Math.max(point[0],point[1])}//转为用公式计算
int a = 0;//f-1
int b = point[0];//f0
int c = -1;//f1 本应该为临界条件,为了计算方便造了个f-1,这个就可以用公式计算了。
for(int i = 1;i<n;i++){
c = Math.max(a+point[i],b);
a = b;
b = c;
}
return c;
}
}
官方题解
官方题解跟我基本差不多,都是转换成小偷问题,不过没有为了省空间用固定的int数组,或者重构一个point数组过滤多余的0。
方案1的动规基本就是我的最初没有优化的思想。
方案2则是直接将不连续的分成子问题了,相当于他边遍历边计算了,没有像我这样copy一份到point再一口气算,我相当于重复遍历。这点比我的要好,值得学习。
方案2的代码
class Solution {
public int deleteAndEarn(int[] nums) {
int n = nums.length;
int ans = 0;
Arrays.sort(nums);
List<Integer> sum = new ArrayList<Integer>();
sum.add(nums[0]);
int size = 1;
for (int i = 1; i < n; ++i) {
int val = nums[i];
if (val == nums[i - 1]) {
sum.set(size - 1, sum.get(size - 1) + val);
} else if (val == nums[i - 1] + 1) {
sum.add(val);
++size;
} else {
ans += rob(sum);
sum.clear();
sum.add(val);
size = 1;
}
}
ans += rob(sum);
return ans;
}
public int rob(List<Integer> nums) {
int size = nums.size();
if (size == 1) {
return nums.get(0);
}
int first = nums.get(0), second = Math.max(nums.get(0), nums.get(1));
for (int i = 2; i < size; i++) {
int temp = second;
second = Math.max(first + nums.get(i), second);
first = temp;
}
return second;
}
}
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/delete-and-earn/solution/shan-chu-bing-huo-de-dian-shu-by-leetcod-x1pu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
总结
第一题学到了:状态转移方程列了两个的话,可以看看能不能化成一个通项。
第二题主要是没想到可以写一个rob函数将隔开的nums化解成一个个子问题,在遍历的过程中就解。我还是习惯于最后统一处理了。结果还是通项能力。要加强这方面的思想。