問題
文字列 s をs のtrue、false を
- 英字の
大文字と 小文字は 区別しません。 Aとaは同じ 文字と 見なします - 空白や
記号など、 英字と 数字の ほかの 文字は 読み飛ばします - 英字と
数字が 1 つも 無い ときは trueを返します sの長さは 0 以上 10000 以下で、 ASCII の 文字 (半角の 英字、 数字、 空白、 記号) だけで できています
入力と出力の例
例 1
英字だけを
入力
s = "Top spot!"出力
true例 2
1a2 を
入力
s = "1-a-2"出力
false例 3
英字も
入力
s = "?! .,"出力
true面接での聞かれ方
面接では、
- 「文字列が
回文か どうかを 判定してください。 記号は 無視して、 大文字小文字も 区別しません」 - 書き終えた
あとに 「余分な 記憶を 使わずに 書けますか?」と 続けて 聞かれます - 「英数字が 1 つも
無い ときは?」と、 端の 場合を 聞かれる こともあります
どの
まず 考える手順
段 1
まずは
段 2
写しを
段 3
指がtrue を
ここから解答です。先に自分で書いてから読むと、考え方が残ります。
解答
Python
Python
def is_palindrome(s):
def ok(ch):
if not ch.isascii():
return False
return ch.isalnum()
def low(k):
return s[k].lower()
i = 0
j = len(s) - 1
while i < j:
if not ok(s[i]):
i += 1
elif not ok(s[j]):
j -= 1
elif low(i) != low(j):
return False
else:
i += 1
j -= 1
return TrueJavaScript
JavaScript
function isPalindrome(s) {
const re = /[A-Za-z0-9]/;
const ok = (c) => re.test(c);
const low = (k) => {
return s[k].toLowerCase();
};
let i = 0;
let j = s.length - 1;
while (i < j) {
if (!ok(s[i])) {
i++;
continue;
}
if (!ok(s[j])) {
j--;
continue;
}
if (low(i) !== low(j)) {
return false;
}
i++;
j--;
}
return true;
}英字とtrue にi < j がtrue を
計算量
- 時間は O(n) です。
どの 繰り返しでも、 2 つの 指の どちらかが 必ず 1 つ以上寄るので、 合わせて n 回ほどで 終わります - 使う
記憶は O(1) です。 持つのは 2 つの 位置だけで、 写しの 文字列を 作りません - 段 1 の、
写しを 作って 逆と 比べる 書き方は、 時間は O(n) で 同じですが、 記憶を O(n) 使います
よく ある間違い
- 大文字と
小文字を そろえずに比べる。 例 1 の Tとtを別の 文字と 見て、 falseを返してしまいます。 比べる 直前に 両方を 小文字にします - 読み飛ばすときに
両方の 指を進める。 片方だけが 記号を 指しているのに 両方を 進めると、 比べるはずの 文字を 飛ばしてしまいます。 進めるのは 記号を 指している 指だけです - Python の
isalnumだけで英数字を 判定する。 isalnumは ASCII のほかの 文字、 たとえば 全角の 数字にも Trueを返します。 この 問題は ASCII の 範囲だけを 見るので、 isasciiと組み合わせます
面接での 話し方の例
書き始める
「英数字だけを
小文字で 取り出して 逆と 比べれば 解けますが、 写しの ために 記憶を O(n) 使います。 両端に 指を 置いて、 英数字でない 文字は その指だけ 進め、 英数字どうしなら 小文字に して 比べながら寄せていけば、 記憶は O(1) で 済みます。 時間は どちらも O(n) です。」
書き終えたら、
「例 2 の
"1-a-2"でたどります。 左の 指は 1、 右の 指は 2 を 指していて、 どちらも 英数字です。 小文字に しても 1 と 2 で 違うので、 そこで false を 返します。」
