1. 程式人生 > >Google 面試題 10 | 最多有k個不同字元的最長子字串

Google 面試題 10 | 最多有k個不同字元的最長子字串

題目描述

給定一個字串,找到最長的包含最多k個不同字元的子串,輸出最長子串的長度即可。

Example:
給出字串”eceba”,k = 2
輸出答案3,最長包含最多2個不同字元的子串為”ece”。

官方題解(並沒有看懂):
最暴力的做法是窮舉所有可能的子串,一共有n^2級別個不同的子串,然後對於每一個子串統計有多少不同的字元,如果少於k個就更新答案。這是一個三重迴圈的窮舉,複雜度是O(n^3)。聰明的讀者肯定想到了第二重和第三重迴圈可以合併,因為之前的統計資訊可以繼續使用而不需要每一次重新統計。這樣的話窮舉子串的開始位置和結束位置,複雜度是O(n^2)。如果在面試時提出了這個演算法,面試官首先會表示認同,然後希望你進行優化。我們來看看如何進行優化。

在統計的過程中我們可以發現,當窮舉的開始位置改變時我們會選擇重新統計,但其實當開始位置改變時,我們之前記錄的大部分資訊都是有用的,我們只需在原有的統計結果上稍作修改。我們在兩層for迴圈的時候其實會發現我們內層的fo迴圈其實並不需要每次都再重複遍歷。比如外層for迴圈指標i每次向前移動變成i+1的時候,內層for迴圈的j沒必要每次都退回到i的位置,其實是可以保留在j的位置不變的。因為可以證明對於從i+1到j-1這些情況都不會出現重複的元素,所以我們只用考慮從i+1到j就可以了。

自己的程式碼:

/**
 *Author: xiaoran
 *Solution:
 *
 */
#include<iostream>
#include<cstdio> #include<string> #include<cstring> #include<cmath> #include<cstdlib> #include<time.h> #include<algorithm> #include<vector> #include<map> #include<set> #include<bitset> #include<fstream> #define LL long long using
namespace std; const int MAXN=1000005; int test1_algorithm(char *s,int k){//O(n^3) int len,m; len = strlen(s); m = 0; for(int i=0;i<len;i++){ for(int j=i;j<len;j++){ int p=0,a[26]={0}; for(int t=i;t<=j;t++){ a[s[t]-'a']++; } for(int t=0;t<26;t++){ if(a[t]) p++; } if(p<=k) m=max(m,j-i+1);//因為是從大區間到小區間的遍歷,第一次滿足的子串就是結果 } } return m; } int test2_algorithm(char *s,int k){//記錄上次的資訊 int len,m,j,i; len = strlen(s); m = 0; for(i=0;i<len;i++){ int p=0,a[26]={0}; for(j=i;j<len;j++){ a[s[j]-'a']=1; p=0; for(int t=0;t<26;t++){ if(a[t]) p++; } if(p<=k){ m = max(m,j-i+1);//去掉最後一個 } } } return m; } int test3_algorithm(char *s,int k){//O(n) int a[26]={0}; int m=0,len=strlen(s); int start,end; start=end=0; while(end<len){ a[s[end++]-'a']++; int p=0; for(int i=0;i<26;i++){ if(a[i]) p++; } if(p<=k){ m = max(m,end-start); } if(p>k) a[s[start++]-'a']--; } return m; } int main() { //freopen("E:/input.txt","r",stdin); //freopen("E:/output.txt","w",stdout); char s[]="aabcdefgggg"; int k=3; cout<<test1_algorithm(s,k)<<endl; cout<<test2_algorithm(s,k)<<endl; cout<<test3_algorithm(s,k)<<endl; return 0; }