巢狀類遞迴

n1ce2cv發表於2024-10-06

巢狀類遞迴

  • 都需要一個全域性變數記錄當前正在處理的位置

基礎計算器 III

  • 含有巢狀的表示式求值,時間複雜度 O(n)
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    int where;

    int calculate(string &s) {
        where = 0;
        return recursive(s, 0);
    }

    // 計算 s 中從 i 位置開始到他對應的右括號結束的位置
    int recursive(string &s, int i) {
        int cur = 0;
        // 數字棧
        vector<int> numbers;
        // 符號棧
        vector<char> ops;

        while (i < s.length()) {
            if (s[i] >= '0' && s[i] <= '9') {
                // 解析數字
                cur = cur * 10 + s[i++] - '0';
            } else if (s[i] == '(') {
                // 遇到左括號,遞迴處理下一段表示式
                cur = recursive(s, i + 1);
                // 從 where + 1 的位置繼續解析
                i = where + 1;
            } else if (s[i] == ')') {
                // 遇到右括號就退出迴圈
                break;
            } else {
                // 遇到運算子,把解析好的數字和這個運算子入棧,cur 清零
                push(numbers, ops, cur, s[i++]);
                cur = 0;
            }
        }
        // 最後一個數字,由於遇不到下一個運算子,所以要手動入棧
        push(numbers, ops, cur, '+');
        // 已經處理到的位置
        where = i;
        // 計算這一對括號內的表示式
        return compute(numbers, ops);
    }

    void push(vector<int> &numbers, vector<char> &ops, int cur, char op) {
        int n = numbers.size();
        if (n == 0 || ops.back() == '+' || ops.back() == '-') {
            // 棧空或者棧頂是加減運算子,就直接放入
            numbers.push_back(cur);
            ops.push_back(op);
        } else {
            // 棧頂是乘除運算,先彈出一個數字運算後,把結果入棧
            int topNumber = numbers.back();
            char topOp = ops.back();
            if (topOp == '*') {
                numbers[n - 1] = topNumber * cur;
            } else {
                numbers[n - 1] = topNumber / cur;
            }
            ops[n - 1] = op;
        }
    }

    // 計算剩下的加減運算
    int compute(const vector<int> &numbers, const vector<char> &ops) {
        int n = numbers.size();
        int ans = numbers[0];
        for (int i = 1; i < n; i++)
            ans += ops[i - 1] == '+' ? numbers[i] : -numbers[i];
        return ans;
    }
};

int main() {
    string s = "6-4/2*(3-1)+(2+1)*(5-3*2+(4/2+2))";
    Solution solution;
    cout << solution.calculate(s);
}
  • 遞迴(推薦),index++ 跳過左括號,而不是用 where + 1 去跳過
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    // 處理到的位置
    int index;

    int calculate(string &s) {
        return recursive(s);
    }

    // 計算 s 中從 index 位置開始到他對應的右括號結束的位置
    int recursive(string &s) {
        int cur = 0;
        // 數字棧
        vector<int> numbers;
        // 符號棧
        vector<char> ops;

        for (; index < s.length(); index++) {
            if (s[index] >= '0' && s[index] <= '9') {
                // 解析數字
                cur = cur * 10 + s[index] - '0';
            } else if (s[index] == '(') {
                // 跳過左括號
                index++;
                // 遞迴處理下一段表示式
                cur = recursive(s);
            } else if (s[index] == ')') {
                // 遇到右括號就退出迴圈
                break;
            } else {
                // 遇到運算子,把解析好的數字和這個運算子入棧,cur 清零
                push(numbers, ops, cur, s[index]);
                cur = 0;
            }
        }
        // 最後一個數字,由於遇不到下一個運算子,所以要手動入棧
        push(numbers, ops, cur, '+');
        // 計算這一對括號內的表示式
        return compute(numbers, ops);
    }

    void push(vector<int> &numbers, vector<char> &ops, int cur, char op) {
        int n = numbers.size();
        if (n == 0 || ops.back() == '+' || ops.back() == '-') {
            // 棧空或者棧頂是加減運算子,就直接放入
            numbers.push_back(cur);
            ops.push_back(op);
        } else {
            // 棧頂是乘除運算,先彈出一個數字運算後,把結果入棧
            int topNumber = numbers.back();
            char topOp = ops.back();
            if (topOp == '*') {
                numbers[n - 1] = topNumber * cur;
            } else {
                numbers[n - 1] = topNumber / cur;
            }
            ops[n - 1] = op;
        }
    }

    // 計算剩下的加減運算
    int compute(const vector<int> &numbers, const vector<char> &ops) {
        int n = numbers.size();
        int ans = numbers[0];
        for (int j = 1; j < n; j++)
            ans += ops[j - 1] == '+' ? numbers[j] : -numbers[j];
        return ans;
    }
};

int main() {
    string s = "6-4/2*(3-1)+(2+1)*(5-3*2+(4/2+2))";
    Solution solution;
    cout << solution.calculate(s);
}

394. 字串解碼

  • 含有巢狀的字串解碼,時間複雜度 O(n)
  • 從第一個右括號開始解析,從裡向外解析每一對括號,解析成字串後入棧,繼續找下個右括號
#include <iostream>
#include <string>
#include <vector>
#include <stack>

using namespace std;

class Solution {
public:
    string decodeString(string s) {
        stack<char> stk;
        // 用於逆序輸出棧中內容
        deque<char> deq;

        for (int i = 0; i < s.size(); ++i) {
            // 右括號前的全部入棧
            while (i < s.size() && s[i] != ']') {
                stk.emplace(s[i]);
                i++;
            }

            // 已經到字串 s 的結尾了
            if (s[i] != ']') break;

            // 解析括號內的字串
            string tempStr;
            while (stk.top() != '[') {
                deq.emplace_front(stk.top());
                stk.pop();
            }
            while (!deq.empty()) {
                tempStr += deq.front();
                deq.pop_front();
            }
            // 彈出 '['
            stk.pop();

            // 解析左括號前的數字,也就是括號內字元的重複次數
            int count = 0;
            while (!stk.empty() && stk.top() >= '0' && stk.top() <= '9') {
                deq.emplace_front(stk.top());
                stk.pop();
            }
            while (!deq.empty()) {
                count *= 10;
                count += deq.front() - '0';
                deq.pop_front();
            }

            // 記錄重複 count 次的字串
            string repeated;
            for (int j = 0; j < count; ++j)
                repeated.append(tempStr);

            // 重新入棧
            for (auto &c: repeated)
                stk.emplace(c);
        }

        // 全部出棧
        string res;
        deq.clear();
        while (!stk.empty()) {
            deq.emplace_front(stk.top());
            stk.pop();
        }
        while (!deq.empty()) {
            res += deq.front();
            deq.pop_front();
        }

        return res;
    }
};
  • 用兩個棧分別記錄左括號之前的數字,以及數字前的字串。每次遇到右括號就把括號內重複相應次數,然後與這個數字之前的字串拼接
class Solution {
public:
    // "abc2[a2[c]t]2[k]xyz";
    // abcacctacctkkxyz
    string decodeString(const string &s) {
        // 臨時記錄數字
        int multi = 0;
        // 臨時記錄數字前的字串
        string str;
        stack<int> stack_multi;
        stack<string> stack_str;

        for (char c: s) {
            if (c == '[') {
                // 遇到左括號,說明左括號前的數字已經被確定,存入棧中
                stack_multi.emplace(multi);
                // 數字之前的字串也確定了,存入棧中
                stack_str.emplace(str);
                // 清空這兩個臨時變數
                multi = 0;
                str.clear();
            } else if (c == ']') {
                // 取出當前括號內字串應該重複的次數
                int cur_multi = stack_multi.top();
                stack_multi.pop();
                // 重複對應的次數後記錄到tmp中
                string tmp;
                for (int i = 0; i < cur_multi; i++) tmp += str;
                // 再接到之前數字前已經出現的字串後面
                str = stack_str.top() + tmp;
                stack_str.pop();
            } else if (c >= '0' && c <= '9') {
                // 確定重複次數
                multi = multi * 10 + (c - '0');
            } else {
                // 記錄數字前的字串,或者是括號內的字串
                str.push_back(c);
            }
        }
        return str;
    }
};
  • 遞迴
#include <string>

using namespace std;

class Solution {
public:
    int where;

    string recursive(string s, int i) {
        string str;
        int multi = 0;
        while (i < s.length() && s[i] != ']') {
            if (s[i] >= '0' && s[i] <= '9') {
                // 解析重複次數
                multi = 10 * multi + s[i++] - '0';
            } else if (s[i] == '[') {
                // 遞迴處理下一段
                string repeatedStr = recursive(s, i + 1);
                // 重複 multi 次,並且接在 str 後面
                while (multi > 0) {
                    str += repeatedStr;
                    multi--;
                }
                i = where + 1;
            } else {
                // 解析數字前的字串,或者括號內的字串
                str += s[i++];
            }
        }
        where = i;
        return str;
    }

    string decodeString(string s) {
        where = 0;
        return recursive(s, 0);
    }
};
  • 遞迴(推薦),index++ 跳過左括號,而不是用 where + 1 去跳過
#include <string>

using namespace std;

class Solution {
public:
    // 當前處理位置的下標
    int index = 0;

    string decodeString(string s) {
        string str;
        int multi = 0;
        for (; index < s.size(); index++) {
            if (s[index] >= '0' && s[index] <= '9') {
                // 解析重複次數
                multi = 10 * multi + s[index] - '0';
            } else if (s[index] == '[') {
                // 跳過這個左括號
                index++;
                // 遞迴處理括號內的字串
                string repeatedStr = decodeString(s);
                // 重複 multi 次,並且接在 str 後面
                while (multi > 0) {
                    str += repeatedStr;
                    multi--;
                }
            } else if (s[index] == ']') {
                // 結束遞迴,返回括號內的字串
                break;
            } else {
                // 解析數字前的字串,或者括號內的字串
                str += s[index];
            }
        }
        return str;
    }
};

726. 原子的數量

  • 含有巢狀的分子式求原子數量,時間複雜度 O(n)
  • 處理過程
    • 遇到字母:解析出元素符號,以及該元素出現次數
    • 遇到左括號 (:遞迴處理這段括號的分子式,從左括號一直到右括號後面的數字
    • 遇到右括號 ):如果右括號後面是數字,則需要乘對應倍數
#include <string>
#include <iostream>
#include <map>

using namespace std;

class Solution {
public:
    // 分子式中處理到的下標
    int index;

    bool isNumber(char ch) {
        return ch >= '0' && ch <= '9';
    }

    bool isLowercaseLetter(char ch) {
        return ch >= 'a' && ch <= 'z';
    }

    bool isUppercaseLetter(char ch) {
        return ch >= 'A' && ch <= 'Z';
    }

    // 返回原子出現次數
    map<string, int> recursive(string &formula) {
        // 有序 map
        map<string, int> freq;

        while (index < formula.length()) {
            if (isUppercaseLetter(formula[index])) {
                // 化學元素符號由一個大寫字母或者一個大寫字母加若干個小寫字母確定
                string atom = "";
                atom += formula[index++];
                // 先生成完整的元素符號,如果後面是小寫字母就追加上去
                while (index < formula.length() && isLowercaseLetter(formula[index]))
                    atom += formula[index++];

                // 判斷這個元素符號出現的次數
                int cnt = 0;
                if (index < formula.length() && isNumber(formula[index])) {
                    while (index < formula.length() && isNumber(formula[index]))
                        cnt = cnt * 10 + formula[index++] - '0';
                } else {
                    // 只出現一次
                    cnt = 1;
                }
                freq[atom] += cnt;
            } else if (formula[index] == '(') {
                index++;
                // 遞迴處理這段括號的分子式,從左括號一直到右括號後面的數字
                map<string, int> tempCount = recursive(formula);
                // 累加出現次數
                for (const auto &item: tempCount)
                    freq[item.first] += item.second;
                // 結束後 index 為下一個非數字的字元的下標,繼續從 index 開始處理
            } else if (formula[index] == ')') {
                index++;
                // 如果右括號後面是數字,則需要乘對應倍數
                if (index < formula.length() && isNumber(formula[index])) {
                    int cnt = 0;
                    while (index < formula.length() && isNumber(formula[index]))
                        cnt = cnt * 10 + formula[index++] - '0';
                    for (auto &item: freq)
                        freq[item.first] = item.second * cnt;
                }
                // 返回這一段分子式的元素統計
                return freq;
            }
        }
        return freq;
    }

    string countOfAtoms(string formula) {
        index = 0;
        map<string, int> count = recursive(formula);
        string res;
        // 從有序 map 中生成結果字串
        for (const auto &item: count) {
            res.append(item.first);
            if (item.second != 1)
                res.append(to_string(item.second));
        }
        return res;
    }
};

相關文章