1. 程式人生 > >演算法學習之尺取法

演算法學習之尺取法

  昨天看了道題目,大概是說給定一個整數S,求一個長度為n的序列(所有元素均為正整數)中總和不小於S的連續子序列的長度的最小值,如果不存在,則輸出0。這題的樸素思路是求出該序列所有的sum(i),然後利用類似 高中學過的數列知識a(n)=s(n)-s(n-1)的方法,將起點s從0開始列舉,列舉限制sum[s]+S<=sum[n],在列舉過程中,利用二分搜尋找到使序列和不小於S的結尾t的最小值,這樣解的時間複雜度為O(nlogn),是不是還有更快的方法呢?這裡通過閱讀資料,我學到了一種名為“尺取法”的方法。這種方法就是利用兩個下標(起點,終點 )的不斷放縮像蟲子伸縮爬行一樣來卡出一個最優解。這種演算法由於只需遍歷一遍就可以求出答案,所以時間複雜度就是O(n)。下面給出該演算法的虛擬碼和思路:

void worm_solve()
{
    int res=MAX;
    int s=0,t=0,sum=0;
    for(;;)
     {
      while(t<n && sum < S){
         sum+=a[t++];
        }
     if(sum<S) break;
     res=min(res,t-s);
     sum-=a[s++];
}
   if(res>n)
   {
    res=0;
 }
 printf("%d\n",res);
}

  就這麼短,說下個人理解,這個演算法的難點就是如何卡出最優解,首先需要找到第一次出現滿足條件的末端t的位置,因此從0開始讓蟲子的頭部t一直向前爬,尾部s保持不動,直到出現滿足條件的時候停下,這時大家會很容易的想到這個a[s]....a[t]]的序列中很可能會有許多"冗餘值",即這些值去掉後,序列的總和依然大於S,這時,我們就要讓蟲子的尾部開始移動,因為沒法確定移動長度,所以每次只需移動一個單位就好,每當尾部縮排1,就要從sum中去掉相應的縮排值,並再次判斷,當前的序列和與S的關係,如何滿足條件,則可以更新res,否則就會重新讓蟲子的頭部前進,就是這樣一伸一縮,像一個蟲子一樣,演算法求出了最優解。這個演算法的適用型別就是解決一些連續區間覆蓋類問題,看完這種解法,我猶如醍醐灌頂,再一次感受到了數學和演算法的美妙,也使我越來越熱愛演算法啦。