阿里實習生電面題目(續):輸出給定字符串的全部連續子串

      昨天晚上一回來,打開CSDN,看到這麼一篇文章《 阿里實習生電面題目:輸出給定字符串的全部連續子串》,看到阿里的面試題,於是好奇的進去看了看,看到好多牛人給出的解決方法,雖然我是那種技術渣,但還是可以看出他們的代碼究竟有沒有降低複雜度的(ps:看了好多,基本都是調用函數,簡化代碼,沒有降低複雜度)


      後來看到了10樓的最後一句話,猛然想到,這道題還應該考察去重複這個點(這篇文章沒有降低複雜度,只是去重的),的確去掉重複的字符串很麻煩,料想我等渣渣目測是想不出來了,於是滾去睡覺了,躺在牀上的時候,還是想了一下那道題(今天很早就起來了,不知道爲啥,然後又開始想)


       開始進入正題,一開始我是這樣想的,假設字符串aabcaa,先考慮第一個字符(a),顯然答案就是a,然後再加入第二個字符(a),包含第二個字符(a)的方法有1 a,2 aa(其實就是從後往前),但是a不行,前面出現過了,於是只有aa,然後在考慮第三個字符(b),包含第三個字符(b)的方法有1 b, 2 ab, 3 aab,沒有重複,都可以作爲子串......


        看到這裏,是不是想明白了什麼,對,關鍵就是加入一個字符的時候,去掉重複,這時就可用用到KMP算法中的next數組來去掉重複了,比如aabcaa的next數組爲[0,1,0,0,1,2],假設現在加入最後一個字符(a),包含a的話,有1 a , 2 aa, 3 caa, 4 bcaa, 5 abcaa, 6 aabcaa,由於最後一個字符的next中值爲2,表示 aabcaa的前綴和後綴最大相同的有兩個(aa),這個時候,用個矩陣m做下標記就行了,m[4][5] = false, m[5][5] = false(m[4][5]表示string[4]~string[5]有重複),如果我們標記好了所有的,就可以去掉重複,得出正確答案了,不過只是計算一次next數組是不行的,因爲aabcaa的next數組只是這個與字符串的前綴比較,如果baabcaa,next數組是無法判斷最後的aa與前面的aa重複了,不過解決方法很簡單,就是多次求next數組(複雜度爲O(n)),然後更新標記矩陣m,


#include <iostream>
#include <cstring>
#include <cmath>
#define MAX 10000
using std::cin;
using std::cout;
using std::string;
using std::endl;
int next[MAX] = {0};
bool m[MAX][MAX];
int total = 0;
string s;
//求next數組,因爲要多次求next數組 ,所以要給定起始點 (O(n))
void kmp_next(int start) {
  int len = s.length();
  int k = start;
  int count_ = 0;
  next[start] = 0;
  for (int i = start+1; i < len; i++) {
    if (s[k] != s[i])
      count_ = 0;
    if (s[k] == s[i]) {
      count_++;
      k++;
    }
    next[i] = count_;
  }
}
//做標記 (O(n^2))
void doMark() {
  int len = s.length();
  for (int i = 0; i < len; i++) {
    kmp_next(i);
    for (int j = i; j < len; j++) {
      if (next[j]) {
        //當next[j]不爲0的時候, k~j的屬於重複,這一段都得標記 
        int k = j - next[j] + 1;
        memset(&m[j][k], false, j-k+1);
      }
    }
  }
}
//打印結果 (O(n^2))
void printAllSubString() {
  int len = s.length();
  for (int i = 0; i < len; i++)
    for (int j = i; j < len; j++)
      if (m[j][i]) {
        total++;
        cout << s.substr(i, j-i+1) << endl;
      }
  cout << "total: " << total << endl;
}
int main() {
  cin >> s;
  memset(m, true, sizeof(m));
  doMark();
  printAllSubString();
  //system("pause");
  return 0;
}



後記:這個算法的複雜度爲O(n^2)(ps:還是說O(n^3)?,那個substr算不算O(n),那個第一個memset算不算O(n)?)(渣渣實在想不到更好的降低複雜的度的方法了),但是還是有一點可以改進的地方的,就是可以縮小標記矩陣的規模(這是上三角的矩陣,可以轉成一維數組,例如m[i][j]對應一維數組的下標爲(i+1)*i/2 + j,但是如果已知一維數組的下標,求二維數組的i,j我就沒想到了),歡迎提出改進意見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章