224. Basic Calculator

原始題目

給定字串包含 { '+', '-', ' ', '(', ')'} 以及數字,求出算式答案。

值得紀錄之處

處理太多條件分支

我覺得這題最麻煩的坑在於符號太多種了。如果用 if 來寫的話,會有太多條件分支( branch condition ),所以我一直很想把某些 branch 合在一起。比如說:

  1. +, - 後面跟著 (
  2. +, - 後面跟著數字
  3. +, - 後面是 , 而再之後還可以混合前兩項組合出更多種變化。

這樣乘起來情況非常多,也很難維護。如果可以忽略掉 +, -, 只在遇到數字和 ( 的時候,再往回檢查符號,就可以減少需要列舉的情況數量了。

處理空格: erase + remove_if

如果可以使得字串裡面完全沒有空格,那需要處理的情況也可以再更加精簡。 C++ 裡面有一套 erase + remove_if 組合技可以用:

s.erase(remove_if(s.begin(), s.end(), [](unsigned char ch) { return std::isspace(ch); }), s.end());

remove 系列 v.s. erase

這兩個我以前都一直搞不清楚差異,直到寫這題時認真去研究他們之間的差別並紀錄於此。

template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
  first = std::find(first, last, value);
  if (first != last)
    for(ForwardIt i = first; ++i != last; )
      if (!(*i == value))
        *first++ = std::move(*i);
  return first;
}

stack 處理變號

直覺上採用 stack 來判斷括號內區域要不要變號,進入括號就 push 一個新的符號,離開括號則 pop 最上面的符號,即可完成判斷。(如果有 *, / 會比較麻煩,要多處理四則運算優先度的問題,還好這題不用😅)

iterator 問題

似乎不能把他理解為按照大小排列的指標,紀錄我把他當作指標用時遇到的坑:

  1. 沒有 <= operator, 如果用以下的寫法會有問題。要寫成 != 才正統。

    for(auto it = obj.begin(); it <= obj.end(); obj++){
      // do something
    }
    
  2. iterator 沒有直接加上某個常數的運算,但是指標有這種 Syntactic sugar 可以自動根據型別決定要往前多少個 bytes.

    auto it = obj.begin();
    // auto it = it + 3; // 沒有這種運算
    std::advance(it, 3); // 好用的移動多步工具
    

如果之後能夠更深入了解 iterator 的底層實作,再來寫一篇紀錄我對於 iterator 的了解好了😆

Solution

原本想多了解 iterator, 可是寫完 AC 之後發現他有不少需要往前往後跳多格的操作,還是 index 的寫法可以比較簡短,因此最後又改回現在這樣了😛

// after parsing, put `it` represent the last digit of num
int parse_num(int& num_head, const string& s){
  int cnt = 0, sign = (num_head > 0 && s[num_head - 1] == '-') ? -1 : 1;
  for(; isdigit(s[num_head + cnt]); cnt++)
    ;
  int n = stoi(s.substr(num_head, cnt));
  num_head += (cnt - 1);
  return n * sign;
}

class Solution {
public:
  int calculate(string s) {
    s.erase(remove_if(s.begin(), s.end(), [](unsigned char ch) { return std::isspace(ch); }),
        s.end());
    stack<int> sign_stack{};
    int ret = 0, cur_sign = 1;
    for(auto it = 0; it < s.size(); it++)
      if(isdigit(s[it]))
        ret += parse_num(it, s) * cur_sign;
      else if(s[it] == '('){
        if(it>0 && s[it-1] == '-')
          sign_stack.push(-1);
        else
          sign_stack.push(1);
        cur_sign *= sign_stack.top();
      }
      else if(s[it] == ')'){
        cur_sign /= sign_stack.top();
        sign_stack.pop();
      }
    return ret;
  }
};