Jewels and Stones
文字列が2つ与えられます。jewels の各文字は宝石の種類を表し、文字の重複はありません。stones の各文字は、あなたが持っている石を1つ表します。持っている石のうち、宝石がいくつあるかを返してください。文字は大文字と小文字を区別します。"a" と "A" は異なる種類です。
関数
- jewelsstring
- 宝石として数えられる石の種類を、それぞれ1文字で
- stonesstring
- あなたが持っている石、それぞれに1文字
- 戻り値integer
- 宝石に含まれる文字を持つ石の数
制約
1 ≤ jewels.length ≤ 521 ≤ stones.length ≤ 104- どちらの文字列も、英字(小文字と大文字)のみを含んでいます。
jewelsの文字はすべて異なります。
例
- 入力
- jewels = "rR"stones = "rubyRRr"
- 出力
- 4
- 説明
- 宝石の種類は
rとRです。rubyRRrでは、石r、R、R、rが一致し、u、b、yは一致しないため、答えは4です。
- 入力
- jewels = "z"stones = "ZZZ"
- 出力
- 0
- 説明
- 宝石の種類は小文字の
zだけです。石はすべて大文字のZで、別の種類なので、どれも該当しません。
提出時に隠しテスト+12件
ヒント
1つずつ開いてください。開くたびに少しずつ答えに近づきます。
石が1個の場合、それが数に含まれるかどうかを決めるのはどんな質問ですか?
石ごとに「この文字は宝石か?」と尋ねます。この質問に定数時間で答えるには、どのデータ構造を使いますか?
jewelsの文字を集合に入れ、次にstonesを順に調べて、集合に含まれる文字をすべて数えます。大文字と小文字はそのままにします。
解説
石ごとに答えが1つ必要です。この文字は宝石でしょうか? 石ごとにjewels文字列を検索すると、同じ走査を何度も繰り返すことになります。宝石の文字を一度セットに入れれば、各石について1回検索するだけで済みます。
すべての石について、宝石をスキャンします
考え方
石を1個ずつ取り出します。それぞれの石について、jewelsを順に調べ、一致する最初の文字で止めます。一致したら、カウントに1を加えます。最初の例では、石uをrおよびRと比較しますが、一致するものがないため、何も加算されません。
宝石の文字はすべて異なるため、石が一致するのは最大でも1つです。そのため、最初に一致した時点で止められます。宝石ではない石は、それが分かるまで、すべての宝石の文字と比較する必要があります。
宝石の種類がj個、石がs個ある場合、比較回数は最大でj × s回です。ここではj ≤ 52なので、石が10^4個でも比較回数は約5 × 10^5回となり、スキャンは時間内に完了します。種類のリストが大きくなると無駄が目立ちます。石ごとに同じ検索を繰り返すからです。
アルゴリズム
countを0に設定します。- 各石について、
jewelsの各文字と比較します。 - 最初に一致した文字が見つかったら、
countに1を加算して、次の石に進みます。 countを返します。
def numJewelsInStones(jewels, stones):
count = 0
for stone in stones:
for jewel in jewels:
if stone == jewel:
count += 1
break # the kinds are distinct: no second match is possible
return count宝石をセットに入れましょう
考え方
「この文字は宝石か?」という質問は、同じ文字について尋ねるたびに答えが変わりません。そこで種類ごとに一度だけ答えられるよう、jewels の文字から集合を作ります。集合を使うと要素の有無を定数時間で調べられるため、石ごとの処理は走査ではなく1回の検索で済みます。
最初の例では、集合は {r, R} です。rubyRRr を順に調べると、検索結果は「はい、いいえ、いいえ、いいえ、はい、はい、はい」となり、宝石は4個です。集合の作成には j ステップ、走査には s ステップかかるため、合計は O(j + s) です。
集合に格納される文字は最大でも 52 個です。組み込みの集合がない言語では、文字コードを添字にしたフラグ配列で同じ処理ができます。
アルゴリズム
jewelsのすべての文字を含む集合を作成します。countを0に設定します。- 各石について、集合にその文字が含まれている場合は
countに1を加えます。 countを返します。
def numJewelsInStones(jewels, stones):
kinds = set(jewels)
count = 0
for stone in stones:
if stone in kinds:
count += 1
return count
落とし穴と境界ケース
アルゴリズムは1つのループで構成されます。誤答の原因は、文字の比較方法と数え方にあります。
- 大文字と小文字を区別しないこと。両方の文字列を小文字にすると、
zとZが一致し、2つ目の例は0ではなく3を返します。 - 宝石の種類ではなく、石の数を数えること。
rubyRRrには宝石が2種類ありますが、宝石の石は4個あります。同じものも含めて、石を1個ずつ数えます。 - 石のループ内で集合を作ること。石ごとに作り直すと、そのたびに
jステップかかり、走査のO(j × s)に戻ってしまいます。ループの前に一度だけ作りましょう。 - 引数を入れ替えること。集合には
jewelsを入れ、ループではstonesを順に処理する必要があります。役割を逆にすると、2つ目の例では宝石の種類z1つを石と照合して、やはり0になりますが、("a", "aaa")は3ではなく1を返します。
よくある質問3
「宝石と石」の時間計算量は何ですか?
セットを使う場合は O(j + s) です。jewels からセットを構築するのに j 回の処理が必要で、s 個の石それぞれについて定数時間の検索を1回行います。石ごとに jewels を走査すると O(j × s) になります。
「宝石と石」にハッシュセットを使う理由は?
それぞれの石について、文字が宝石かどうかを同じ方法で判定します。ハッシュセットなら定数時間で判定できますが、jewels 文字列を検索するには、その長さに比例した時間がかかります。セットの構築に一度だけ時間をかければ、その後の石すべてについて時間を節約できます。
セットを使わずに解けますか?
はい。文字は英字なので、文字コードをインデックスとする128または256個のフラグの配列を使えば、ハッシュ化なしで集合として機能します。宝石の各文字に印を付けてから、フラグが設定されている石の数を数えます。Rubyのstones.count(jewels)なら1回の呼び出しですべて処理できますが、フラグ配列を使うと内部で何が起きているかがわかります。
似た問題
同じ考え方を使う問題です。2〜3問解くとパターンが身につきます。
Python
def numJewelsInStones(jewels, stones):
# ここにコードを書いてくださいケース1
ケース2
入力
jewels = "rR" stones = "rubyRRr"
期待値
4