• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

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


            前言

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

                前幾天跟百度的幾個朋友線下閑聊,聽他們說,百度校招群內的不少朋友在找工作的時候都看過我的blog,一聽當即便激起了自己重寫此blog的欲望,恰巧眼下陽春三月(雖說已是3月,奇妙的是,前兩天北京還下了一場大雪),又是找工作的季節(相對于每年的9月份來說,3月則是一個小高潮),那就從繼續更新專為IT人員找工作時準備筆試面試的程序員編程藝術系列開始吧。

                再者從去年4月份上傳的編程藝術前27章的PDF文檔的1.3萬下載量來看http://download.csdn.net/detail/v_july_v/4256339,此系列確確實實幫助了成千上萬的人。Yeah,本文講兩個問題,

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

                這兩個問題皆是各大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.     
            11.     for( i=0; i< v.size() ;i++)    
            12.     {    
            13.         maxCurrent *= v[i];    
            14.         minCurrent *= v[i];    
            15.         if(maxCurrent > maxProduct)     
            16.             maxProduct = maxCurrent;    
            17.         if(minCurrent > maxProduct)    
            18.             maxProduct = minCurrent;    
            19.         if(maxCurrent < minProduct)    
            20.             minProduct = maxCurrent;    
            21.         if(minCurrent < minProduct)    
            22.             minProduct = minCurrent;    
            23.         if(minCurrent > maxCurrent)    
            24.             swap(maxCurrent,minCurrent);    
            25.         if(maxCurrent<1)    
            26.             maxCurrent = 1;    
            27.         //if(minCurrent>1)    
            28.         //    minCurrent =1;    
            29.     }    
            30.     return maxProduct;     
            31. }    

                代碼二:思路,記錄以第i個結尾的最大乘積M和最小乘積m,并且記錄這兩個區間的起點(終點都是i),不斷更新,來源http://www.51weixue.com/thread-246-1-1.html

            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.            
            32.     }  
            33.     return ret;  
            34. }  

                解法三
                本題除了上述類似最大子數組和的解法,也可以直接用動態規劃求解(其實,上述的解法一本質上也是動態規劃,只是解題所表現出來的具體形式與接下來的解法二不同罷了。這個不同就在于下面的解法二會寫出動態規劃問題中經典常見的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. }  

                C#版完整代碼(代碼來自參加英雄會在線編程挑戰之1019、最大乘積連續子串:http://hero.pongo.cn/Question/Details?ID=19&ExamID=19的在線提交代碼的用戶):

            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.       
            11.     void Min(double a, double b, double c)  
            12.     {  
            13.         double d = (a>b)?b:a;  
            14.         return (d>c)?c:d;  
            15.     }  
            16.       
            17.       
            18.     public static void Main()  
            19.     {  
            20.         int n = Int32.parse(Console.readline());  
            21.         double[] a = new double[n];  
            22.         double maxvalue = a[0];  
            23.         double[] max = new double[n];  
            24.         double[] min = new double[n];  
            25.         double start, end;  
            26.           
            27.         String[] s = Console.readline().split(' ');  
            28.         for (int i = 0; i < n; i++)  
            29.         {  
            30.             a[i] = Double.parse(s[i])  
            31.         }  
            32.           
            33.         max[0] = a[0];  
            34.         min[0] = a[0];  
            35.         start = 0, end = 0;  
            36.           
            37.         for (int i = 1; i < n; i++)  
            38.         {  
            39.             max[i]=Max(a[i], a[i]*max[i-1], a[i]*min[i-1]);  
            40.             min[i]=Min(a[i], a[i]*max[i-1], a[i]*min[i-1]);  
            41.               
            42.             if (max[i] > maxvalue)  
            43.             {  
            44.                 maxvalue = max[i];  
            45.                 end = i;  
            46.             }  
            47.         }  
            48.           
            49.         double mmm = maxvalue;  
            50.         while ( (mmm - 0.0) > 0.00001 )  
            51.         {  
            52.             start = end;  
            53.             mmm = mmm / a[start];  
            54.         }  
            55.           
            56.         Console.Writeline(a[start] + " " + a[end] + " " + maxvalue);  
            57.           
            58.     }  
            59. }  

            變種

              此外,此題還有另外的一個變種形式,即給定一個長度為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.     
            3. //f[i,j]表示s[0...i]與t[0...j]的最小編輯距離。    
            4. 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) }    
            5.     
            6. //分別表示:添加1個,刪除1個,替換1個(相同就不用替換)。   

                解法二、本解法來自為學論壇:http://www.51weixue.com/thread-482-1-1.html

                 編輯距離的定義和計算方法如下:
            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. //copyright@ 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.    
            50.     int **pScore = new int*[sizeR];  //二維指針  
            51.     for (int i = 0; i <= row; i++)  
            52.     pScore = new int[sizeC];  
            53.    
            54.     //初始化第一行和第一列  
            55.     for (int c = 0; c <= column; c++)  
            56.         pScore[0][c] = 0 - c;  
            57.     for (int r = 0; r <= row; r++)  
            58.         pScore[r][0] = 0 - r;  
            59.    
            60.     //從v(1,1)開始每列計算  
            61.     for (int c = 1; c <= column; c++)  
            62.     {  
            63.         for (int r = 1; r <= row; r++)  
            64.         {  
            65.           //計算v(i,j)  
            66.           int valueMatch;  
            67.           if (vStrColumn[r-1] == vStrRow[c-1])  
            68.               valueMatch = MATCH;  
            69.           else  
            70.               valueMatch = MISS_MATCH;    
            71.           int A = pScore[r-1][c] + INSERT;  
            72.           int B = pScore[r][c-1] + DELETE;  
            73.           int C = pScore[r-1][c-1] + valueMatch;  
            74.           pScore[r][c] = getMaxValue(A, B, C);  
            75.         }  
            76.     }  
            77.    
            78.     //計算編輯距離  
            79.     int r = row, c = column;  
            80.     while(r > 0 && c > 0)  
            81.     {  
            82.         if (pScore[r][c]+1 == pScore[r-1][c])      { editDis++; r--; continue; }  
            83.         else if (pScore[r][c]+1 == pScore[r][c-1]) { editDis++; c--; continue; }  
            84.         else if (pScore[r][c]+1 == pScore[r-1][c-1]){ editDis++; r--; c--; continue; }  
            85.         else { r--; c--; }  
            86.     }  
            87.     if (r > 0 && c == 0)  editDis += r;  
            88.     else if (c > 0 && r == 0) editDis += c;  
            89.    
            90.     std::cout << std::endl;  
            91.     //----------------DEBUG-------------------//  
            92.     //打印數據  
            93.     for (int i = 0; i <= row; i++)  
            94.     {  
            95.          for (int j = 0; j <= column; j++)  
            96.          std::cout << std::setw(2) << pScore[j] << "  ";  
            97.          std::cout << std::endl;  
            98.     }  
            99.     std::cout << std::endl;  
            100.    
            101.     //設置編輯距離和得分  
            102.     setEditDis(editDis);  
            103.     setScore(pScore[row][column]);  
            104.    
            105.     for (int i = 0; i <= row; i++)   //釋放內存  
            106.     {  
            107.         delete pScore;  
            108.         pScore = NULL;  
            109.     }  
            110.     delete[] pScore;  
            111. }  

            類似

                上述問題類似于編程之美上的下述一題「以下內容摘自編程之美第3.3節」:

            許多程序會大量使用字符串。對于不同的字符串,我們希望能夠有辦法判斷其相似程度。我們定義了一套操作方法來把兩個不相同的字符串變得相同,具體的操作方法為:

            1. 修改一個字符(如把“a”替換為“b”);
            2. 增加一個字符(如把“abdd ”變為“aebdd ”);
            3. 刪除一個字符(如把“travelling”變為“traveling”)。
                比如,對于“abcdefg”和“abcdef ”兩個字符串來說,我們認為可以通過增加/減少一個“g”的方式來達到目的。上面的兩種方案,都僅需要一次操作。把這個操作所需要的次數定義為兩個字符串的距離,而相似度等于“距離+1”的倒數。也就是說,“abcdefg”和“abcdef”的距離為1,相似度為1 / 2 = 0.5。
            給定任意兩個字符串,你是否能寫出一個算法來計算出它們的相似度呢?

             這樣,很快就可以完成一個遞歸程序,如下所示:

            1. Int CalculateStringDistance(string strA, int pABegin, int pAEnd,    
            2.    string strB, int pBBegin, int pBEnd)     
            3. {    
            4.      if(pABegin > pAEnd)    
            5.      {    
            6.           if(pBBegin > pBEnd)    
            7.                return 0;     
            8.           else    
            9.      
            10.                return pBEnd – pBBegin + 1;    
            11.      }    
            12.     
            13.      if(pBBegin > pBEnd)    
            14.      {    
            15.           if(pABegin > pAEnd)    
            16.                return 0;    
            17.           else    
            18.                return pAEnd – pABegin + 1;    
            19.      }    
            20.     
            21.      if(strA[pABegin] == strB[pBBegin])    
            22.      {    
            23.           return CalculateStringDistance(strA, pABegin + 1, pAEnd,    
            24.             strB, pBBegin + 1, pBEnd);    
            25.      }    
            26.      else    
            27.      {    
            28.           int t1 = CalculateStringDistance(strA, pABegin, pAEnd, strB,     
            29.             pBBegin + 1, pBEnd);    
            30.           int t2 = CalculateStringDistance(strA, pABegin + 1, pAEnd,     
            31.             strB,pBBegin, pBEnd);    
            32.           int t3 = CalculateStringDistance(strA, pABegin + 1, pAEnd,    
            33.             strB,pBBegin + 1, pBEnd);    
            34.           return minValue(t1,t2,t3) + 1;    
            35.      }    
            36. }    
            上面的遞歸程序,有什么地方需要改進呢?問題在于:在遞歸的過程中,有些數據被重復計算了。

               我們知道適合采用動態規劃方法的最優化問題中的兩個要素:最優子結構和重疊子問題。另外,還有一種方法稱為備忘錄(memoization),可以充分利用重疊子問題的性質。

              下面簡述一下動態規劃的基本思想。和分治法一樣,動態規劃是通過組合子問題的解而解決整個問題的。我們知道,分治算法是指將問題劃分 成一睦獨立的子問題,遞歸 地求解各子問題,然后合并子問題的解而得到原問題的解。與此不同,動態規劃適用于子問題不是獨立 的情況,也就是各子問題包含公共的子子問題。在這種情況 下,若用分治法則會做許多不必要的工作,即重復地求解公共的子子問題。動態規劃算法對每個子子問題只求解一次,將其結果保存在一張表中,從而避免每次遇到 各個子問題時重新計算答案。

            動態規劃通常應用于最優化問題。此類問題可能有很多種可行解,每個解有一個值,而我們希望找出一個具有最優(最大或最小)值的解。稱這樣的解為該問題的“一個”最優解(而不是“確定的”最優解),因為可能存在多個取最優值的解。

              動態規劃算法的設計可以分為如下4個步驟:

              1)描述最優解的結構。

              2)遞歸定義最優解的值。

              3)按自底向上的方式計算最優解的值。

              4)由計算出的結果構造一個最優解。

              第1~3步構成問題的動態規劃解的基礎。第4步在只要求計算最優解的值時可以略去。如果的確做了第4步,則有時要在第3步的計算中記錄一些附加信息,使構造一個最優解變得容易。

              該問題明顯完全符合動態規劃的兩個要素,即最優子結構和重疊子問題特性。該問題的最優指的是兩個字符串的最短距離,子問題的重疊性可以從原書中的那個遞歸算法中看出。

              下面再來詳細說說什么是重疊子問題。適用于動態規劃求解的最優化問題必須具有的第二個要素是子問題的空間要“很小”,也就是用來解原問題的遞歸 算法可以反復地解同樣的子問題,而不是總在產生新的子問題。典型地,不同的子問題數是輸入規模的一個多項式。當一個遞歸算法不斷地調用同一問題時,我們說 該最優問題包含重疊子問題。相反地,適合用分治法解決的問題只往往在遞歸的每一步都產生全新的問題。動態規劃算法總是充分利用重疊子問題,即通過每個子問 題只解一次,把解保存在一個需要時就可以查看的表中,而每次查表的時間為常數。

            根據以上的分析,我寫了如下的動態規劃算法:

            復制代碼
            /*DP Algorithm
              * A loop method using dynamic programming.
              * Calculate from bottom to top.
             
            */
            int calculateStringDistance(string strA, string strB)
            {
                
            int lenA = (int)strA.length();
                
            int lenB = (int)strB.length();
                
            int c[lenA+1][lenB+1];
            // Record the distance of all begin points of each string
            //初始化方式與背包問題有點不同
                 for(int i = 0; i < lenA; i++) c[i][lenB] = lenA - i;
                
            for(int j = 0; j < lenB; j++) c[lenA][j] = lenB - j;
                 c[lenA][lenB]
            = 0;
                
            for(int i = lenA-1; i >= 0; i--)
                    
            for(int j = lenB-1; j >= 0; j--)
                     {
                        
            if(strB[j] == strA[i])
                             c[i][j]
            = c[i+1][j+1];
                        
            else
                             c[i][j]
            = minValue(c[i][j+1], c[i+1][j], c[i+1][j+1]) + 1;
                     }

                
            return c[0][0];
            }

            深入

            1. 詳細讀者朋友們也已經看到了,百度/Google經常喜歡出這個字符串編輯距離,實際上,關于這個“編輯距離”問題在搜索引擎中有著重要的作用,如搜索引擎關鍵字查詢中拼寫錯誤的提示,如下圖所示,當你輸入“Jult”后,因為沒有這個單詞“Jult”,所以搜索引擎猜測你可能是輸入錯誤,進而會提示你是不是找“July”:
              但這個拼寫錯誤檢查的原理是什么呢?Google是基于貝葉斯統計推斷的方法,相關原理詳情可以看下Google的研發總監Peter Norvig寫的這篇文章:http://norvig.com/spell-correct.html,以及fuanyif寫的這篇:http://www.ruanyifeng.com/blog/2012/10/spelling_corrector.html
            2. 關于什么是“編輯距離”:一個快速、高效的Levenshtein算法實現,這個是計算兩個字符串的算法,Levenshtein距離又稱為“編輯距離”,是指兩個字符串之間,由一個轉換成另一個所需的最少編輯操作次數。當然,次數越小越相似。這里有一個BT樹的數據結構,挺有意思的:http://blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees
            3. 最后,Lucene中也有這個算法的實現(我想,一般的搜索引擎一般都應該會有此項拼寫錯誤檢查功能的實現),下面是lucene的源碼(并沒有太多優化,與實際工程中java注重實用性的原則并不背離):
              1. public final class LevensteinDistance {  
              2.    
              3.     public LevensteinDistance () {  
              4.     }  
              5.       
              6. // Compute Levenshtein distance:   
              7. // see org.apache.commons.lang.StringUtils#getLevenshteinDistance(String, String)  
              8.       
              9.     public float getDistance (String target, String other) {  
              10.       char[] sa;  
              11.       int n;  
              12.       int p[];   
              13. //'previous' cost array, horizontally  
              14.       int d[];   
              15. // cost array, horizontally  
              16.       int _d[];   
              17. //placeholder to assist in swapping p and d  
              18.    
              19.         sa = target.toCharArray();  
              20.         n = sa.length;  
              21.         p = new int[n+1];   
              22.         d = new int[n+1];   
              23.          
              24.         final int m = other.length();  
              25.         if (n == 0 || m == 0) {  
              26.           if (n == m) {  
              27.             return 1;  
              28.           }  
              29.           else {  
              30.             return 0;  
              31.           }  
              32.         }   
              33.           
              34. // indexes into strings s and t  
              35.         int i;   
              36. // iterates through s  
              37.         int j;   
              38. // iterates through t  
              39.    
              40.         char t_j;   
              41. // jth character of t  
              42.    
              43.         int cost;   
              44. // cost  
              45.    
              46.         for (i = 0; i<=n; i++) {  
              47.             p[i] = i;  
              48.         }  
              49.    
              50.         for (j = 1; j<=m; j++) {  
              51.             t_j = other.charAt(j-1);  
              52.             d[0] = j;  
              53.    
              54.             for (i=1; i<=n; i++) {  
              55.                 cost = sa[i-1]==t_j ? 0 : 1;  
              56.                   
              57. // minimum of cell to the left+1, to the top+1, diagonally left and up +cost  
              58.                 d[i] = Math.min(Math.min(d[i-1]+1, p[i]+1),  p[i-1]+cost);  
              59.             }  
              60.    
              61.               
              62. // copy current distance counts to 'previous row' distance counts  
              63.             _d = p;  
              64.             p = d;  
              65.             d = _d;  
              66.         }  
              67.    
              68.           
              69. // our last action in the above loop was to switch d and p, so p now  
              70.           
              71. // actually has the most recent cost counts  
              72.         return 1.0f - ((float) p[n] / Math.max(other.length(), sa.length));  
              73.     }  
              74.    
              75. }  

            擴展

                當然,面試官還可以繼續問下去,如請問,如何設計一個比較兩篇文章相似性的算法?這個問題的討論可以看看這里:http://t.cn/zl82CAH。OK,字符串編輯距離這個問題實用性很強,限于篇幅,詳情讀者自己深入吧。


            精品久久久久久无码国产| 久久av免费天堂小草播放| 久久这里只有精品首页| 久久伊人五月天论坛| 热久久国产欧美一区二区精品| 久久亚洲国产午夜精品理论片| 久久99国产精品一区二区| 日本久久久精品中文字幕| 亚洲а∨天堂久久精品| 精产国品久久一二三产区区别| 久久香蕉国产线看观看精品yw| 日韩亚洲欧美久久久www综合网| 国产精品无码久久综合网| 久久久久久久综合狠狠综合| 国产一区二区精品久久| 久久综合亚洲色HEZYO国产| 久久久久无码精品国产不卡| 国产精品青草久久久久福利99 | 久久婷婷色综合一区二区| 久久精品青青草原伊人| 7国产欧美日韩综合天堂中文久久久久| 久久午夜无码鲁丝片午夜精品| 亚洲国产欧美国产综合久久| 久久se精品一区精品二区国产| 久久精品亚洲一区二区三区浴池| 久久久久亚洲精品天堂久久久久久 | 国产精品久久久久久影院| 亚洲а∨天堂久久精品| 国产香蕉97碰碰久久人人| 久久无码人妻一区二区三区午夜 | 久久久久青草线蕉综合超碰| 国内精品久久久久久久久| 久久久久久久亚洲Av无码| 久久WWW免费人成一看片| 日韩电影久久久被窝网| 久久久久99精品成人片牛牛影视| 久久精品国产亚洲av日韩| 人妻无码久久一区二区三区免费| 国产成年无码久久久免费| 性欧美丰满熟妇XXXX性久久久| 国产精品久久久久久久久久影院|