Given an array nums
of integers, you can perform operations on the array.
In each operation, you pick any nums[i]
and delete it to earn nums[i]
points. After, you must delete everyelement equal to nums[i] - 1
or nums[i] + 1
.
You start with 0 points. Return the maximum number of points you can earn by applying such operations.
Example 1:
Input: nums = [3, 4, 2] Output: 6 Explanation: Delete 4 to earn 4 points, consequently 3 is also deleted. Then, delete 2 to earn 2 points. 6 total points are earned.
Example 2:
Input: nums = [2, 2, 3, 3, 3, 4] Output: 9 Explanation: Delete 3 to earn 3 points, deleting both 2's and the 4. Then, delete 3 again to earn 3 points, and 3 again to earn 3 points. 9 total points are earned.
Note:The length of nums
is at most 20000
.Each element nums[i]
is an integer in the range [1, 10000]
.
题意:
给你一个整数数组nums,你可以在数组上做如下操作:选择任意一个数字nums[i]把它删掉并赚到nums[i]个点。此后,你必须删除数组中所有值为nums[i]-1和nums[i]+1的数字。你最开始有0个点。请问你如此执行操作,最多能从数组上赚到多少个点?假设数组中的数字在[1, 10000]的范围之内。
例如假设输入数组nums为[2, 2, 3, 3, 3, 4],你最多可以赚到9个点。你的最佳方案是删掉一个3赚到3个点。此时需要删掉所有的2和4。接下来你可以再删掉一个3赚到3个点。由于所有的2和4都已经删掉了,你不需要再删除更多的数字。然后你再删掉最后一个3再赚到3个点。因此你一共赚到9个点。
分析:
数组nums中的数字都是在[1,10000]的范围之内。这个范围不算太大。因此我们可以创建一个长度为10001的数组vals。该数组中下标为m的数值为原数组nums中所有值为m的数字的和(数值m可能在数组nums中出现0次、一次或者多次)。
根据题目的规则,如果我们赚到数组nums中值为m的点数,那么我们将不能赚到所有值为m-1和m+1的点数。那么在数组vals中如果我们赚到了下标为m的数字对应的点数,那么就不能赚到下标为m-1和m+1的数字对应的点数。
如果我们用函数f(i)表示在数组vals中前i个数字中最多能够赚到的点数,f(i)=max(f(i-1), vals[i]+f(i-2))。这是一个典型的可以用动态规划求解的问题。
由于我们只需要保存f(i-1)和f(i-2)的值就能求的f(i)的值,因此我们在求解过程中只需要使用两个变量存储之前子问题的解即可。
这个思路对应的Java代码如下所示:
public static long deleteAndEarn(int[] nums) {
int[] val = new int[10001];
for (int i = 0; i < nums.length; i ++) {
val[nums[i]] += nums[i];
}
int f1 = 0;
int f2 = val[1];
int max = f2;
for (int i = 2; i < 10001; i ++) {
max = Math.max(f2, val[i]+f1);
f1 = f2;
f2 = max;
}
return max;
}
在上述代码中有两个for循环。第一个for循环的执行次数为数组nums的长度,时间复杂度为O(n)。第二个for循环的执行次数为数组vals的长度即常数10001,时间复杂度是O(1)。因此总的时间复杂度为O(n)。我们创建了一个常数长度的数组vals,以及使用了若干变量,总的空间复杂度是O(1)。
以下是LeetCode给出的解答,基本思想相差不多,但是速度更快。
public int deleteAndEarn(int[] nums) {
int[] count = new int[10001];
for (int x: nums) count[x]++;
int avoid = 0, using = 0, prev = -1;
for (int k = 0; k <= 10000; ++k) if (count[k] > 0) {
int m = Math.max(avoid, using);
if (k - 1 != prev) {
using = k * count[k] + m;
avoid = m;
} else {
using = k * count[k] + avoid;
avoid = m;
}
prev = k;
}
return Math.max(avoid, using);
}