[LeetCode] 用圖片來解釋怎麼解 #14 Longest Common Prefix

這題真的很直覺垂直掃描一次就是最直覺又最有效率的解法。

來看 GIF

如果 strs 有三個 strings,那我們就從左到右一排一排慢慢掃:


要掃到什麼時候? 我們想要讓大家前面都長得一樣,所以就掃到:

  1. 遇到不合群的字元的時候
  2. 或是某個字串太短,早就超過他的尾巴了

所以前面最長的 substring 就會是出現分歧的前一刻:


來寫 C++ Code

一開始我們就先這樣寫:


class Solution {
 public:
  std::string longestCommonPrefix(std::vector<std::string> &strs) {
    const std::string &firstStr = strs.at(0);
    for (size_t i = 0; i < firstStr.length(); i++) {
      const char c = firstStr.at(i);
      for (size_t otherIndex = 1; otherIndex < strs.size(); otherIndex++) {
        const std::string &otherStr = strs.at(otherIndex);
        if (i >= otherStr.size() || otherStr.at(i) != c) {
          return firstStr.substr(0, i);
        }
      }
    }
    return firstStr;
  }
};


別忘記如果 function 要 return 東西,如果掃到最後跑出 for loop 外就代表大家都長一樣,那就隨便抓一個 strs 裡面的 string 回傳。

讓程式快一點

我們觀察一下上面的 Code 發現每次 for loop 中都要做 i >= otherStr.size() 這會不會讓程式變慢呢?

再看一下上面的 gif 我們會發現其實只要跑到最短的字串就好了,其他人再長都沒有用。

那我們可以先偷算一下最短的字串是誰,那我們 index 最多就跑到最短字串的長度就好:


#include <algorithm>

class Solution {
 public:
  std::string longestCommonPrefix(std::vector<std::string> &strs) {
    const std::string &firstStr = strs.front();
    const size_t minLength = getMinLength(strs);
    for (size_t i = 0; i < minLength; i++) {
      const char c = firstStr.at(i);
      for (size_t otherIndex = 1; otherIndex < strs.size(); otherIndex++) {
        const std::string &otherStr = strs.at(otherIndex);
        if (otherStr.at(i) != c) {
          return firstStr.substr(0, i);
        }
      }
    }
    return firstStr.substr(0, minLength);
  }

  size_t getMinLength(const std::vector<std::string> &strs) {
    size_t minLength = 200;
    for (const auto &str : strs) {
      minLength = std::min(str.length(), minLength);
    }
    return minLength;
  }
};


不過可能這題的測資都太短,你看他的 constraints 部分 vector 和 string 的長度限制都只到 200,所以我丟上去是沒有發現什麼差異啦,兩個版本都大概 4ms 左右:


進階優化

記得以前上 parallel programming 之類的課都有說過,每次都跑去存取位置差很遠的記憶體位置時,電腦就無法好好利用 caching 的功能。

像上面這種 vertical scanning 其實每次都跑去不同的 string 去抓一個字元 cache 就沒什麼用,還是要跑去記憶體讀資料。

更好的做法當然是把 vertical scanning 的框框裡面的字元都擺在很鄰近的記憶體位置,但可能測資要非常大這樣做才會有用吧,別忘了我們還要先花力氣把資料轉個方式擺放。

結語

一開始我還以為要解 common substring 嚇死,想說 Easy 的題目怎麼突然就這麼進階了。

看到 LeetCode 提供的 solutions 裡面擺了那麼多解法更是嚇到,這到底是怎樣,為什麼要花這麼多力氣寫這麼多 overkill 的解法,你的測資又那麼小。

後面那個 trie 解法你就擺到真的是 repeated query 的題目去就好了,擺在這題是想要讓面試者有成就感,面試官又有時間可以多秀一些花樣嗎?

留言

此網誌的熱門文章

[試算表] 追蹤台股 Google Spreadsheet (未實現損益/已實現損益)

[Side Project] 互動式教學神經網路反向傳播 Interactive Computational Graph

[插件] 在 Chrome 網頁做區分大小寫的搜尋