[LeetCode] 會把阿拉伯數字轉羅馬,你也轉得回來 #13 Roman to Integer

阿拉伯數字 -> 羅馬數字

回顧一下 LeetCode #12,我們觀察羅馬數字一定是從大排到小:


如果要轉回數字,我們按照顏色的區塊也是從大到小轉回來就好了。

要小心的陷阱

這題目其實一點都不 Easy,如果你從左到右讀字串,最容易遇到的問題就是:

如果你讀到一個字元 "I" 就很開心的以為這是 1,但其實他只是 "IV" = 4 的一部分怎麼辦?

所以大部分解答都花了很多力氣去處理這類的問題,看了 code 就會覺得很頭痛。

轉換的順序


Step    Symbol    Value
1       M         1000
2       CM        900
3       D         500
4       CD        400
5       C         100
6       XC        90
7       L         50
8       XL        40
9       X         10
10      IX        9
11      V         5
12      IV        4
13      I         1


如果我們按照上面這樣 Step 1~13 看 string 前面長的一樣就加上數字,不一樣就去下一個 step,永不回頭,會有什麼問題嗎?

前面我們說可能會搞混的像是 IV=4 和 I=1,可是 Step 12 先判斷最不合群的 "IV" = 4,失敗了 Step 13 再去判斷是不是 "I" = 1 或 "II" = 2 或 "III" = 3,就不可能會搞混 1~4 的數字。

其他更大的數字也是如此,你可以抓兩個容易搞混的羅馬數字試試看。

C++ Code

我們就按照 Step 1~13 慢慢判斷,程式碼就會長得像這樣:


struct RomanAndValue {
  std::string romanStr;
  int value = 0;
};

class Solution {
 public:
  int romanToInt(std::string s) {
    static const RomanAndValue TABLE[] = {
        {"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400}, {"C", 100},
        {"XC", 90},  {"L", 50},   {"XL", 40}, {"X", 10},   {"IX", 9},
        {"V", 5},    {"IV", 4},   {"I", 1},
    };

    int num = 0;
    for (const auto &item : TABLE) {
      while (s.substr(0, item.romanStr.length()) == item.romanStr) {
        num += item.value;
        s = s.substr(item.romanStr.length());
      }
    }
    return num;
  }
};


要用別的程式語言來做我相信也是非常容易。

萬用解法

上面這個解法其實和 LeetCode #12 的解法非常類似,我們來回顧一下:


struct RomanAndValue {
  std::string romanStr;
  int value = 0;
};

class Solution {
 public:
  std::string intToRoman(int num) {
    static const RomanAndValue TABLE[] = {
        {"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400}, {"C", 100},
        {"XC", 90},  {"L", 50},   {"XL", 40}, {"X", 10},   {"IX", 9},
        {"V", 5},    {"IV", 4},   {"I", 1},
    };

    std::string roman;
    for (const auto &item : TABLE) {
      while (num >= item.value) {
        roman += item.romanStr;
        num -= item.value;
      }
    }
    return roman;
  }
};


那我們可不可以把兩個解法合起來,變成 #12 #13 題都可以解?

把一模一樣的 TABLE 拿到外面去之後:


struct RomanAndValue {
  std::string romanStr;
  int value = 0;
};

static const RomanAndValue TABLE[] = {
    {"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400}, {"C", 100},
    {"XC", 90},  {"L", 50},   {"XL", 40}, {"X", 10},   {"IX", 9},
    {"V", 5},    {"IV", 4},   {"I", 1},
};

class Solution {
 public:
  std::string intToRoman(int num) {
    std::string roman;
    for (const auto &item : TABLE) {
      while (num >= item.value) {
        roman += item.romanStr;
        num -= item.value;
      }
    }
    return roman;
  }

  int romanToInt(std::string s) {
    int num = 0;
    for (const auto &item : TABLE) {
      while (s.substr(0, item.romanStr.length()) == item.romanStr) {
        num += item.value;
        s = s.substr(item.romanStr.length());
      }
    }
    return num;
  }
};


這程式碼拿去丟 #12 或 #13 結果都會過。

哇,我們真的是超偷懶的。

Submission 結果


這題速度和記憶體都不是關鍵,了解羅馬數字的閱讀方法之後,寫出最 clean 的程式碼才是我們想要的。

留言

此網誌的熱門文章

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

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

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