出處:勇幸|thinking (
題:從乙個字串中找到乙個連續子串,該子串中任何兩個字元不能相同,求子串的最大長度並輸出一條最長不重複子串。
本節從最直接的方法逐步優化,漸進探索了四種實現方式,並最終找到時間複雜度為o(n),輔助空間為常數的方案,內容如下:
==基本演算法 使用hash==
==dp方案==
==dp + hash 方案==
==dp + hash 優化方案==
基本演算法 使用hash
要求子串中的字元不能重複,判重問題首先想到的就是hash,尋找滿足要求的子串,最直接的方法就是遍歷每個字元起始的子串,輔助hash,尋求最長的不重複子串,由於要遍歷每個子串故複雜度為o(n^2),n為字串的長度,輔助的空間為常數hash[256]。**如下:
/* 最長不重複子串 設串不超過30
* 我們記為 lnrs
*/int maxlen;
int maxindex;
void output(char * arr);
/* lnrs 基本演算法 hash */
char visit[256];
void lnrs_hash(char * arr, int size)
else
break;}}
if((j == size) && (j-i > maxlen))
}output(arr);
}
dp方案
前面剛剛討論過最長遞增子串行的問題,咋一想就覺得二者有點類似,何不向dp方面想一下,為什麼說二者類似,在lis問題中,對於當前的元素,要麼是與前面的lis構成新的最長遞增子串行,要麼就是與前面稍短的子串行構成新的子串行或單獨構成新子串行;
同理,對於最長不重複子串,某個當前的字元,如果它與前面的最長不重複子串中的字元沒有重複,那麼就可以以它為結尾構成新的最長子串;如果有重複,且重複位置在上乙個最長子串起始位置之後,那麼就與該起始位置之後的稍短的子串構成新的子串或者單獨成乙個新子串。
舉個例子:例如字串「abcdeab」,第二個字元a之前的最長不重複子串是「abcde」,a與最長子串中的字元有重複,但是它與稍短的「bcde」串沒有重複,於是它可以與其構成乙個新的子串,之前的最長重複子串「abcde」結束;
再看乙個例子:字串「abcb」,跟前面類似,最長串「abc」結束,第二個字元b與稍短的串「c」構成新的串;
這兩個例子,可以看出些眉目:當乙個最長子串結束時(即遇到重複的字元),新的子串的長度是與第乙個重複的字元的下標有關的,如果該下標在上乙個最長子串起始位置之前,則dp[i] = dp[i-1] + 1,即上乙個最長子串的起始位置也是當前最長子串的起始位置;如果該下標在上乙個最長子串起始位置之後,則新的子串是從該下標之後開始的。
於是類似lis,對於每個當前的元素,我們「回頭」去查詢是否有與之重複的,如沒有,則最長不重複子串長度+1,如有,則考察上乙個子串起始位置與重複字元下標的關係,當然,如果dp使用o(n^2)的方案,則我們只需在內層迴圈遍歷到上乙個最長子串的起始位置即可,如下。
o(n^2)的dp方案,我們可以與lis的dp方案進行對比,是乙個道理的。**如下:
/* lnrs dp */
int dp[30];
void lnrs_dp(char * arr, int size)
else if(j == last_start) // 無重複
}if(dp[i] > maxlen)
}output(arr);
}
dp + hash 方案
上面的dp方案是o(n^2)的,之所以是n^2,是因為「回頭」去尋找重複元素的位置了,受啟發於最初的hash思路,我們可以用hash記錄元素是否出現過,我們當然也可以用hash記錄元素出現過的下標,既然這樣,在dp方案中,我們何不hash記錄重複元素的位置,這樣就不必「回頭」了,而時間複雜度必然降為o(n),只不過需要乙個輔助的常數空間visit[256],典型的空間換時間。
**如下:這樣遍歷一遍便可以找到最長不重複子串
/* lnrs dp + hash 記錄下標 */
void lnrs_dp_hash(char * arr, int size)
else
else
}if(dp[i] > maxlen)
}output(arr);
}
dp + hash 優化方案
寫到這裡,還是有些彆扭,因為輔助的空間多了,是不是還能優化,仔細看dp最優子問題解的更新方程:
dp[i] = dp[i-1] + 1;
dp[i-1]不就是更新dp[i]當前的最優解麼?這與最大子陣列和問題的優化幾乎同出一轍,我們不需要o(n)的輔助空間去儲存子問題的最優解,而只需o(1)的空間就可以了,至此,我們找到了時間複雜度o(n),輔助空間為o(1)(乙個額外變數與256大小的雜湊表)的演算法,**如下:
/* lnrs dp + hash 優化 */
void lnrs_dp_hash_impro(char * arr, int size)
else
else
}if(curlen > maxlen)
}output(arr);
}
最後給出輸出函式與測試用例:
/* 輸出lnrs */
void output(char * arr)
printf("the len of lnrs is %d\n",maxlen);
int i = maxindex;
while(maxlen--)
printf("\n");}
void main()
(全文完)
出處:勇幸|thinking (
最長不重複子串
演算法參考 找到乙個字串中的乙個連續子串,這個子串內不能有任何兩個字元是相同的,並且這個子串是符合要求的最長的。例如輸入 abcbef 輸出 cbef o n 的演算法,具體思路如下 以abcbef這個串為例,用乙個陣列pos記錄每個元素曾出現的下標,初始化為 1。從s 0 開始,依次考察每個字元,...
最長不重複子串
題目鏈結 給定乙個字串,找到最長的子串,要求該子串中沒有重複的字元。例如 字串abcabcbb的不含重複字元的 最長 子串為abc,長度為 3。而bbbbbb的不含重複字元的 最長 子串為b,長度為 1。輸入格式 輸入包含多行,每一行對應乙個長度不超過 100 的輸出,直到遇到結束符為止。每行依次輸...
最長不重複子串
用visit陣列記錄出現過的地方 dp的時候注意乙個問題就可以 abcdacbd來看看如果遇到重複的該怎麼處理 遇到第二個a前都沒有問題,第二次遇到a,然後肯定是更新a的位置的dp 4 4 0 4,第二次遇到c,更新c的位置為dp 5 5 2 3 然後問題來了,第二次遇到b,從第乙個b和第二個b之間...