Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions 392.Is-Subsequence/benchmark_sols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import importlib.util
import random
import string
import time
from pathlib import Path

DIR = Path(__file__).resolve().parent


def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(mod)
return mod


def is_sub(sol, s: str, t: str) -> bool:
return sol.isSubsequence(s, t)


def _ref_subsequence(s: str, t: str) -> bool:
if not s:
return True
i = 0
for c in t:
if c == s[i]:
i += 1
if i == len(s):
return True
return False


def _random_string(n: int) -> str:
return "".join(random.choices(string.ascii_lowercase, k=n))


def main() -> None:
mod1 = load_module("sol1_impl", DIR / "sol1.py")
mod3 = load_module("sol3_impl", DIR / "sol3.py")
sol1 = mod1.Solution()
sol3 = mod3.Solution()

random.seed(0)
cases: list[tuple[str, str]] = [
("", "abc"),
("abc", ""),
("abc", "ahbgdc"),
("axc", "ahbgdc"),
("", ""),
("a" * 100, "a" * 200 + "b" * 200),
]
for _ in range(200):
la, lb = random.randint(0, 500), random.randint(0, 2000)
cases.append((_random_string(la), _random_string(lb)))

for s, t in cases:
is_sub(sol1, s, t)
is_sub(sol3, s, t)

reps = 50
t0 = time.perf_counter()
for _ in range(reps):
for s, t in cases:
is_sub(sol1, s, t)
t_sol1 = time.perf_counter() - t0

t0 = time.perf_counter()
for _ in range(reps):
for s, t in cases:
is_sub(sol3, s, t)
t_sol3 = time.perf_counter() - t0

print(f"sol1: {t_sol1 * 1000:.3f} ms")
print(f"sol3: {t_sol3 * 1000:.3f} ms")
ratio = t_sol3 / t_sol1
print(f"(sol3 / sol1: {ratio:.3f})")


if __name__ == "__main__":
main()
33 changes: 33 additions & 0 deletions 392.Is-Subsequence/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 392. Is Subsequence

sとtに対してそれぞれindexを保存して最後まで一致させられるかどうかを判定する: sol1.py
- 時間計算量 O(|t|)、空間計算量 O(1)

書き終わって early return が可能なことに気が付く。index の取り方も変えた: sol2.py
indexの取り方はもとのままの場合にも書く: sol3.py 添字の取り方が分かりやすいのでこちらの方が良さそう
- 時間計算量 O(|t|)、空間計算量 O(1)
- 漸近計算量では差がないが、early returnがある影響でこちらの方が早い

https://discord.com/channels/1084280443945353267/1201211204547383386/1231637671831408821

> これは、正規表現で s の文字のすべての間に .* を挟み込んで、マッチすればいいので、一回舐めれば解けそうですね

> これは、本当はエスケープしないといけない

正規表現を使ってかいてみる: sol4.py

https://discord.com/channels/1084280443945353267/1225849404037009609/1243290893671465080

関数型として書き、ループに直している
再帰で書いてみる: sol5.py





### 追記
sol3.pyとsol1.pyの速度はそれほど変わらず、sol3.pyの方が遅い場合もあった

sol1: 1532.053 ms
sol3: 1710.168 ms
(sol3 / sol1: 1.116)
17 changes: 17 additions & 0 deletions 392.Is-Subsequence/sol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s:
return True
if not t:
return False

index_s = 0
index_t = 0
while index_t < len(t):
if s[index_s] == t[index_t]:
index_s += 1
if index_s == len(s):
return True
Comment on lines +13 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは、index_sをインクリメントした時以外は判定不要なので、上のifの中に入れた方が良さそうです。

index_t += 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

毎回のiterationで、+1するので、forループで書き換えた方が良さそうです。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、どちらもご指摘の通りです。自分でも見直してこうした点に気づけるようになりたいです。


return False
15 changes: 15 additions & 0 deletions 392.Is-Subsequence/sol1_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s:
return True
if not t:
return False

index_s = 0
for index_t in range(len(t)):
if s[index_s] == t[index_t]:
index_s += 1
if index_s == len(s):
return True

return False
17 changes: 17 additions & 0 deletions 392.Is-Subsequence/sol2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s:
return True
if not t:
return False

remaining_num_s = len(s)
remaining_num_t = len(t)
while remaining_num_s <= remaining_num_t:
if s[len(s) - remaining_num_s] == t[len(t) - remaining_num_t]:
remaining_num_s -= 1
if remaining_num_s == 0:
return True
remaining_num_t -= 1

return False
17 changes: 17 additions & 0 deletions 392.Is-Subsequence/sol3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s:
return True
if not t:
return False

index_s = 0
index_t = 0
while len(s) - index_s <= len(t) - index_t:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

制約を見ると、sは短めなので、early returnするメリットはあまりないかもしれませんね。

0 <= s.length <= 100
0 <= t.length <= 104

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

速度を簡単に測ってみましたが大きな差はなく sol3.pyの方が遅い場合もありました。
高速化したつもりがそうなっていない場合もあるんですね。

sol1: 1532.053 ms
sol3: 1710.168 ms
(sol3 / sol1: 1.116)

if s[index_s] == t[index_t]:
index_s += 1
if index_s == len(s):
return True
index_t += 1

return False
9 changes: 9 additions & 0 deletions 392.Is-Subsequence/sol4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import re


class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
pattern = ""
for c in s:
pattern += ".*" + re.escape(c)
return re.match(pattern, t) is not None
9 changes: 9 additions & 0 deletions 392.Is-Subsequence/sol5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if len(s) == 0:
return True
if len(s) > len(t) or len(t) == 0:
return False
if s[0] == t[0]:
return self.isSubsequence(s[1:], t[1:])
return self.isSubsequence(s, t[1:])