740. 删除并获得点数

740. 删除并获得点数
给你一个整数数组 nums ,你可以对它进行一些操作。每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。下例
规范数据
nums = [2,5,2,7,3,3,5,3,4,5];现在我们就获得了一个拥有列表元素和的地图了,我们对这组数据的处理还不够
题目要求我们每次选一个数就得把其相邻的所欲元素删去,就比如说,你选择了3,就得把列表里所有的2与4删了
我们想,删除相邻的数有什么好方法吗,还真有,把数组排序,排序后,每个数的旁边就是与其最接近的数,至于如果两个在列表相邻的数实际不相邻,写个逻辑判断就行了吧
//sort接受的第一个参数代表排序开始的地方,第二个代表排序结束的地方sort(nums.begin(),nums.end());//nums.begin()返回数组开头,nums.end()返回数组结尾//值得注意的是,大多数时候sort()就只有这一种用法这样我们就获得了一个排好序的数组,下例
nums = [2,2,3,3,3,4,5,5,5,7];我们发现现在相同的数都连在一起了,并且题目中说拿走一个数,不会删除其他的那个数,也就是我们可以拿走所有同样的数
所以对于相同的数,我们要全部拿走,所以我们就可以把相同的数和起来算,以便我们的计算
map<int,int> add;//map类型数据,给定一个键可以返回一个值,与python中的字典同理,可以理解为一个非常大的数组 for(int i = 0;i < size(nums);i++){ add[nums[i]] += nums[i]; //在add的第nums[i]项加上nums[i],求得列表中同一元素的和 }但是这还不够,我们把列表中的数加到一起了,但列表里数的的总量没有变,我们希望这些数都只计算一次,所以我们要给数去重
map<int,int> add;add[nums[0]] = nums[0];//为后面的代码做让步vector<int> new_nums;//新数组,用来存储去重后变量new_nums.push_back(nums[0]);//为后面的代码做让步for(int i = 1;i < size(nums);i++){ add[nums[i]] += nums[i]; if(nums[i] != nums[i-1]){ new_nums.push_back(nums[0]); } //这里如果从0开始的话,数组就会越界,所以从1开始,第2、4行的让步也是为了这个 }这样我们就得到了一个去重并排过序的新数组了,唯一剩的就是逻辑处理部分了
new_nums = [2,3,4,5,7];逻辑处理
这道题的逻辑部分很循规蹈矩,就是一般的动态规划算法——从局部到整体
我们先从局部看起——先看前2项
new_nums = [2,3];前三项的最优拿法是什么呢,因为我们是人,我们可以直接看出来——拿3,但机器可不能直接看出来,需要我们推导出来公式供其使用
所以我们来分析一下,这里有两种可能
- 拿第1项 总点数=2
- 拿第2项 总点数=3
看起来很简单,加到3项试试
new_nums = [2,3,4];- 拿第1、3项 总点数=2+4=6
- 拿第2项 总点数=3
加到4项
new_nums = [2,3,4,5];- 拿第1、4项 总点数=2+5=7
- 拿第2、4项 总点数=3+5=9
- 拿第1、3项 总点数=2+4=6
这里我们发现拿了第4项之后要在1-2项之间找最大的拿才能获得大数,我们注意到1、2项的最大项我们之前找过,现在可以如果服用的话可以减少一种可能
我们定义一个数组,用来存储之前计算的结果,就取名叫MAX
vector<int> MAX={new_nums[0]};//前1项的最大值一定为第1项,因为只有第1项参与计算这个数组MAX之前计算出的最大值,例如MAX[1] = 3,MAX[2] = 6
有了这个数组之后我们再回看new_nums的前4项
- 拿第4项和MAX[1] 总点数= 3 + 5 = 9
- 拿第1、3项 总点数=2 + 4 = 6
加到前5项
new_nums = [2,3,4,5,7];- 拿第5项和MAX[3] 总点数=5 + 6 = 11
- 拿第2、4项 总点数=3+5=9
- 拿第1、4项 总点数=2+5=7
欸,这里的2.3.是不是有些熟悉,我们往前看发现这个我们也算过啊,在算前4项的时候就算过,所有我们也直接拿我们存下的数来算
- 拿第5项和MAX[3] 总点数=5 + 6 = 11
- 拿MAX[4] 总点数=9
于此我们就可以推导出公式,对于前i个数来说,最大值有两种可能
- 第i项本身的值 + MAX[i-2]
- MAX[i-1]
我们找出其中最大的就行了,写成代码就是
MAX[i]={ add[new_nums[i]] + MAX[i-2], MAX[i-1] //new_nums[i]返回当前我们正在处理的数 //add寻找其相同的数的和 //add[new_nums[i]] 就是在初始数组里某个数其相同数的和};我们可以理解为2个值和一个区域的最大值
//非实际要这么做,为方便理解new_nums = [(2,3,4),5,7];//(2,3,4)代表在里面的最大拿法//这个就表示在(2,3,4) + 7和5中做选择我们直接写成代码提交,没通过 /_ \
new_nums = [1,2,3,5]我们的代码在上例中就通不过,最佳选择应该是3和5,但我们的代码会选择2和5,因为我们的代码默认数组为一串连续的整数,而实际上它是不连续的
那我们就加一个条件判断,如果这个数不是连续整数,即new_nums[i] -new_nums[i-1] != 1,那么我们就特殊处理它
因为它不是连续的所以我们可以直接用add[new_nums[i]] + MAX[i-1],这样就一定是最大的
class Solution {public: int deleteAndEarn(vector<int>& nums) { sort(nums.begin(),nums.end());//排序需#include <algorithm>
map<int,int> add;//需#include <map> add[nums[0]] = nums[0];//为后面的代码做让步
vector<int> new_nums;//新数组,用来存储去重后变量 new_nums.push_back(nums[0]);//为后面的代码做让步
for(int i = 1;i < size(nums);i++){ add[nums[i]] += nums[i]; if(nums[i] != nums[i-1]){ new_nums.push_back(nums[i]); } }
int n1=add[new_nums[0]],n2=0;//观察公式发现只需要两个变量即可代替MAX
for(int i = 1;i < size(new_nums);i++){//i = 0的情况无需计算 int curl;//但我们还是需要第三个变量零时存储 if(new_nums[i] - new_nums[i-1] == 1){//判断是否连续 curl = max(add[new_nums[i]] + n2, n1); }else{ curl = add[new_nums[i]] + n1; //new_nums[i]返回当前我们正在处理的数 //add寻找其相同的数和 //add[new_nums[i]] 就是在初始数组里某个数其相同数的和 } n2 = n1; n1 = curl; //滚动更新 }
return n1; }};//时间复杂O(n log n),主要与sort()本身性质决定//空间复杂O(n),与n的增长成正比//这个代码的优化空间极大,为了能代码易读性所以这么写文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












