1. 程式人生 > >程式設計師程式設計藝術-----第二十八 ~ 二十九章-----最大連續乘積子串、字串編輯距離

程式設計師程式設計藝術-----第二十八 ~ 二十九章-----最大連續乘積子串、字串編輯距離

               第二十八~二十九章:最大連續乘積子串、字串編輯距離

前言

    時間轉瞬即逝,一轉眼,又有4個多月沒來更新blog了,過去4個月都在幹啥呢?對的,今2013年元旦和朋友利用業餘時間一起搭了個方便朋友們找工作的程式設計面試演算法論壇:為學論壇http://www.51weixue.com/。最近則開始負責一款線上程式設計挑戰平臺:英雄會http://hero.pongo.cn/,包括其產品運營,出題審題,寫程式碼測試,制定比賽規則等等。

    前幾天跟百度的幾個朋友線下閒聊,聽他們說,百度校招群內的不少朋友在找工作的時候都看過我的blog,一聽當即便激起了自己重寫此blog的慾望,恰巧眼下陽春三月(雖說已是3月,奇妙的是,前兩天北京還下了一場大雪

),又是找工作的季節(相對於每年的9月份來說,3月則是一個小高潮),那就從繼續更新專為IT人員找工作時準備筆試面試的程式設計師程式設計藝術系列開始吧。

  • 第二十八章、最大連續乘積子串,
  • 第二十九章、字串編輯距離,

    這兩個問題皆是各大IT公司最喜歡出的筆試面試題,比如說前者是小米2013年校招筆試原題,而後者則更是反覆出現,如去年9月26日百度一二面試題,10月9日騰訊面試題第1小題,10月13日百度2013校招北京站筆試題第二 大道題第3小題,及去年10月15日2013年Google校招筆試最後一道大題皆是考察的這個字串編輯距離問題。

    OK,歡迎朋友們在本文下參與討論,如果線上編譯自己的程式碼(程式語言任選C/C++/Java/C#

),可以上英雄會提交你的程式碼,有任何問題,歡迎隨時不吝批評或指正,感謝。

第二十八章、最大連續乘積子串

題目描述:給一個浮點數序列,取最大乘積連續子串的值,例如 -2.5,4,0,3,0.5,8,-1,則取出的最大乘積連續子串為3,0.5,8。也就是說,上述陣列中,3 0.5 8這3個數的乘積3*0.5*8=12是最大的,而且是連續的。

提醒:此最大乘積連續子串與最大乘積子序列不同,請勿混淆,前者子串要求連續,後者子序列不要求連續。也就是說:最長公共子串(Longest CommonSubstring)和最長公共子序列(LongestCommon Subsequence,LCS)的區別:

  • 子串(Substring)是串的一個連續的部分,
  • 子序列(Subsequence)則是從不改變序列的順序,而從序列中去掉任意的元素而獲得的新序列;
    更簡略地說,前者(子串)的字元的位置必須連續,後者(子序列LCS)則不必。比如字串“ acdfg ”同“ akdfc ”的最長公共子串為“ df ”,而它們的最長公共子序列LCS是“ adf ”,LCS可以使用動態規劃法解決。

解答

    解法一、窮舉,所有的計算組合:
    或許,讀者初看此題,自然會想到最大乘積子序列問題類似於最大子陣列和問題:http://blog.csdn.net/v_JULY_v/article/details/6444021,可能立馬會想到用最簡單粗暴的方式:兩個for迴圈直接輪詢。

  1. double max=0;  
  2. double start=0;  
  3. double end=0;  
  4. for (int i=0;i<num;i++) {  
  5.     double x=arrs[i];  
  6.     for (int j = i+1; j < num; j++) {  
  7.         x*=arrs[j];  
  8.         if(x>max){  
  9.             max=x;  
  10.             start=arrs[i];  
  11.             end=arrs[j];  
  12.         }  
  13.     }  

     解法二、雖說類似於最大子陣列和問題,但實際上具體處理起來諸多不同。為什麼呢,因為乘積子序列中有正有負也還可能有0。我們可以把問題簡化成這樣:陣列中找一個子序列,使得它的乘積最大;同時找一個子序列,使得它的乘積最小(負數的情況)。因為雖然我們只要一個最大積,但由於負數的存在,我們同時找這兩個乘積做起來反而方便。也就是說,不但記錄最大乘積,也要記錄最小乘積。So,我們讓

  • maxCurrent表示當前最大乘積的candidate,
  • minCurrent反之,表示當前最小乘積的candidate,
  • 而maxProduct則記錄到目前為止所有最大乘積candidates的最大值。
以上用candidate這個詞是因為只是可能成為新一輪的最大/最小乘積)    由於空集的乘積定義為1,在搜尋陣列前,maxCurrent,minCurrent,maxProduct都賦為1。
假設在任何時刻你已經有了maxCurrent和minCurrent這兩個最大/最小乘積的candidates,新讀入陣列的元素x(i)後,新的最大乘積candidate只可能是maxCurrent或者minCurrent與x(i)的乘積中的較大者,如果x(i)<0導致maxCurrent<minCurrent,需要交換這兩個candidates的值。
    當任何時候maxCurrent<1,由於1(空集)是比maxCurrent更好的candidate,所以更新maxCurrent為1,類似的可以更新minCurrent。任何時候maxCurrent如果比最好的maxProduct大,更新maxProduct。
程式碼一
  1. template <typename Comparable>    
  2. Comparable maxprod( const vector<Comparable>&v)    
  3. {    
  4.     int i;    
  5.     Comparable maxProduct = 1;    
  6.     Comparable minProduct = 1;    
  7.     Comparable maxCurrent = 1;    
  8.     Comparable minCurrent = 1;    
  9.     //Comparable t;    
  10.     for( i=0; i< v.size() ;i++)    
  11.     {    
  12.         maxCurrent *= v[i];    
  13.         minCurrent *= v[i];    
  14.         if(maxCurrent > maxProduct)     
  15.             maxProduct = maxCurrent;    
  16.         if(minCurrent > maxProduct)    
  17.             maxProduct = minCurrent;    
  18.         if(maxCurrent < minProduct)    
  19.             minProduct = maxCurrent;    
  20.         if(minCurrent < minProduct)    
  21.             minProduct = minCurrent;    
  22.         if(minCurrent > maxCurrent)    
  23.             swap(maxCurrent,minCurrent);    
  24.         if(maxCurrent<1)    
  25.             maxCurrent = 1;    
  26.         //if(minCurrent>1)    
  27.         //    minCurrent =1;    
  28.     }    
  29.     return maxProduct;     
  30. }    
  1. pair<int,int> maxproduct(double *f,int n) { //返回最大乘積的起點終點  
  2. int R = 0, r = 0;   //最大最小區間的 起點  
  3. pair<int,int> ret = make_pair(0, 0);   //最大 最小的區間下標  
  4. double M = f[0], m = f[0], answer = f[0];     // 最大 最小值  
  5.     for (int i = 1; i < n; ++i) {  
  6.         double t0 = f[i] * M, t1 = f[i] * m;  
  7.         if (t0 > t1) {  
  8.             M = t0;  
  9.             m = t1;  
  10.         }  
  11.         else {  
  12.             int t = R;  
  13.             R = r;  
  14.             r = t;  
  15.             M = t1;  
  16.             m = t0;  
  17.         }  
  18.         if (M < f[i]) {  
  19.             M = f[i];  
  20.             R = i;  
  21.         }  
  22.         if (m > f[i]) {  
  23.             m = f[i];  
  24.             r = i;  
  25.         }  
  26.         if (answer < M) {  
  27.             answer = M;  
  28.             ret = make_pair(R, i);  
  29.         }  
  30.     }  
  31.     return ret;  
  32. }  

    解法三
    本題除了上述類似最大子陣列和的解法,也可以直接用動態規劃求解(其實,上述的解法一本質上也是動態規劃,只是解題所表現出來的具體形式與接下來的解法二不同罷了。這個不同就在於下面的解法二會寫出動態規劃問題中經典常見的DP方程,而解法一是直接求解)。具體解法如下:
    假設陣列為a[],直接利用動歸來求解,考慮到可能存在負數的情況,我們用Max來表示以a結尾的最大連續子串的乘積值,用Min表示以a結尾的最小的子串的乘積值,那麼狀態轉移方程為:
       Max=max{a, Max[i-1]*a, Min[i-1]*a};
       Min=min{a, Max[i-1]*a, Min[i-1]*a};
    初始狀態為Max[1]=Min[1]=a[1]。

    C/C++程式碼一,很簡潔的一小段程式碼:

  1. double func(double *a,const int n)  
  2. {  
  3.     double *maxA = new double[n];  
  4.     double *minA = new double[n];  
  5.     maxA[0] = minA[0] =a[0];  
  6.     double value = maxA[0];  
  7.     for(int i = 1 ; i < n ; ++i)  
  8.     {  
  9.         maxA[i] = max(max(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]);  
  10.         minA[i] = min(min(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]);  
  11.         value=max(value,maxA[i]);  
  12.     }  
  13.     return value;  
  14. }  

    C/C++程式碼二:

  1. /*  
  2.  給定一個浮點數陣列,有正有負數,0,正陣列成,陣列下標從1算起  
  3.  求最大連續子序列乘積,並輸出這個序列,如果最大子序列乘積為負數,那麼就輸出-1  
  4.  用Max[i]表示以a[i]結尾乘積最大的連續子序列  
  5.  用Min[i]表示以a[i]結尾乘積最小的連續子序列  因為有複數,所以儲存這個是必須的  
  6. */    
  7. void longest_multiple(double *a,int n){    
  8.  double *Min=new double[n+1]();    
  9.  double *Max=new double[n+1]();    
  10.  double *p=new double[n+1]();    
  11.  //初始化    
  12.  for(int i=0;i<=n;i++){    
  13.   p[i]=-1;    
  14.  }    
  15.  Min[1]=a[1];    
  16.  Max[1]=a[1];    
  17.  double max_val=Max[1];    
  18.  for(int i=2;i<=n;i++){    
  19.   Max[i]=max(Max[i-1]*a[i],Min[i-1]*a[i],a[i]);    
  20.   Min[i]=min(Max[i-1]*a[i],Min[i-1]*a[i],a[i]);    
  21.   if(max_val<Max[i])    
  22.    max_val=Max[i];    
  23.  }    
  24.  if(max_val<0)    
  25.   printf("%d",-1);    
  26.  else    
  27.   printf("%d",max_val);    
  28. //記憶體釋放    
  29.  delete [] Max;    
  30.  delete [] Min;    
  31. }  
  1. //答題英雄:danielqkj  
  2. using System;  
  3. public class Test   
  4. {  
  5.     void Max(double a, double b, double c)  
  6.     {  
  7.         double d = (a>b)?a:b;  
  8.         return (d>c)?d:c;      
  9.     }  
  10.     void Min(double a, double b, double c)  
  11.     {  
  12.         double d = (a>b)?b:a;  
  13.         return (d>c)?c:d;  
  14.     }  
  15.     public static void Main()  
  16.     {  
  17.         int n = Int32.parse(Console.readline());  
  18.         double[] a = new double[n];  
  19.         double maxvalue = a[0];  
  20.         double[] max = new double[n];  
  21.         double[] min = new double[n];  
  22.         double start, end;  
  23.         String[] s = Console.readline().split(' ');  
  24.         for (int i = 0; i < n; i++)  
  25.         {  
  26.             a[i] = Double.parse(s[i])  
  27.         }  
  28.         max[0] = a[0];  
  29.         min[0] = a[0];  
  30.         start = 0, end = 0;  
  31.         for (int i = 1; i < n; i++)  
  32.         {  
  33.             max[i]=Max(a[i], a[i]*max[i-1], a[i]*min[i-1]);  
  34.             min[i]=Min(a[i], a[i]*max[i-1], a[i]*min[i-1]);  
  35.             if (max[i] > maxvalue)  
  36.             {  
  37.                 maxvalue = max[i];  
  38.                 end = i;  
  39.             }  
  40.         }  
  41.         double mmm = maxvalue;  
  42.         while ( (mmm - 0.0) > 0.00001 )  
  43.         {  
  44.             start = end;  
  45.             mmm = mmm / a[start];  
  46.         }  
  47.         Console.Writeline(a[start] + " " + a[end] + " " + maxvalue);  
  48.     }  
  49. }  

變種

  此外,此題還有另外的一個變種形式,即給定一個長度為N的整數陣列,只允許用乘法,不能用除法,計算任意(N-1)個數的組合中乘積最大的一組,並寫出演算法的時間複雜度。

  我們可以把所有可能的(N-1)個數的組合找出來,分別計算它們的乘積,並比較大小。由於總共有N個(N-1)個數的組合,總的時間複雜度為O(N2),顯然這不是最好的解法。
  OK,以下解答來自程式設計之美
解法1

 解法2
  此外,還可以通過分析,進一步減少解答問題的計算量。假設N個整數的乘積為P,針對P的正負性進行如下分析(其中,AN-1表示N-1個數的組合,PN-1表示N-1個數的組合的乘積)。
1.P為0          那麼,陣列中至少包含有一個0。假設除去一個0之外,其他N-1個數的乘積為Q,根據Q的正負性進行討論:
Q為0
說明陣列中至少有兩個0,那麼N-1個數的乘積只能為0,返回0;
Q為正數
返回Q,因為如果以0替換此時AN-1中的任一個數,所得到的PN-1為0,必然小於Q
Q為負數
如果以0替換此時AN-1中的任一個數,所得到的PN-1為0,大於Q,乘積最大值為0。

     2.    P為負數

根據“負負得正”的乘法性質,自然想到從N個整數中去掉一個負數,使得PN-1為一個正數。而要使這個正數最大,這個被去掉的負數的絕對值必須是陣列中最小的。我們只需要掃描一遍陣列,把絕對值最小的負數給去掉就可以了。

      3.    P為正數

    類似地,如果陣列中存在正數值,那麼應該去掉最小的正數值,否則去掉絕對值最大的負數值。
    上面的解法採用了直接求N個整數的乘積P,進而判斷P的正負性的辦法,但是直接求乘積在編譯環境下往往會有溢位的危險(這也就是本題要求不使用除法的潛在用意),事實上可做一個小的轉變,不需要直接求乘積,而是求出陣列中正數(+)、負數(-)和0的個數,從而判斷P的正負性,其餘部分與以上面的解法相同。

    在時間複雜度方面,由於只需要遍歷陣列一次,在遍歷陣列的同時就可得到陣列中正數(+)、負數(-)和0的個數,以及陣列中絕對值最小的正數和負數,時間複雜度為O(N)。

第二十九章、字串編輯距離

題目描述:給定一個源串和目標串,能夠對源串進行如下操作:
   1.在給定位置上插入一個字元
   2.替換任意字元
   3.刪除任意字元
寫一個程式,返回最小運算元,使得對源串進行這些操作後等於目標串,源串和目標串的長度都小於2000。

提醒:上文前言中已經說過了,此題反覆出現,最近考的最多的是百度和Google的筆試面試經常考察。下圖則是2013年Google的校招試題原景重現:


解答

    解法一、    此題跟上面的最大連續乘積子串類似,常見的思路是動態規劃,下面是簡單的DP狀態方程:

  1. //動態規劃:    
  2. //f[i,j]表示s[0...i]與t[0...j]的最小編輯距離。    
  3. f[i,j] = min { f[i-1,j]+1,  f[i,j-1]+1,  f[i-1,j-1]+(s[i]==t[j]?0:1) }    
  4. //分別表示:新增1個,刪除1個,替換1個(相同就不用替換)。   

     編輯距離的定義和計算方法如下:
Given two strings A and B, edit A to B with the minimum number of edit operations:

  • a) .Replace a letter with another letter
  • b) .Insert a letter
  • c) .Delete a letter
    E.g.
A = interestingly    _i__nterestingly
B = bioinformatics   bioinformatics__
                     1011011011001111
Edit distance = 11
    Instead of minimizing the number of edge operations, we can associate a cost function to the
operations and minimize the total cost. Such cost is called edit distance. Instead of using string edit, in computational biology, people like to use string alignment.We use similarity function, instead of cost function, to evaluate the goodness of the alignment.
    E.g. of similarity function: match – 2, mismatch, insert, delete – -1.
Consider two strings ACAATCC and AGCATGC.
One of their alignment is

1.jpg

In the above alignment, space (‘_’) is introduced to both strings. There are 5 matches, 1
mismatch, 1 insert, and 1 delete.The alignment has similarity score 7.
    A_CAATCC
    AGCA_TGC
    Note that the above alignment has the maximum score.Such alignment is called optimal
alignment.String alignment problem tries to find the alignment with the maximum similarity
score!String alignment problem is also called global alignment problem.
Needleman-Wunsch algorithm
    Consider two strings S[1..n] and T[1..m].Define V(i, j) be the score of the optimal alignment
between S[1..i] and T[1..j].
Basis:
V(0, 0) = 0
V(0, j) = V(0, j-1) + d(_, T[j]):Insert j times
V(i, 0) = V(i-1, 0) + d(S, _):Delete i times
that is:

2.jpg

Example :

3.jpg

4.jpg

下面是程式碼,測試資料比較少,若有問題請指正:

  1. //[email protected] peng_weida  
  2. //實現程式碼如下:  
  3. //標頭檔案StrEditDistance.h  
  4. #pragma once  
  5. #include <string>  
  6. class CStrEditDistance  
  7. {  
  8. public:  
  9.     CStrEditDistance(std::string& vStrRow, std::string& vStrColumn);  
  10.     ~CStrEditDistance(void);  
  11.     int  getScore()    { return m_Score;   }  
  12.     int  getEditDis()  { return m_EditDis; }  
  13.     void setEditDis(int vDis) { m_EditDis = vDis; }  
  14.     void setScore(int vScore) { m_Score = vScore; }  
  15. private:  
  16.     void process(const std::string& vStrRow, const std::string& vStrColumn);  
  17.     int getMaxValue(int a, int b, int c)  
  18.     {  
  19.         if (a < b){ if (b < c) return c; return b; }  
  20.         else { if (b > c) return a; return a < c ? c : a; }  
  21.     }  
  22. private:  
  23.     int   m_EditDis;  
  24.     int   m_Score;  
  25. };  
  26. //原始檔StrEditDistance.cpp  
  27. #include "StrEditDistance.h"  
  28. #include <iostream>  
  29. #include <iomanip>  
  30. #define MATCH        2  
  31. #define MISS_MATCH   -1  
  32. #define INSERT       -1  
  33. #define DELETE       -1  
  34. CStrEditDistance::CStrEditDistance(std::string& vStrRow, std::string& vStrColumn)  
  35. {  
  36.     process(vStrRow, vStrColumn);  
  37. }  
  38. CStrEditDistance::~CStrEditDistance(void)  
  39. {  
  40. }  
  41. //FUNCTION:  
  42. void CStrEditDistance::process(const std::string& vStrRow, const std::string& vStrColumn)  
  43. {  
  44.     int editDis = 0;     //編輯距離  
  45.     int row = vStrColumn.length();    
  46.     int column = vStrRow.length();  
  47.     const int sizeR = row + 1;  
  48.     const int sizeC = column + 1;  
  49.     int **pScore = new int*[sizeR];  //二維指標  
  50.     for (int i = 0; i <= row; i++)  
  51.     pScore =