← 題庫 / Archive
2026-06-16 Daily Medium StringSimulation

3612. Process String with Special Operations I

題目 / Problem

中文:給你一個字串 s,由小寫英文字母以及三個特殊字元 *#% 組成。從左到右依序處理 s,逐步建立一個新字串 result

  • 遇到小寫字母:把它接到 result 的尾端。
  • 遇到 *:刪除 result 的最後一個字元(如果 result 非空)。
  • 遇到 #:把目前的 result 複製一份接在自己後面(長度變兩倍)。
  • 遇到 %:把目前的 result 反轉。

處理完所有字元後,回傳最終的 result

English: You are given a string s made of lowercase letters and the special characters *, #, %. Process s left to right, building a string result:

  • A lowercase letter is appended to result.
  • * removes the last character of result (if any).
  • # duplicates result (appends a copy of itself).
  • % reverses result.

Return the final result.

Constraints / 限制: - 1 <= s.length <= 20 - s 只包含小寫字母與 *#%

Example / 範例

Input:  s = "a#b%*"
Output: "ba"

a   -> "a"      (append 'a')
#   -> "aa"     (duplicate)
b   -> "aab"    (append 'b')
%   -> "baa"    (reverse)
*   -> "ba"     (remove last char)

名詞解釋 / Glossary

  • 模擬 / Simulation:照著題目描述的規則,一步一步真的去執行每個操作,而不是尋找數學公式。當操作次數不大時,這是最直接也最不易出錯的做法。 Just follow the rules literally, step by step, instead of looking for a clever formula. Best when the input is small.
  • 動態陣列 / Dynamic array:一個可以隨需要自動增長的陣列。在 C 裡我們用 malloc/realloc 手動管理;在 C++ 裡 std::string 就是一個會自動長大的字元陣列。 An array that grows as needed; std::string in C++ is exactly this for characters.
  • 長度欄位 / Length field(len:我們用一個整數變數記錄 result 目前實際有幾個字元。append 就 len++,刪除就 len--,不必每次重算。 An integer tracking how many characters result currently holds, so append/delete are just len++ / len--.
  • 就地反轉 / In-place reverse:用兩個索引,一個從頭、一個從尾,互換字元並往中間靠攏,不需要額外開一個新陣列。 Swap characters from both ends moving inward — no extra array needed.
  • malloc / freemalloc(n) 向系統要 n 位元組的記憶體並回傳一個指標;用完要 free 釋放。LeetCode 的 C 題要求你回傳一塊 malloc 出來的記憶體。 malloc(n) requests n bytes and returns a pointer; LeetCode expects you to return malloc'd memory.

思路

這題的關鍵觀察是:字串長度最多只有 20,而且每個 # 最多讓字串長度加倍。20 個字元裡就算全是 #,長度也不會超過 2^20(大約一百萬),完全在電腦能輕鬆處理的範圍內。所以我們不需要任何聰明的數學技巧,直接「照著題目說的做」就好——這就是模擬(simulation)。我們維護一個可變的字元緩衝區 result 和一個長度 len。從左到右掃過 s:是字母就寫到 result[len] 並把 len 加一;是 * 就在 len > 0 時把 len 減一(不必真的清除那個位元組,下次 append 會蓋過去);是 # 就把前 len 個字元再複製一份接到後面,長度變兩倍;是 % 就用頭尾雙索引就地反轉前 len 個字元。唯一要小心的是緩衝區要夠大:由於 # 會加倍,我們一開始就配置足夠大的空間(2^20 + 1)以容納最壞情況,避免越界。

The key observation is that the input length is at most 20, and each # at most doubles the string. Even 20 consecutive # operations cap the length at about 2^20 (~1 million), which is trivial for a computer. So no clever math is needed — we just do exactly what the problem says, a pure simulation. We keep a growable character buffer result plus a counter len. Scanning s left to right: a letter is written at result[len] and we increment len; a * decrements len only when len > 0 (we don't bother erasing the byte — a future append overwrites it); a # copies the current len characters onto the end, doubling the length; a % reverses the first len characters in place using two indices walking from the ends toward the middle. The one thing to be careful about is buffer size: because # doubles, we pre-allocate enough room (2^20 + 1) for the worst case so we never write out of bounds.

逐步走查 / Walkthrough

Input: s = "a#b%*". We track result (only the first len chars matter) and len.

Step char Operation 操作 result len
start 初始空字串 / empty "" 0
0 a append a / 接上 a a 1
1 # duplicate / 複製:把 a 再接一份 aa 2
2 b append b / 接上 b aab 3
3 % reverse / 反轉前 3 個字元 baa 3
4 * remove last / len--,最後的 a 被丟掉 ba 2

最後用 result[len] = '\0' 收尾,得到 "ba"。 Finally terminate with result[len] = '\0', giving "ba".

Solution — C

// 演算法:純模擬。維護一個夠大的字元緩衝區 result 與長度 len,
//        逐字處理 s:字母 append、'*' 刪尾、'#' 複製加倍、'%' 就地反轉。
// Algorithm: pure simulation. Keep a large enough buffer `result` and a length
//        `len`; for each char: letter -> append, '*' -> pop, '#' -> double, '%' -> reverse.

#include <stdlib.h>   // malloc / free
#include <string.h>   // strlen, memcpy

char* processStr(char* s) {
    int n = strlen(s);                       // n = s 的長度 / length of input s

    // '#' 最多把長度加倍;20 個字元最壞情況下長度可達 2^20。
    // '#' doubles length; with up to 20 chars the worst case is 2^20.
    // 多 +1 是為了結尾的 ''。 The +1 is room for the terminating ''.
    int cap = (1 << 20) + 1;                  // 1<<20 是位元左移,等於 2 的 20 次方 / 1<<20 == 2^20
    char* result = (char*)malloc(cap);        // 向系統要一塊記憶體 / request a buffer from the heap

    int len = 0;                              // result 目前的字元數 / current length of result

    for (int i = 0; i < n; i++) {             // 從左到右掃過 s / scan s left to right
        char c = s[i];                        // 取出第 i 個字元 / current character

        if (c == '*') {                       // 刪除最後一個字元 / remove last char
            if (len > 0) len--;               // 只在非空時減;不需真的清除位元組 / only if non-empty
        } else if (c == '#') {                // 複製整個 result 接在後面 / duplicate result
            // 把前 len 個字元複製到 result+len 起的位置 / copy first len chars onto the tail
            memcpy(result + len, result, len);// memcpy(dst, src, bytes):複製 len 個位元組 / copy len bytes
            len *= 2;                          // 長度變兩倍 / length doubles
        } else if (c == '%') {                // 就地反轉前 len 個字元 / reverse in place
            int l = 0, r = len - 1;           // 左右兩個索引 / two indices from the ends
            while (l < r) {                   // 往中間靠攏直到相遇 / move inward until they meet
                char t = result[l];           // 暫存左邊字元 / temp save left char
                result[l] = result[r];        // 左邊放右邊的值 / left gets right
                result[r] = t;                // 右邊放暫存的值 / right gets the temp
                l++; r--;                     // 兩端各往中間移一格 / step both indices inward
            }
        } else {                              // 否則是小寫字母 / otherwise it's a lowercase letter
            result[len] = c;                  // 寫到下一個空位 / write at the next free slot
            len++;                            // 長度加一 / length grows by one
        }
    }

    result[len] = '';                       // C 字串以 '' 結尾 / C strings are NUL-terminated
    return result;                            // 回傳這塊 malloc 的記憶體 / return the malloc'd buffer
}

Solution — C++

// 演算法:純模擬。用 std::string 當作會自動長大的緩衝區,逐字處理 s:
//        字母 push_back、'*' pop_back、'#' 自我串接加倍、'%' 用 std::reverse 反轉。
// Algorithm: pure simulation with std::string as a growable buffer; per char:
//        letter -> push_back, '*' -> pop_back, '#' -> append self, '%' -> std::reverse.

#include <string>      // std::string
#include <algorithm>   // std::reverse

class Solution {
public:
    std::string processStr(std::string s) {
        std::string result;                       // 自動長大的字元容器 / auto-growing char container

        for (char c : s) {                        // range-for:依序取出 s 的每個字元 / iterate each char of s
            if (c == '*') {                       // 刪除最後一個字元 / remove last char
                if (!result.empty())              // 只有非空時才能 pop / only when not empty
                    result.pop_back();            // pop_back 移除尾端字元 / removes the trailing char
            } else if (c == '#') {                // 複製自己接在後面 / duplicate result
                result += result;                 // string + string 串接:等同接上一份自己 / append a copy of itself
            } else if (c == '%') {                // 反轉整個 result / reverse the whole result
                // std::reverse(begin, end):就地反轉這段區間 / reverses the range in place
                std::reverse(result.begin(), result.end());
            } else {                              // 否則是小寫字母 / otherwise a lowercase letter
                result.push_back(c);              // push_back 在尾端加一個字元 / append one char
            }
        }

        return result;                            // 回傳最終字串 / return the final string
    }
};

複雜度 / Complexity

n = s.lengthL 為最終字串長度(最壞情況 L ≈ 2^n)。

  • Time: O(L) — 每個字元做常數工作,但 #(複製)和 %(反轉)都要碰到目前整個 result,其長度最大為 L。由於長度呈幾何級數增長,所有操作的總成本由最後幾次最大的操作主導,合計為 O(L)。 Each #/% touches the whole current string; geometric growth means the total is dominated by the largest operations, giving O(L). With n ≤ 20, L is at most ~10^6 — fast.
  • Space: O(L) — 我們需要存下整個 result,最大為 L 個字元。 We store the entire result, up to L characters.

Pitfalls & Edge Cases

  • 緩衝區太小 / Buffer too small (C)# 會讓長度加倍,若只配置 n+1 大小會嚴重越界寫入。我們直接配置 2^20+1 來涵蓋最壞情況。 # doubles length; allocating only n+1 overflows badly. We pre-size to 2^20+1 to cover the worst case.
  • 對空字串做 * / Popping an empty string:題目說「if it exists」,所以 len(或 result)為空時 * 什麼都不做。少了 len > 0 / !empty() 檢查,C 會把 len 變成負數導致崩潰。 * on empty does nothing; without the guard, len goes negative in C and crashes.
  • # 之後別忘了長度加倍 / Forgetting to double after #:複製字元後必須 len *= 2,只 memcpy 而不更新長度會讓後續操作讀到舊長度。 After copying you must update the length, or later ops use the stale value.
  • 反轉只反轉有效部分 / Reverse only the valid portion:在 C 裡反轉時用 len-1 當右界,不要去動緩衝區裡那些已被「刪除」但還殘留的舊位元組。 Reverse up to len-1 only — don't touch leftover bytes past the logical end.
  • 別忘了 '\0' / Don't forget the terminator (C):回傳前一定要 result[len] = '\0',否則印出的字串會接上一堆垃圾字元。 Always NUL-terminate before returning, or the printed string runs into garbage.