B - 文字列の整理 / Organizing Strings Editorial by admin
GPT 5.4 High概要
隣り合う同じ文字を 2 文字ずつ消していくとき、最終的に残る文字列の長さの最小値を求める問題です。
この問題は、文字列を左から順に見ながら「消えずに残っている文字」をスタックで管理すると、\(O(|S|)\) で解けます。
考察
操作では、隣り合う同じ文字 2 つを取り除けます。
例えば abba なら、
abbabbを消してaaaaを消して空文字列
となり、最終的な長さは \(0\) です。
重要な気づき
ある文字を左から順に見ていくとき、新しく見た文字 c が消える可能性があるのは、今の時点で一番右に残っている文字だけです。
なぜなら、途中の文字たちはすでに「消せるものは消した」状態にしておけば、新しい文字 c と隣り合う可能性があるのは、その残りの末尾だけだからです。
例で考える
例えば abbaca を左から処理してみます。
aを見る → 残り:abを見る → 残り:abbを見る → 末尾もbなので消す → 残り:aaを見る → 末尾もaなので消す → 残り: 空cを見る → 残り:caを見る → 末尾はcなので消せない → 残り:ca
最終的な長さは \(2\) です。
素朴な方法がだめな理由
毎回文字列全体を見て、aa や bb などを探して削除する、という方法も考えられます。
しかし、1 回削除するたびに文字列を詰め直して再探索すると、最悪で \(O(|S|^2)\) かかります。
今回は \(|S| \le 10^6\) なので、この方法では間に合いません。
どう解決するか
「今までに処理した部分の、消せるだけ消した後の文字列」をスタックで持ちます。
新しい文字 c を読むたびに:
- スタックの一番上が
cなら、隣り合う同じ文字になるのでpopして消す - そうでなければ
pushして残す
これを繰り返せば、常に「ここまでの最小形」を保てます。
アルゴリズム
- 空のスタックを用意する。
- 文字列 \(S\) を左から 1 文字ずつ見る。
- 現在の文字を
cとする。- スタックが空でなく、かつスタックの先頭(末尾)が
cと同じなら、2 文字を消す代わりにスタックから 1 文字popする。 - そうでなければ、
cをスタックにappendする。
- スタックが空でなく、かつスタックの先頭(末尾)が
- 最後にスタックに残っている文字数が答え。
なぜこれで正しいか
各時点でスタックには、「先頭からそこまでの部分文字列に対して、可能な限り操作をした後に残る文字列」が入っています。
新しい文字 c を追加したとき、
- 末尾が
cなら、その 2 文字は今まさに隣り合っているので消せます - 末尾が
cでないなら、cは今の時点では消せないので残すしかありません
つまり、その場での判断が常に最適です。
この処理を最後まで行えば、文字列全体に対する最小の残り長さが得られます。
計算量
- 時間計算量: \(O(|S|)\)
- 空間計算量: \(O(|S|)\)
各文字はスタックに高々 1 回 push され、高々 1 回 pop されるだけなので、全体で線形時間です。
実装のポイント
Python では、リストをスタックとして使うのが定番です。
stack.append(c)で積むstack.pop()で取り出すstack[-1]で一番上を確認する
今回のコードでは、文字列長が最大 \(10^6\) と大きいため、入力は sys.stdin.readline().strip() を使うと安全です。
ソースコード
import sys
S = sys.stdin.readline().strip()
stack = []
for c in S:
if stack and stack[-1] == c:
stack.pop()
else:
stack.append(c)
print(len(stack))
この解説は gpt-5.4-high によって生成されました。
posted:
last update: