candy-leetcode :只需要遍历一遍的解法

注;绝大部分解法都是O(n)的复杂度,但是可能需要扫描多遍。 
本方法只需要从左到右扫描一遍,因此在数据量大的时候应该是会有优势的。但复杂度也是O(n)
版本1:寻找局部最高点和最低点,局部最低点取值为1,并向两边逐1扩展;局部最高点取左右极限(虽然不是极限的概念)的最大值。
版本2:
基本思路:从上面的方法可以发现:那么从左往右扫描时,可以确定递增序列中每个位置该分配多少个糖果;对于递减序列,无法确定每个位置的糖果数,但是可以确定其总和。 因为假设有递减序列4 2 1,那么分配的糖果数分别为3,2,1  我们将[3 2 1]颠倒为[1 2 3],并不影响糖果总数。因此,当确定一个单调序列的首尾时,我们可以忽略序列的单调性,统一分配糖果1 2 3 4....
有了这个思路后,只要再做一些收尾工作,如局部最高点的糖果的值、每个序列中第一个糖果的值等,即可在一遍遍历后得到结果。

单调性序列: 1.   A[i-1]<A[i-2]且A[i]<A[i-1],或A[i-1]>A[i-2]且A[i]>A[i-1]>A[i]   则 i与i-1属于同一序列。即A[i]与A[i-1]的左差分同号时。
                    2. 当A[i]==A[i-1]或者(1)中的式子不满足时,A[i-1]与A[i]分处2个序列中,且分别为下一个序列的第一个元素和上一个元素的最后                                 一个元素。       
从左往右遍历元素,记T[i]为i分配的糖果数,当i与i-1处于同一序列时,i分配的糖果数T[i]=T[i-1]+1。
假设我们现在有Ratings={1 2 3 5 3 2 3 4},则应该分配的糖果数T={1 2 3 4 2 1 2 3}(可以画个图,看起来比较清楚)。
序列第一个元素该分配的糖果数
暂时不考虑第一个元素(i=0),则T中按单调性序列定义方法可以分为如下子集: {1 2 3 4 2 1 2 3} -> {1 {2 3 4} {2 1} {2 3}}。可以看到,再按基本思路理提到的,将[2 1]颠倒,则可以得到 {1 {2 3 4} {1 2} {2 3}}。   
从中我们可以找到规律:对于递增序列,序列中的第一个元素该分配2颗糖;递减序列中第一个元素分配1颗糖。 这一结论也很容易证明。由于上述单调性序列的定义,局部最小点总是在递减序列的最尾部,而局部最小点必定分1颗糖,因此递增序列分配的元素在颠倒之前就是{...  3 2 1},颠倒之后就是{1 2 3...}。   而递减序列的第一个点必定是局部最小点之后(确切地说是分配1颗糖之后),因此必定是2。
结论:递增序列的第一个元素分配2颗糖,递减序列的第一个元素分配一颗糖。

局部最大点该分配的糖果数
还是以上述Ratings={1 2 3 5 3 2 3 4}为例,局部最大值在Ratings[4],左边为递增序列T_l={2 3 4},右边为递减序列{1 2}。按上述单调性序列的定义,局部最大值总是在左侧的递增序列中,值为4;如果把该点分配给右边的递减序列,则该分配的糖果数是3.因为4>3,所以局部最大点应分配的糖果数为4。
一般化而言,局部最大点左侧的递增序列为{2 3 ...m},右侧递减序列为{1 2 ...n}时,该点该分配的糖果数为max(m,n+1)。
在一次遍历中,我们采用便分配边累加的方式。由于n的值只有在递减序列的尾部结束,即下一个递增序列开始时才可以确定,因此我们先记录m的值,在递减序列结束时,比较递减序列最后一个元素分配的糖果数n再加上1,即n+1与m的大小。如果n+1>m,则将插值累加上。

————————————————————————————————————
代码中最后一部分的逻辑统一
最后还剩对于A[i]=A[i-1]的情况,当A[i]=A[i-1]的时候,i分配的糖果必为1,i与i-1分属2个序列。
当A[i]=A[i-1],且A[i+1]>A[i]时,A[i+1]的值取为2,也可以看做是A[i+1]=2=A[i]+1,因此可以将i与i+1看做是同一序列中。这样的做法虽然与上述的单调                                  性序列定义相悖,但有利于代码的简洁。
                     当A[i+1]<A[i]<A[i]时,A[i+1]与A[i]分属于2个不同的序列,且可以将A[i]这一个元素看做是递增序列,这样A[i]作为局部最大值,局部最大点                      该分配的糖果的策略依然有效。

由此,我们重新定义单调性定义(注,第一个版本是为了后续推导,关注定义的简洁;这一版本是为了代码中的逻辑整合,目的是代码的简洁):
。。。算了不定义了  还是看算法吧。

结果ans,初始化为1  
从下标为1的元素向右逐个遍历,记当前元素下标为i:
if(i在序列中时)
给i分配的糖果数T[i]=T[i-1]+1;
 else     //i为新序列的第一个元素:
if(上一序列为递减序列)   //开始清尾工作
ans+=max(T[i-1]+1-m,0);      //m为上上个序列,即递增序列的最大值
       //开始初始化工作
         m=T[i-1]                                                 //保存上一序列的最大值,不管递增还是递减了
       T[i]=1+当前是否为递增序列            //是的话T[i]=2,else T[i]=1
ans+=T[i];


nt candy(vector<int> &ratings) {
        if (ratings.size() <= 1) return ratings.size();
        int i = 0, m=0,n=1, ans = 1;
        bool ac = true;
        while (++i < ratings.size()){
            if ( ratings[i]==ratings[i - 1]||ac ^ (ratings[i] > ratings[i - 1]))			//转折点
            {		//使用"上个状态结束做....下个状态开始做...."的思路,上个状态由ac决定,下个状态由i于i-1的关系决定
                if (!ac)
                    ans += n + 1 > m ? n+1-m : 0;
                m = n;
                ac = ratings[i] >= ratings[i - 1];
                n = 1 + (ratings[i] > ratings[i - 1]);
            }
            else
                ++n;
            ans += n;
        }
        if (!ac)
            ans += n + 1 > m ? n + 1 - m : 0;
        return ans;
    }



     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值