思路
题目要看清
每次操作只会将元素的值减少1
没有增加1的操作,只有对元素进行减少1的操作
思路一
自己一开始没有看清题目,误以为也可以递增1,所以想复杂了,虽然最后做出来了,但是与标准答案不沾边,这里记录一下。
锯齿数组的特点就是一增一减,怎么样才能更加直观的体现这种一增一减的大小变化呢,我想到了差分,相邻两个元素作减法的值不就更能直观的体现元素间的大小关系,所以通过给出的原数组可以得到一个体现相邻元素大小关系的正负数组,如果正负数组中,有连续的正值或者负值,则原数组不是锯齿数组。
对于这个正负数组,我们可以通过将其改造为"±±±…“或者”-±±+…"这两种形式得到锯齿数组,如何改造呢?分情况遍历呗:
- 首先要求正负数组中元素正负关系为"±±±…",那么对于不满足条件的正负值的位计算需要多少次操作能变成满足条件的,累计这些操作次数就可得到结果。
- "-±±+…"情况同样是这样做
但是有一个问题,比如正负数组中某个元素为+,表示当前的两个元素a和b的大小关系为a>b,但是我们为了满足锯齿数组,需要将此元素变为-,那么我们实际会对a和b哪个元素做减法呢?很明显是a,这样做并不会影响后续b和c的正负关系;但是如果某个元素为-,而我们要变成+,即从a<b变成a>b,这个时候我们实际上会改变b的值,从而会改变正负数组中b和c对应的大小关系,而我们还没有遍历到b和c,所以我们需要更新b和c的大小关系,即正负数组的下一项,更新为正确的大小关系。
好了,最终得到的两个结果选取最小值即可。
具体算法过程:
1、数据声明
(1)保存正负关系的数组:data
(2)两次计算结果
2、函数声明
我们可以使用一个函数来对一个正负数组进行处理,判断转化为指定的正负数组需要的操作数,函数包含两个参数,一个是原正负数组data,一个是bool值,来确定是"±±±…“关系还是”-±±+…"关系。
在内部遍历数组的每一个元素,如果不满足正负关系,则计算除满足正负关系需要的最少操作数叠加到结果上。
在这里要注意如果是‘-’变‘+’正的话,还要同时更新正负数组的下一项。
2、过程
(1)首先通过遍历给定数组获得正负数组的值
(2)接下来调用计算"正负关系满足条件所需要操作数"这个函数两次,分别表示两种情况
(3)返回最小值
代码
class Solution {
public:
int compute(vector<int> data, bool curIsPositive) {
int cnt = 0;
int len = data.size();
for(int i=0;i<len;i++) {
if(curIsPositive && data[i] <= 0) {
cnt += 1 - data[i];
if(i+1 < len) {
data[i+1] -= 1 - data[i];
}
}
if(!curIsPositive && data[i] >= 0) {
cnt += data[i] + 1;
}
curIsPositive = !curIsPositive;
}
return cnt;
}
int movesToMakeZigzag(vector<int>& nums) {
vector<int> data;
int cnt1, cnt2;
int len = nums.size();
if(len == 1) return 0;
for(int i=1;i<len;i++) {
data.push_back(nums[i-1] - nums[i]);
}
cnt1 = compute(data,true);
cnt2 = compute(data,false);
return min(cnt1, cnt2);
}
};
思路二
思路二是看了题解后明白的,自己把问题想复杂了,只能进行递减操作,利用贪心的思想,对于一个锯齿数组来说,对较大元素进行减有好处吗?比如相邻的三个元素a,b,c,如果要求a>b<c,那么最开始的时候对a和c进行减法操作很明显是没有好处的,如果b比a和c大的话,你把a和c减小了,增加了一些操作次数,这个时候你还要对b操作将b变得更小,又多了一些操作次数,如果b本来就比a和c小,你对a和c再进行减小操作反而没有任何意义,有可能还会使b变得比a、c大,得不偿失;综上,利用贪心的思想,可以知道,对于要成为锯齿数组,只需将较小的元素变得比相邻元素小就可以了,不用去管那些较大元素,因为你并不能增大他们的值,只能减小他们的值。
有两种情况
- 奇数位较小
- 偶数位较小
我们可以分别遍历奇数位和偶数位,在处理每一位的时候,看看是否比相邻的元素小,如果大的话,就看至少需要减少多少能够达到最小值。
过程:
1、数据声明
(1)记录两种情况分别所需的操作数cnt1,cnt2
2、遍历奇数位,注意对边界值的处理
(1)奇数位不可能是最左侧的,但有可能是最右侧的,如果当前位达到数组右侧边界,则只用和左侧元素比较大小,否则要和两侧中的较小者进行比较大小
(2)如果当前奇数位小于相应元素,则什么都不用操作
(3)如果当前奇数位大于等于相应元素,则记录要变得小于需要的操作次数
3、遍历偶数位,同奇数位,注意边界值即可
4、返回较小者
代码
class Solution {
public:
int movesToMakeZigzag(vector<int>& nums) {
int len = nums.size();
if(len <= 1) return 0;
int cnt1 = 0, cnt2 = 0;
// 遍历奇数位
for(int i=1;i<len;i+=2) {
int tar;
if(i == len -1) {
tar = nums[i-1];
}else{
tar = min(nums[i-1], nums[i+1]);
}
if(nums[i]>=tar) {
cnt1+=nums[i] - tar + 1;
}
}
// 遍历偶数位
for(int i=0;i<len;i+=2) {
int tar;
if(i==0){
tar = nums[i+1];
}else if(i == len -1){
tar = nums[i-1];
}else{
tar = min(nums[i-1], nums[i+1]);
}
if(nums[i] >= tar){
cnt2+=nums[i] - tar +1;
}
}
return min(cnt1, cnt2);
}
};
代码优化
自己写的代码看起来还是很冗余,较于大佬的代码还有很大差距,看了大佬的代码后,有以下点可以优化
1、分别遍历奇数位和偶数位,可以放到一个for里面,使用if条件判断是奇数位还是偶数位
2、边界值的处理,可以认为左值和右值为无穷大,这样就可以共用代码min(left, right)
3、使用if条件更新cnt的值,但是没有else,可以想到else更新cnt时是加0操作,所以不用if else语句,只用选择cnt加的是0还是nums[i]-tar+1即可
优化代码如下:
class Solution {
public:
int movesToMakeZigzag(vector<int>& nums) {
int cnt1=0, cnt2=0;
int len = nums.size();
if(len <=1 ) return 0;
for(int i=0;i<len;i++){
int left = i == 0 ? INT_MAX : nums[i-1];
int right = i == len - 1 ? INT_MAX : nums[i+1];
if(i&1) {
cnt1+=max(0, nums[i] - min(left, right) + 1);
}else{
cnt2+=max(0, nums[i] - min(left, right) + 1);
}
}
return min(cnt1, cnt2);
}
};
总结
- 看清题目的已知条件,如这道题,只能进行递减操作,而不能递增
- 学会想办法优化代码结构