Reverse a String
英字と数字で構成された文字列 s が与えられます。同じ文字を逆順に並べた新しい文字列を返してください。つまり、最後の文字を先頭に、最初の文字を末尾にします。大文字・小文字を含め、すべての文字をそのまま保持してください。
関数
- sstring
- 反転する文字列
- 戻り値string
- sの文字を逆順に
制約
1 ≤ s.length ≤ 104sに含まれるのは、英字(aからz、AからZ)と数字(0から9)のみです。
例
- 入力
- s = "Coddy2026"
- 出力
- "6202yddoC"
- 説明
Coddy2026を最後の文字から最初の文字まで読みます。6、2、0、2、次にy、d、d、o、そして最後に大文字のCです。
- 入力
- s = "noon"
- 出力
- "noon"
- 説明
noonは回文なので、逆から読んでも同じ単語です。外側のnが入れ替わり、次に2つのoが入れ替わります。
- 入力
- s = "Q"
- 出力
- "Q"
- 説明
- 1文字の文字列には入れ替える相手がないため、変更されずに戻ります。
提出時に隠しテスト+14件
発展問題
各単語の文字の順序はそのままにして、hello big world を world big hello に変えるように、文中の単語の順序を逆にするにはどうすればよいでしょうか?
ヒント
1つずつ開いてください。開くたびに少しずつ答えに近づきます。
インデックス
0の文字は、答えの最後に来ます。インデックスiの文字はどこに来ますか?インデックス
n-1-iに移動します。最初と最後の文字が入れ替わり、次に2番目と最後から2番目の文字が入れ替わる、というように中央に向かって続きます。文字列を文字の配列にコピーします。1つのインデックスを先頭に、もう1つを末尾に置き、2つの文字を入れ替えてから、インデックスが出会うまで両方を内側に移動します。その後、配列を再び文字列に結合します。
解説
各文字には決まった移動先があります。インデックス i にある文字は、インデックス n-1-i に移動します。その順序で文字を新しい文字列に書き込む方法と、両端からペアごとに入れ替える方法があります。面接でよく聞かれるのは入れ替えの方法です。同じ両端から進めるポインターの動きで、配列をその場で反転させたり、回文かどうかを確認したりできるからです。
後ろから文字をコピーする
考え方
s를 뒤집은 문자열은 s의 마지막 문자로 시작하고, 끝에서 두 번째 문자로 이어지며, 첫 번째 문자로 끝납니다. 그러므로 인덱스를 n-1에서 0까지 거꾸로 이동하며 각 문자를 만날 때마다 답에 추가하세요. Coddy2026의 경우 6, 2, 0, 2, y 등을 추가하면 6202yddoC가 됩니다.
각 문자를 한 번씩 읽고 한 번씩 쓰므로 작업량은 O(n)입니다. 답은 n개의 문자로 이루어진 두 번째 문자열이므로 추가 공간은 O(n)입니다.
문자를 어떻게 추가하는지가 중요합니다. 변경할 수 없는 문자열에 +로 문자 하나를 추가하면 매번 문자열 전체가 복사되며, n = 10^4일 때 약 5 × 10^7개의 문자가 복사됩니다. 문자를 리스트나 문자열 빌더에 모은 다음 마지막에 한 번만 결합하세요.
アルゴリズム
- 答えを格納する空のリストまたは文字列ビルダーを作成します。
iをn-1から0まで逆順にループします。- 答えに
s[i]を追加します。 - 答えを文字列に結合して返します。
def reverseString(s):
result = []
for i in range(len(s) - 1, -1, -1):
result.append(s[i])
return "".join(result)両端から2つのポインターで入れ替える
考え方
反転では、文字を外側から内側に向かってペアにします。最初の文字と最後の文字を入れ替え、次に2番目の文字と最後から2番目の文字を入れ替える、というように中央に向かって進みます。インデックス 0 にポインター left を置き、インデックス n-1 にポインター right を置いて、2つの文字を入れ替え、両方のポインターを内側に1つずつ進めます。
ポインターが一致するか、交差したら停止します。noon では、ポインターは 0 と 3 から始まり、次に 1 と 2 に進み、その後交差します。入れ替えは2回です。xYz のように長さが奇数の場合、中央の文字でポインターが一致します。この文字はすでに最終位置にあるため、触れることはありません。入れ替えのたびに2つの文字が最終位置に収まるので、n / 2 回の入れ替えで完了します。
入れ替え自体に必要なのは一時変数1つだけで、追加の領域は O(1) です。ほとんどの言語では文字列をその場で変更できないため、まず文字配列にコピーする必要があり、これには O(n) のコストがかかります。入力がすでに文字配列である面接の問題では、この方法なら追加メモリをまったく使わずに反転できます。
アルゴリズム
sを文字の配列にコピーします。left = 0、right = n-1に設定します。left < rightの間、leftとrightの位置にある文字を入れ替え、その後leftに1を加え、rightから1を引きます。- 配列を文字列に戻して返します。
def reverseString(s):
chars = list(s)
left, right = 0, len(chars) - 1
while left < right:
chars[left], chars[right] = chars[right], chars[left]
left += 1
right -= 1
return "".join(chars)
落とし穴と境界ケース
逆順にする処理は1行で書けそうですが、バグはループの境界条件や答えの組み立て方に潜んでいます。
leftをn-1までループさせること。中央を過ぎると、各ペアがもう一度入れ替わり、文字列は元に戻ってしまいます。left < rightで止めてください。- 逆方向のループを
n-1ではなくnから始めること。これでは末尾の次の位置を読み取ってしまいます。LuaとRでは、代わりにインデックスが1からnまでになります。 - 不変文字列に対して
result = result + chで答えを組み立てること。各ステップでそれまでの内容をすべてコピーするため、長い入力では線形の処理が二次の処理になってしまいます。 - Cで終端文字
'\0'を忘れること。nバイトのバッファでは1バイト足りません。n + 1を確保してください。 - 一時変数を使わずに入れ替えること。
chars[left] = chars[right]の後では、言語が両方の値を一度に入れ替えるのでない限り、元の左側の文字は失われます。
よくある質問4
文字列を反転する時間計算量は何ですか?
反転にはO(n)時間がかかります。すべての文字を新しい位置に移動する必要があり、それぞれを1回ずつ処理するためです。新しい文字列を作成すると、O(n)の追加領域が必要になります。文字がすでに変更可能な配列に入っている場合、2つのポインターを使った交換に必要な追加領域はO(1)だけです。
組み込みの reverse 関数を使わずに文字列を逆順にするにはどうすればよいですか?
文字を配列にコピーし、両端にポインターを1つずつ置いて、2つの文字を入れ替え、ポインターが出会うまで互いに向かって移動させます。または、最後のインデックスから最初のインデックスまでループし、各文字をビルダーに追加します。どちらの方法でも、1回の処理で文字列を反転できます。
文字列をその場で逆順にできますか?
文字が、C、Java、C#のchar配列、Pythonのリスト、C++のstd::stringなどの変更可能なバッファー内にある場合に限ります。Java、Python、JavaScriptなど多くの言語の文字列は不変なので、配列にコピーして、その中で入れ替え、新しい文字列を作成します。どちらの場合も、入れ替えの処理自体はその場で行われます。
2ポインターのループはなぜ真ん中で止まるのですか?
各スワップで2つの文字がそれぞれ最終位置に配置されるため、n / 2 回スワップすると、すべての文字が正しい位置に収まります。中央を過ぎて続けると、同じペアを逆方向にスワップしてしまい、処理が元に戻ります。長さが奇数の場合、中央の文字はすでに自身の鏡像インデックスにあるため、スワップは必要ありません。
似た問題
同じ考え方を使う問題です。2〜3問解くとパターンが身につきます。
Python
def reverseString(s):
# ここにコードを書いてくださいケース1
ケース2
ケース3
入力
s = "Coddy2026"
期待値
"6202yddoC"