diff --git a/392.Is-Subsequence/benchmark_sols.py b/392.Is-Subsequence/benchmark_sols.py new file mode 100644 index 0000000..72c6028 --- /dev/null +++ b/392.Is-Subsequence/benchmark_sols.py @@ -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() diff --git a/392.Is-Subsequence/memo.md b/392.Is-Subsequence/memo.md new file mode 100644 index 0000000..bc887b3 --- /dev/null +++ b/392.Is-Subsequence/memo.md @@ -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) diff --git a/392.Is-Subsequence/sol1.py b/392.Is-Subsequence/sol1.py new file mode 100644 index 0000000..f8946c8 --- /dev/null +++ b/392.Is-Subsequence/sol1.py @@ -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 + index_t += 1 + + return False diff --git a/392.Is-Subsequence/sol1_revised.py b/392.Is-Subsequence/sol1_revised.py new file mode 100644 index 0000000..af47288 --- /dev/null +++ b/392.Is-Subsequence/sol1_revised.py @@ -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 diff --git a/392.Is-Subsequence/sol2.py b/392.Is-Subsequence/sol2.py new file mode 100644 index 0000000..62da42a --- /dev/null +++ b/392.Is-Subsequence/sol2.py @@ -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 diff --git a/392.Is-Subsequence/sol3.py b/392.Is-Subsequence/sol3.py new file mode 100644 index 0000000..1e0c670 --- /dev/null +++ b/392.Is-Subsequence/sol3.py @@ -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: + if s[index_s] == t[index_t]: + index_s += 1 + if index_s == len(s): + return True + index_t += 1 + + return False diff --git a/392.Is-Subsequence/sol4.py b/392.Is-Subsequence/sol4.py new file mode 100644 index 0000000..657a29b --- /dev/null +++ b/392.Is-Subsequence/sol4.py @@ -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 diff --git a/392.Is-Subsequence/sol5.py b/392.Is-Subsequence/sol5.py new file mode 100644 index 0000000..93e651f --- /dev/null +++ b/392.Is-Subsequence/sol5.py @@ -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:])