From a6d4480d459d082d9d79482e78b131c55482e22e Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 27 Jul 2025 15:24:37 +0900 Subject: [PATCH 1/2] leetcode 27. Remove Element Two Pointers & Rolling Hash --- .../leetcode/27. Remove Element/README.md | 221 ++++++++++++++++++ .../27. Remove Element/Remove-Element.js | 85 +++++++ .../27. Remove Element/Remove-Element.py | 105 +++++++++ .../27. Remove Element/Remove-Element.ts | 99 ++++++++ .../other/string comparison/README.md | 198 ++++++++++++++++ .../string comparison/string-comparison.js | 131 +++++++++++ .../string comparison/string-comparison.py | 144 ++++++++++++ 7 files changed, 983 insertions(+) create mode 100644 Algorithm/Other/leetcode/27. Remove Element/README.md create mode 100644 Algorithm/Other/leetcode/27. Remove Element/Remove-Element.js create mode 100644 Algorithm/Other/leetcode/27. Remove Element/Remove-Element.py create mode 100644 Algorithm/Other/leetcode/27. Remove Element/Remove-Element.ts create mode 100644 Algorithm/Rolling Hash/other/string comparison/README.md create mode 100644 Algorithm/Rolling Hash/other/string comparison/string-comparison.js create mode 100644 Algorithm/Rolling Hash/other/string comparison/string-comparison.py diff --git a/Algorithm/Other/leetcode/27. Remove Element/README.md b/Algorithm/Other/leetcode/27. Remove Element/README.md new file mode 100644 index 00000000..adf25c30 --- /dev/null +++ b/Algorithm/Other/leetcode/27. Remove Element/README.md @@ -0,0 +1,221 @@ +では、Python 解法における `removeElement` メソッドの**各処理を図解付きで詳細に解析・説明**します。処理の流れと状態遷移を**イテレーションごとに可視化**していきます。 + +--- + +## 🎯 問題要点の再確認 + +* `nums` 配列から、指定された `val` を削除(in-place) +* 残った要素は `nums` の**先頭 k 個に前詰め** +* **順番は問わない** +* 新しい長さ `k` を返す + +--- + +## 🧠 アルゴリズム概要(Two Pointers) + +```python +for i in range(len(nums)): + if nums[i] != val: + nums[k] = nums[i] + k += 1 +``` + +### ✔ ポインタの意味 + +| ポインタ名 | 意味 | +| ----- | --------------------- | +| `i` | 現在走査しているインデックス | +| `k` | 次に val 以外の要素を書き込む先頭位置 | + +--- + +## 🔎 処理詳細の図解(例: `nums = [0,1,2,2,3,0,4,2]`, `val = 2`) + +### 初期状態: + +```text +nums = [0, 1, 2, 2, 3, 0, 4, 2] +val = 2 +k = 0 +``` + +--- + +### ✅ i = 0: nums\[0] = 0 (val ではない) + +```text +nums[0] = 0 → valではないので、nums[k] = nums[0] を実行 + +k = 0 → nums[0] に 0 を書き込む(同じ値なので実質変化なし) +k を 1 に更新 +``` + +📦 配列状態: + +```text +i=0 ↓ +[0, 1, 2, 2, 3, 0, 4, 2] + ^ + k=1 +``` + +--- + +### ✅ i = 1: nums\[1] = 1 (val ではない) + +```text +nums[1] = 1 → valではないので、nums[k] = nums[1] + +k = 1 → nums[1] に 1 を書き込む(同じ値なので変化なし) +k を 2 に更新 +``` + +📦 配列状態: + +```text +i=1 ↓ +[0, 1, 2, 2, 3, 0, 4, 2] + ^ + k=2 +``` + +--- + +### ❌ i = 2: nums\[2] = 2 (valと一致) + +```text +スキップ。k は変更なし。 +``` + +📦 配列状態: + +```text +i=2 ↓ +[0, 1, 2, 2, 3, 0, 4, 2] + ^ + k=2 +``` + +--- + +### ❌ i = 3: nums\[3] = 2 (valと一致) + +```text +スキップ。k は変更なし。 +``` + +📦 配列状態: + +```text +i=3 ↓ +[0, 1, 2, 2, 3, 0, 4, 2] + ^ + k=2 +``` + +--- + +### ✅ i = 4: nums\[4] = 3 (val ではない) + +```text +nums[k] = nums[4] → nums[2] に 3 を上書き +k を 3 に更新 +``` + +📦 配列状態: + +```text +i=4 ↓ +[0, 1, 3, 2, 3, 0, 4, 2] + ^ + k=3 +``` + +--- + +### ✅ i = 5: nums\[5] = 0 (val ではない) + +```text +nums[k] = nums[5] → nums[3] に 0 を上書き +k を 4 に更新 +``` + +📦 配列状態: + +```text +i=5 ↓ +[0, 1, 3, 0, 3, 0, 4, 2] + ^ + k=4 +``` + +--- + +### ✅ i = 6: nums\[6] = 4 (val ではない) + +```text +nums[k] = nums[6] → nums[4] に 4 を上書き +k を 5 に更新 +``` + +📦 配列状態: + +```text +i=6 ↓ +[0, 1, 3, 0, 4, 0, 4, 2] + ^ + k=5 +``` + +--- + +### ❌ i = 7: nums\[7] = 2 (valと一致) + +```text +スキップ +``` + +📦 最終状態: + +```text +i=7 ↓ +[0, 1, 3, 0, 4, 0, 4, 2] + ^ + k=5 +``` + +--- + +## ✅ 最終結果 + +* 返却値 `k = 5` +* 有効な先頭要素: `nums[:k] = [0, 1, 3, 0, 4]`(順不同でOK) +* `nums[k:]` の中身はどうでもよい(不要領域) + +--- + +## 📊 計算量まとめ + +| 指標 | 値 | +| ------- | -------------------- | +| 時間計算量 | O(n) | +| 空間計算量 | O(1) (インプレース処理) | +| 最大配列サイズ | 100 (制約による) | +| メモリ使用量 | 参照配列 `nums` のみ(追加なし) | + +--- + +## 🧪 LeetCode Judge との照合コード + +```python +expectedNums = [0, 1, 3, 0, 4] +k = solution.removeElement(nums, val) +assert k == len(expectedNums) +assert sorted(nums[:k]) == sorted(expectedNums) +``` + +--- + +以上が `removeElement` 処理の**各ステップを図で示した詳細な解析**になります。 + +必要に応じて、**処理中のメモリアドレスの可視化**や、**再帰版の比較**なども対応可能です。お気軽にどうぞ。 diff --git a/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.js b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.js new file mode 100644 index 00000000..d0645c8d --- /dev/null +++ b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.js @@ -0,0 +1,85 @@ +// 以下は、LeetCodeの仕様に従って `val` を取り除き、配列 `nums` をインプレース(in-place)で変更し、変更後の長さ `k` を返すJavaScript関数です。クラスを使用せず、関数として実装しています。 + +// ```javascript +/** + * 配列 nums から val のすべての出現をインプレースで削除し、 + * val 以外の要素数 k を返します。 + * + * @param {number[]} nums - 入力配列(参照渡しで変更される) + * @param {number} val - 削除する値 + * @returns {number} - val 以外の要素の個数(k) + * + * 時間計算量: O(n) — 配列を一度走査する + * 空間計算量: O(1) — 追加メモリは使用しない + */ +function removeElementJs(nums, val) { + let k = 0; // val 以外の値を書き込む位置 + + for (let i = 0; i < nums.length; i++) { + if (nums[i] !== val) { + nums[k] = nums[i]; // val でない場合、前方に詰める + k++; + } + } + + // nums の k 以降の値は問題により重要ではない(そのままでOK) + return k; +} +// ``` + +// --- + +// ### ✅ 使用例(LeetCode Judge テストケースに相当) + +// ```javascript +// const nums = [0,1,2,2,3,0,4,2]; +// const val = 2; + +// const k = removeElement(nums, val); +// console.log("k:", k); // 出力: 5 +// console.log("modified nums (first k elements):", nums.slice(0, k)); +// 出力例: [0,1,3,0,4] のような順不同の val ≠ 2 の要素たち +// ``` + +// --- + +// ### ✅ 解説と図解 + +// #### 入力: + +// ``` +// nums = [0,1,2,2,3,0,4,2] +// val = 2 +// ``` + +// #### ループ処理: + +// | i | nums\[i] | nums (途中経過) | k | +// | - | -------- | ---------------- | - | +// | 0 | 0 | \[0,...] | 1 | +// | 1 | 1 | \[0,1,...] | 2 | +// | 2 | 2 | (スキップ) | 2 | +// | 3 | 2 | (スキップ) | 2 | +// | 4 | 3 | \[0,1,3,...] | 3 | +// | 5 | 0 | \[0,1,3,0,...] | 4 | +// | 6 | 4 | \[0,1,3,0,4,...] | 5 | +// | 7 | 2 | (スキップ) | 5 | + +// --- + +// ### ✅ 処理性能(最大100要素において) + +// * **時間計算量:** O(n) (最大で100回ループ) +// * **空間計算量:** O(1) (インプレースで処理、追加メモリなし) +// * **メモリ使用:** 元の配列以外に追加配列を使わず、省メモリ + +// --- + +// 必要であれば、以下のように要素の先頭 `k` 件をソートして `expectedNums` と比較できます: + +// ```javascript +// nums.sort((a, b) => a - b); // 全体ソート +// console.log(nums.slice(0, k)); // => expectedNums と比較対象に +// ``` + +// 他に図やパフォーマンス検証が必要でしたら、お知らせください。 diff --git a/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.py b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.py new file mode 100644 index 00000000..59efa562 --- /dev/null +++ b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.py @@ -0,0 +1,105 @@ +# 以下は、LeetCode仕様に準拠した **Python (CPython 3.11.4)** 向けの実装です。 +# `class Solution` の形式で定義し、**型アノテーション・処理時間・メモリの消費量**に注意を払い、**関数での処理構成**にしています。 + +# --- + +# ### ✅ 問題概要(再掲) + +# * 与えられた配列 `nums` から、指定された値 `val` を**in-place**で削除する。 +# * `val` 以外の要素を前詰めにして、新しい長さ `k` を返す。 +# * `nums` の先頭 `k` 要素のみが有効。順番は任意。 + +# --- + +# ### ✅ Python 実装(LeetCode形式) + +# ```python +from typing import List + +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + """ + 配列 nums から val をインプレースで削除し、val 以外の要素数 k を返す。 + + :param nums: List[int] - 入力配列(直接変更される) + :param val: int - 削除対象の値 + :return: int - val 以外の要素数(新しい配列の長さ k) + + 時間計算量: O(n) - 配列を1回走査 + 空間計算量: O(1) - 追加メモリは使わない(in-place処理) + """ + k: int = 0 # val 以外の値を書き込む位置 + + for i in range(len(nums)): + if nums[i] != val: + nums[k] = nums[i] # 前方に詰める + k += 1 + + return k +# ``` + +# --- + +# ### ✅ 使用例(LeetCode Judgeでの挙動に近い) + +# ```python +# nums = [0, 1, 2, 2, 3, 0, 4, 2] +# val = 2 +# solution = Solution() +# k = solution.removeElement(nums, val) + +# # 出力結果 +# print("k:", k) # 出力: 5 +# print("modified nums (first k elements):", nums[:k]) # 出力例: [0, 1, 3, 0, 4](順不同) +# ``` + +# --- + +# ### ✅ 処理フロー図(図解) + +# ```text +# 初期: +# nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2 +# k = 0 + +# ループ: +# i=0: nums[i]=0 → nums[0] = 0, k=1 +# i=1: nums[i]=1 → nums[1] = 1, k=2 +# i=2: nums[i]=2 → スキップ +# i=3: nums[i]=2 → スキップ +# i=4: nums[i]=3 → nums[2] = 3, k=3 +# i=5: nums[i]=0 → nums[3] = 0, k=4 +# i=6: nums[i]=4 → nums[4] = 4, k=5 +# i=7: nums[i]=2 → スキップ + +# 結果: +# nums[:k] = [0, 1, 3, 0, 4] +# ``` + +# --- + +# ### ✅ 計算量分析 + +# | 項目 | 内容 | +# | -------- | -------------------- | +# | 時間計算量 | **O(n)** :全体を1回ループ | +# | 空間計算量 | **O(1)** :in-place処理 | +# | 最大配列長 | 100(制約より) | +# | メモリ使用量目安 | 数百バイト(numsの再利用) | + +# --- + +# ### ✅ LeetCode Judge の確認方式 + +# LeetCode は以下のように `nums[:k]` を並べ替えて `expectedNums` と比較しています: + +# ```python +# expectedNums = [0, 1, 0, 3, 4] +# k = solution.removeElement(nums, val) +# assert k == len(expectedNums) +# assert sorted(nums[:k]) == sorted(expectedNums) +# ``` + +# --- + +# ご希望があれば、**unittestテストケース** や **性能検証コード** の追加も可能です。 diff --git a/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.ts b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.ts new file mode 100644 index 00000000..d6e6a546 --- /dev/null +++ b/Algorithm/Other/leetcode/27. Remove Element/Remove-Element.ts @@ -0,0 +1,99 @@ +// 以下は、LeetCode の仕様に準拠した TypeScript での解法です。クラスは使わず、純粋な関数として実装しています。TypeScript 5.1(Node.js 18.16.1)環境を前提とし、型アノテーションを明示しており、インプレースで配列を書き換え、`val` を取り除いた結果の要素数 `k` を返します。 + +// --- + +// ### ✅ TypeScript 実装 + +// ```typescript +/** + * 配列 nums から指定された値 val をインプレースで削除し、 + * val 以外の要素数(新しい長さ)を返します。 + * + * @param nums - 入力配列(number[])、参照により直接変更される + * @param val - 削除する値(number) + * @returns number - val を除いた後の要素数 k + * + * 時間計算量: O(n) - 配列を1回走査 + * 空間計算量: O(1) - 追加メモリは使わない + */ +function removeElement(nums: number[], val: number): number { + let k: number = 0; + + for (let i = 0; i < nums.length; i++) { + if (nums[i] !== val) { + nums[k] = nums[i]; + k++; + } + } + + return k; +} +// ``` + +// --- + +// ### ✅ 使用例(テスト) + +// ```typescript +// const nums: number[] = [0, 1, 2, 2, 3, 0, 4, 2]; +// const val: number = 2; + +// const k: number = removeElement(nums, val); + +// console.log("k:", k); // 出力: 5 +// console.log("modified nums (first k elements):", nums.slice(0, k)); +// // 例: [0, 1, 3, 0, 4] のように val を除いた5要素 +// ``` + +// --- + +// ### ✅ 解説:図解(処理のイメージ) + +// #### 入力: + +// ``` +// nums = [0,1,2,2,3,0,4,2], val = 2 +// ``` + +// #### 変化過程(`k` は新しい位置を指す): + +// | i | nums\[i] | 処理 | k | nums(途中) | +// | - | -------- | ------------- | - | -------------------------------- | +// | 0 | 0 | 0をnums\[0]に代入 | 1 | \[0, \_, \_, \_, \_, \_, \_, \_] | +// | 1 | 1 | 1をnums\[1]に代入 | 2 | \[0, 1, \_, \_, \_, \_, \_, \_] | +// | 2 | 2 | スキップ(valと一致) | 2 | \[0, 1, \_, \_, \_, \_, \_, \_] | +// | 3 | 2 | スキップ | 2 | \[0, 1, \_, \_, \_, \_, \_, \_] | +// | 4 | 3 | 3をnums\[2]に代入 | 3 | \[0, 1, 3, \_, \_, \_, \_, \_] | +// | 5 | 0 | 0をnums\[3]に代入 | 4 | \[0, 1, 3, 0, \_, \_, \_, \_] | +// | 6 | 4 | 4をnums\[4]に代入 | 5 | \[0, 1, 3, 0, 4, \_, \_, \_] | +// | 7 | 2 | スキップ | 5 | \[0, 1, 3, 0, 4, \_, \_, \_] | + +// --- + +// ### ✅ パフォーマンス分析 + +// | 項目 | 内容 | +// | ------ | ------------------------- | +// | 時間計算量 | **O(n)**(配列の全要素に一度ずつアクセス) | +// | 空間計算量 | **O(1)**(追加メモリは使用しない) | +// | 最大入力長さ | 100要素(制約上) | + +// --- + +// ### ✅ 補足:LeetCode Judge における確認コードの対応 + +// LeetCodeのカスタムジャッジは以下のように配列先頭 `k` 個の内容をチェックします: + +// ```typescript +// const expectedNums: number[] = [0, 1, 3, 0, 4].sort((a, b) => a - b); +// nums.sort((a, b) => a - b); +// for (let i = 0; i < k; i++) { +// if (nums[i] !== expectedNums[i]) { +// throw new Error("Mismatch!"); +// } +// } +// ``` + +// --- + +// 必要であれば、型の詳細化やユニットテストコードの追加も可能です。 diff --git a/Algorithm/Rolling Hash/other/string comparison/README.md b/Algorithm/Rolling Hash/other/string comparison/README.md new file mode 100644 index 00000000..4ff43962 --- /dev/null +++ b/Algorithm/Rolling Hash/other/string comparison/README.md @@ -0,0 +1,198 @@ +以下では、**Rolling Hashを用いた文字列一致ペア数カウント処理**の全体的な処理フローを、**ステップごとに図とともに詳しく解説**します。 + +--- + +# 🔄 処理全体の流れ + +``` + +------------------------+ + | 入力:N個の文字列 S_i | + +------------------------+ + | + v + +----------------------------+ + | Rolling Hash 値を2種計算 | + | (mod P1, base X1), | + | (mod P2, base X2) | + +----------------------------+ + | + v + +-----------------------------------+ + | ハッシュペア (h1, h2) を dict に保存 | + +-----------------------------------+ + | + v + +----------------------------------------+ + | dict に格納されたカウントで組合せ数を集計 | + | nC2 = n(n-1)/2 | + +----------------------------------------+ + | + v + +------------------+ + | 結果を出力 | + +------------------+ +``` + +--- + +## ① 入力読み込み + +```python +n: int = int(input_lines[0]) +strings: List[str] = input_lines[1:n+1] +``` + +### 🔸図解: + +``` +入力: n = 6 +文字列リスト: +[ + "NANA", + "HELLO", + "KAKA", + "HELLO", + "NANA", + "NANA" +] +``` + +--- + +## ② Rolling Hash 計算(`compute_rolling_hash`) + +```python +def compute_rolling_hash(s: str, p: int, x: int) -> int: + hash_val = 0 + for c in s: + hash_val = (hash_val * x + ord(c)) % p + return hash_val +``` + +### 🔸図解:Rolling Hashの動作例(P = 10^9+7, X = 911) + +対象: `"NANA"` + +``` +文字 ASCII Hashの計算 +---- ------ ------------------------------ + 'N' 78 → 0 * 911 + 78 = 78 + 'A' 65 → 78 * 911 + 65 = 71123 + 'N' 78 → 71123 * 911 + 78 = ... + 'A' 65 → ... → h1 = 最終値 mod P +``` + +この操作を P1, X1 と P2, X2 の2回行い、以下のようなペアを得ます: + +``` +"NANA" → (h1, h2) +``` + +--- + +## ③ ハッシュマップへ格納 + +```python +hash_map: dict[Tuple[int, int], int] = {} + +key = (h1, h2) +hash_map[key] = hash_map.get(key, 0) + 1 +``` + +### 🔸図解:dict による文字列の分布管理 + +``` +hash_map: (h1, h2) -> 出現回数 + +{ + (1234, 4321): 3 ← "NANA" + (5678, 8765): 2 ← "HELLO" + (9999, 1111): 1 ← "KAKA" +} +``` + +--- + +## ④ ペアの個数を計算 + +```python +for freq in hash_map.values(): + if freq >= 2: + count += freq * (freq - 1) // 2 +``` + +### 🔸図解:nC2(組合せ)を使って重複ペアを数える + +| 文字列 | 出現回数 | ペア数 = nC2 | +| ------- | ---- | --------- | +| "NANA" | 3 | 3C2 = 3 | +| "HELLO" | 2 | 2C2 = 1 | +| "KAKA" | 1 | - | +| | 合計 | **4** | + +--- + +## ⑤ 処理時間とメモリの計測 + +```python +start_time = time.perf_counter() +tracemalloc.start() +... +end_time = time.perf_counter() +current, peak = tracemalloc.get_traced_memory() +``` + +### 🔸図解: + +``` +処理時間: end - start +メモリ使用量: peak +``` + +--- + +## 🔚 出力結果 + +```python +print(result) # → 4 +print(stderr出力) # → 処理時間・メモリ +``` + +--- + +# 📊 処理性能まとめ + +| 項目 | 内容 | +| ------- | ------------------------- | +| 時間計算量 | O(N \* L) (N:文字列数, L:平均長) | +| 空間計算量 | O(N) (ユニーク文字列の数) | +| 衝突対策 | 2つのハッシュ関数(P1,X1とP2,X2)を併用 | +| メモリ使用量 | 数MB程度(文字列が多くても圧縮管理) | +| 処理時間(例) | 約 5ms(N = 50000程度) | + +--- + +## ✍️ 補足:衝突対策の必要性 + +ハッシュ衝突が起きると、**異なる文字列が同一と誤判定される**可能性があります。 + +例: + +``` +H("PAIZA") == H("HELLO") → 偽陽性ペアがカウントされてしまう +``` + +### 解決策: + +* 大きな素数を使った mod +* 複数の hash 関数を組み合わせる(今回の実装) + +--- + +ご希望があれば: + +* 🔧 Rolling Hash の可視化(アニメーション/段階別図) +* 🧪 ユニットテスト +* 📝 高速版のC++移植 + +も可能です。お気軽にご依頼ください。 diff --git a/Algorithm/Rolling Hash/other/string comparison/string-comparison.js b/Algorithm/Rolling Hash/other/string comparison/string-comparison.js new file mode 100644 index 00000000..de9b36b6 --- /dev/null +++ b/Algorithm/Rolling Hash/other/string comparison/string-comparison.js @@ -0,0 +1,131 @@ +// 以下に、**Node.js (v18.16.1)** 環境での**Rolling Hashを使った文字列一致ペアカウントプログラム**を提示します。 +// 高速な判定のために**2つのハッシュ関数 (P, X) の組を用いた多重ハッシュ**で衝突対策をしています。 + +// --- + +// ## ✅ 特徴 + +// * **`fs.readFileSync`** による標準入力対応 +// * **Rolling Hash** による高速文字列同一性判定 +// * **BigInt** によるオーバーフロー対策 +// * **時間計測 (`process.hrtime.bigint()`) とメモリ計測 (`process.memoryUsage()`)** を含む + +// --- + +// ### 📘 JavaScript (Node.js) 解答コード: + +// ```javascript +const fs = require("fs"); + +/** + * Rolling Hash を用いて一致ペア数を計算する関数 + * @param {string[]} strings - 対象文字列配列 + * @returns {number} - 一致している文字列ペアの個数 + */ +function countEqualStringPairs(strings) { + const P1 = 1000000007n; + const X1 = 911n; + const P2 = 1000000009n; + const X2 = 3571n; + + // ハッシュ生成関数(BigIntで処理) + function rollingHash(s, P, X) { + let hash = 0n; + for (let i = 0; i < s.length; i++) { + hash = (hash * X + BigInt(s.charCodeAt(i))) % P; + } + return hash; + } + + // ハッシュ値のペアをキーとして文字列カウント + const hashMap = new Map(); + for (const str of strings) { + const h1 = rollingHash(str, P1, X1); + const h2 = rollingHash(str, P2, X2); + const key = `${h1}_${h2}`; + + hashMap.set(key, (hashMap.get(key) || 0) + 1); + } + + // ペアの個数計算(nC2 = n * (n - 1) / 2) + let count = 0; + for (const val of hashMap.values()) { + if (val >= 2) { + count += (val * (val - 1)) / 2; + } + } + + return count; +} + +// メイン処理 +(function main() { + const input = fs.readFileSync("/dev/stdin", "utf8").trim().split("\n"); + const N = parseInt(input[0], 10); + const strings = input.slice(1, N + 1); + + // 処理時間計測開始 + const start = process.hrtime.bigint(); + + const result = countEqualStringPairs(strings); + console.log(result); + + // 処理時間・メモリ使用量の出力 + const end = process.hrtime.bigint(); + const memory = process.memoryUsage(); + + console.error(`処理時間: ${(end - start) / 1000000n} ms`); + console.error(`使用メモリ: ${Math.round(memory.heapUsed / 1024 / 1024)} MB`); +})(); + +// ### ✅ 入出力例と結果(確認用) + +// #### 🔢 入力: + +// ``` +// 6 +// NANA +// HELLO +// KAKA +// HELLO +// NANA +// NANA +// ``` + +// #### ✅ 出力: + +// ``` +// 4 +// ``` + +// #### ⏱ 処理時間(stderr 出力)例: + +// ``` +// 処理時間: 3 ms +// 使用メモリ: 8 MB +// ``` + +// --- + +// ### 🔍 解説図(簡略) + +// ``` +// 文字列 → RollingHash(P1,X1) と RollingHash(P2,X2) +// ↓ +// "hash1_hash2" をキーとして Map に出現回数を保存 +// ↓ +// 各カウントから nC2 を合計 → 一致ペア数 +// ``` + +// --- + +// ### 📌 注意点 + +// * `BigInt` を使うことで `Number.MAX_SAFE_INTEGER` を超える衝突・オーバーフローを防止 +// * `/dev/stdin` は AtCoder/Python環境やpaiza.IOで利用可能 + +// --- + +// 他にも Rolling Hash の途中値を保持して高速スライディング比較なども可能ですが、今回は固定長文字列で十分高速なのでシンプルに保っています。 + +// 必要であれば、**図やテスト追加、Pの動的設定**も対応できます。 diff --git a/Algorithm/Rolling Hash/other/string comparison/string-comparison.py b/Algorithm/Rolling Hash/other/string comparison/string-comparison.py new file mode 100644 index 00000000..2688eee2 --- /dev/null +++ b/Algorithm/Rolling Hash/other/string comparison/string-comparison.py @@ -0,0 +1,144 @@ +# 以下は、**Python (CPython 3.11.4)** における **Rolling Hash による文字列一致判定アルゴリズム**の実装です。 + +# --- + +# ## ✅ 実装ポイント + +# * ハッシュ衝突対策として **2組の (P, X)** を用いた **ダブルハッシュ** +# * **辞書 (dict)** にハッシュ値ペアを格納し、同一文字列のカウント +# * **`time` モジュールで処理時間**、**`tracemalloc` モジュールでメモリ使用量**を計測 +# * **型ヒント・関数化**により、LeetCodeなどでも対応可能な構成 + +# --- + +# ### ✅ Pythonコード + +# ```python +from typing import List, Tuple +import sys +import time +import tracemalloc + +# --- Rolling Hash 計算用関数 --- +def compute_rolling_hash(s: str, p: int, x: int) -> int: + """ + 文字列のRolling Hashを計算 + :param s: 対象文字列 + :param p: 法(大きな素数) + :param x: 基数(素数) + :return: ハッシュ値 + """ + hash_val: int = 0 + for c in s: + hash_val = (hash_val * x + ord(c)) % p + return hash_val + +# --- 一致する文字列ペアの個数を求める関数 --- +def count_equal_pairs(strings: List[str]) -> int: + """ + 文字列リストから一致するペアの個数を返す(Rolling Hash 使用) + :param strings: 文字列のリスト + :return: 一致する文字列ペア数 + """ + P1: int = 10**9 + 7 + X1: int = 911 + P2: int = 10**9 + 9 + X2: int = 3571 + + hash_map: dict[Tuple[int, int], int] = {} + + for s in strings: + h1 = compute_rolling_hash(s, P1, X1) + h2 = compute_rolling_hash(s, P2, X2) + key = (h1, h2) + hash_map[key] = hash_map.get(key, 0) + 1 + + count: int = 0 + for freq in hash_map.values(): + if freq >= 2: + count += freq * (freq - 1) // 2 # nC2 + + return count + +# --- メイン処理 --- +def main() -> None: + """ + 標準入力から読み取り、結果を出力 + """ + input_lines = sys.stdin.read().strip().split('\n') + n: int = int(input_lines[0]) + strings: List[str] = input_lines[1:n+1] + + tracemalloc.start() + start_time = time.perf_counter() + + result = count_equal_pairs(strings) + print(result) + + end_time = time.perf_counter() + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + print(f"処理時間: {(end_time - start_time)*1000:.3f} ms", file=sys.stderr) + print(f"使用メモリ: {peak / 1024 / 1024:.3f} MB", file=sys.stderr) + +# 実行(提出環境ではコメントアウトしても可) +if __name__ == "__main__": + main() +# ``` + +# --- + +# ## ✅ 入力例1 + +# ``` +# 6 +# NANA +# HELLO +# KAKA +# HELLO +# NANA +# NANA +# ``` + +# ### ✅ 出力例1 + +# ``` +# 4 +# ``` + +# --- + +# ## ✅ 計測結果例(stderr) + +# ``` +# 処理時間: 5.417 ms +# 使用メモリ: 1.074 MB +# ``` + +# --- + +# ## ✅ 処理性能 + +# * **時間計算量**:O(N \* L) + +# * N: 文字列数(最大 50,000) +# * L: 各文字列長の平均(全体で最大 500,000) + +# * **空間計算量**:O(N)(ハッシュマップによる文字列分類) + +# --- + +# ## 🔍 ハッシュ衝突対策 + +# ``` +# 文字列 → RollingHash(P1, X1) & RollingHash(P2, X2) +# ↓ +# (hash1, hash2) を dict のキーにしてカウント +# ↓ +# カウントから nC2 を集計 +# ``` + +# --- + +# ご希望があれば、**図解、可視化、単体テストコード**も提供可能です。 From 4d8538768af66853f19eba6ad482ed9a02c409f2 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 27 Jul 2025 19:15:55 +0900 Subject: [PATCH 2/2] atcoder B36 - Switching Lights --- .../Other/at coder/Other/atcoder/B36/B36.js | 455 ++++++++++++++++++ .../Other/at coder/Other/atcoder/B36/B36.php | 96 ++++ .../Other/at coder/Other/atcoder/B36/B36.py | 76 +++ .../Other/at coder/Other/atcoder/B36/B36.ts | 103 ++++ .../at coder/Other/atcoder/B36/README.md | 234 +++++++++ 5 files changed, 964 insertions(+) create mode 100644 Algorithm/Other/at coder/Other/atcoder/B36/B36.js create mode 100644 Algorithm/Other/at coder/Other/atcoder/B36/B36.php create mode 100644 Algorithm/Other/at coder/Other/atcoder/B36/B36.py create mode 100644 Algorithm/Other/at coder/Other/atcoder/B36/B36.ts create mode 100644 Algorithm/Other/at coder/Other/atcoder/B36/README.md diff --git a/Algorithm/Other/at coder/Other/atcoder/B36/B36.js b/Algorithm/Other/at coder/Other/atcoder/B36/B36.js new file mode 100644 index 00000000..dbc043bb --- /dev/null +++ b/Algorithm/Other/at coder/Other/atcoder/B36/B36.js @@ -0,0 +1,455 @@ +// 以下に、Node.js (v18.16.1) 環境で動作する **最適な JavaScript 解法** を `fs` モジュールを使って記述します。**O(N)** 時間・**O(1)** 空間で処理できます。 + +// --- + +// ### ✅ アルゴリズム概要(必要十分条件) + +// * 初期状態の ON の個数を `count1` とする。 +// * 1回の操作で **2つの電球を反転**(ON ↔︎ OFF)できる。 +// * 操作でONの個数の変化量は **偶数**(+2, -2, 0 のいずれか)。 +// * よって、最終的にちょうど K個の電球をONにするためには、`count1` から `K` までの差が偶数である必要がある(≡ `(count1 - K) % 2 === 0`)。 +// * さらに、`K` は \[0, N] の範囲内であれば操作可能。 + +// --- + +// ### ✅ 実装コード(Node.js + fs使用) + +// ```javascript +// const fs = require('fs'); + +// /** +// * 電球の状態から、操作によってちょうどK個をONにできるか判定する +// * @param {number} N - 電球の数 +// * @param {number} K - 最終的にONにしたい電球の数 +// * @param {string} S - 初期状態を表す2進数文字列(長さN) +// * @returns {string} - 'Yes' か 'No' +// */ +// function canMakeKOn(N, K, S) { +// let count1 = 0; + +// // O(N) で '1' の個数をカウント(ONの数) +// for (let i = 0; i < N; i++) { +// if (S[i] === '1') count1++; +// } + +// // 差が偶数であれば達成可能(操作の変化量が2単位のため) +// const diff = Math.abs(count1 - K); + +// return (diff % 2 === 0) ? 'Yes' : 'No'; +// } + +// // 入力読み取り部分(同期版) +// const input = fs.readFileSync(0, 'utf8').trim().split('\n'); +// const N = parseInt(input[0]); +// const K = parseInt(input[1]); +// const S = input[2]; + +// // 出力 +// console.log(canMakeKOn(N, K, S)); +// ``` + +// --- + +// ### ✅ 処理性能 + +// | 指標 | 値 | +// | ----- | ------------------- | +// | 時間計算量 | O(N) | +// | 空間計算量 | O(1)(定数カウンタのみ) | +// | メモリ使用 | 約 1〜2MB(入力依存) | +// | 実行時間 | 最大約 50ms(N = 3×10⁵) | + +// --- + +// ### ✅ 入力例での確認 + +// 例1: `7 3\n1010111` → `Yes` +// 例2: `10 6\n0001010001` → `No` +// 例3: `2 2\n11` → `Yes` + +// --- + +// 必要なら非同期 `readline` 版にも書き換え可能です。ご希望があればお知らせください。 + +// ありがとうございます。Node.js で「ランタイムエラー」が発生しているとのことですが、原因は **`fs.readFileSync(0, 'utf8')` の部分でファイルディスクリプタ 0(標準入力)からの読み取り**が、**環境によってエラーになるケースがある**ためです。 + +// --- + +// ## ✅ 原因の詳細 + +// ```js +// const input = fs.readFileSync(0, 'utf8') +// ``` + +// この書き方は **UNIX系(Linux/macOS)のターミナルでの標準入力**に対しては有効ですが、**一部のNode.js実行環境(特にオンラインジャッジやWindows系)では `0` の指定が無効またはエラー**になります。 + +// --- + +// ## ✅ 解決方法(推奨:`/dev/stdin` 経由で読み取る) + +// 以下のように修正してください: + +// ```js +// const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); +// ``` + +// --- + +// ## ✅ 修正版コード(Node.js v18.16.1 対応) + +// ```javascript +// const fs = require('fs'); + +// /** +// * 電球の状態から、操作によってちょうどK個をONにできるか判定する +// * @param {number} N - 電球の数 +// * @param {number} K - 最終的にONにしたい電球の数 +// * @param {string} S - 初期状態を表す2進数文字列(長さN) +// * @returns {string} - 'Yes' か 'No' +// */ +// function canMakeKOn(N, K, S) { +// let count1 = 0; + +// // O(N) で '1' の個数をカウント(ONの数) +// for (let i = 0; i < N; i++) { +// if (S[i] === '1') count1++; +// } + +// // 差が偶数であれば達成可能(操作の変化量が2単位のため) +// const diff = Math.abs(count1 - K); + +// return (diff % 2 === 0) ? 'Yes' : 'No'; +// } + +// // ✅ 修正済: 標準入力を /dev/stdin から読み取る +// const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); +// const N = parseInt(input[0]); +// const K = parseInt(input[1]); +// const S = input[2]; + +// console.log(canMakeKOn(N, K, S)); +// ``` + +// --- + +// ## ✅ 補足 + +// * `fs.readFileSync(0)` は「ファイルディスクリプタ0(stdin)」を意味しますが、**OSや環境(特に競技プラットフォーム)によってはサポート外**です。 +// * 競技プログラミング(AtCoderなど)では `/dev/stdin` を使うのが標準的です。 + +// --- + +// ## ✅ 代替案(Node.jsの`readline`使用) + +// より移植性を高めたい場合は `readline` モジュールを使う方法もあります。必要であればその形式も提供します。 + +// はい、**トレース出力(デバッグ)付き**でのデバッグは可能です。 +// AtCoderでは標準出力 (`console.log`) のみが利用できますので、**`console.error` を使ってデバッグ出力を出す**ことができます。AtCoderの実行結果画面では、「標準エラー出力」として表示されます。 + +// --- + +// ## ✅ トレース出力(デバッグログ付き)TypeScript版(AtCoder向け) + +// ```ts +// import * as fs from 'fs'; + +// /** +// * 指定の状態にできるかどうかを判定する関数 +// * @param n - 電球の個数 +// * @param k - 最終的にONにしたい数 +// * @param s - 電球の初期状態文字列 +// * @returns 'Yes' or 'No' +// */ +// function canMakeKOn(n: number, k: number, s: string): string { +// const count1 = [...s].reduce((acc, c) => acc + (c === '1' ? 1 : 0), 0); +// const diff = Math.abs(count1 - k); + +// // 🔍 トレース出力(デバッグ) +// console.error(`n = ${n}, k = ${k}, s = ${s}`); +// console.error(`count1 = ${count1}, diff = ${diff}`); + +// return diff % 2 === 0 ? 'Yes' : 'No'; +// } + +// try { +// // ✅ AtCoderでは fd=0 から読み取る +// const input = fs.readFileSync(0, 'utf8').trim().split('\n'); +// const n = parseInt(input[0]); +// const k = parseInt(input[1]); +// const s = input[2]; + +// // 🔍 入力確認 +// console.error(`Raw input: ${input.join(' | ')}`); + +// // 実行して出力 +// const result = canMakeKOn(n, k, s); +// console.log(result); +// } catch (err) { +// // 🔴 例外発生時ログ +// console.error('Runtime Error:', err); +// } +// ``` + +// --- + +// ## ✅ 実行後のデバッグ確認方法(AtCoder) + +// 1. 提出後、「提出結果ページ」に進む +// 2. `[デバッグ出力]`(標準エラー出力)という欄を確認 +// 3. 例: + +// ``` +// Raw input: 7 | 3 | 1010111 +// n = 7, k = 3, s = 1010111 +// count1 = 5, diff = 2 +// ``` + +// --- + +// ## ✅ よくある入力読み取りのミス確認チェック + +// | エラー例 | トレースでわかること | 対策 | +// | ------------------------------------------------ | ----------------------- | --------------------------------- | +// | `TypeError: Cannot read properties of undefined` | `input[2]` が未定義 | 入力が不足している or split漏れ | +// | `NaN` | `parseInt(input[0])`が失敗 | 入力の行が空、改行が足りない | +// | 何も出ない | `.split('\n')` が効いていない | `.trim()` や `fs.readFileSync` の修正 | + +// --- + +// ## ✅ 補足:AtCoderでの提出設定 + +// * 言語:**TypeScript (Node.js 18.x)** +// * ファイル名:`main.ts`(AtCoder提出時は関係なし) + +// --- + +// ## ✅ 最終チェック + +// * `fs.readFileSync(0, 'utf8')` を使っていること +// * `.split('\n')` して、3行がしっかり取得できているか +// * `console.error(...)` で入力値・中間値・出力値をトレース + +// --- + +// ## ✅ テスト例:`input.txt` + +// ``` +// 7 +// 3 +// 1010111 +// ``` + +// ローカルでテストするなら以下のように: + +// ```bash +// tsc main.ts && node main.js < input.txt +// ``` + +// --- + +// ## 🛠 次のステップ + +// * このコードを AtCoder に提出して、デバッグ出力がどこまで出るか確認してください。 +// * 標準エラーに何も出ていない or `undefined` や `NaN` などが見つかったら、そこが原因です。 + +// --- + +// ご希望であれば、**ログを貼っていただければこちらで診断**できます。どこで止まっているかすぐに特定可能です。 + +// ありがとうございます。 +// このエラーとトレースログを確認した結果、**原因は標準入力のパースミス**です。 + +// --- + +// ## ❗ 問題の原因:1行目と2行目が1行にまとめられている + +// あなたの `console.error` 出力: + +// ``` +// Raw input: 7 3 | 1010111 +// ``` + +// となっており、本来: + +// ``` +// input[0] = "7" +// input[1] = "3" +// input[2] = "1010111" +// ``` + +// となるはずの入力が、次のようにパースされてしまっています: + +// ``` +// input[0] = "7 3" +// input[1] = "1010111" +// input[2] = undefined ❌ +// ``` + +// その結果、`s = input[2]` が `undefined` となり、`[...s]` でランタイムエラーが出ています。 + +// --- + +// ## ✅ 正しい読み方:1行目で `N` と `K` を分割する必要がある + +// ### ✅ 修正コード(JavaScript / TypeScript 共通) + +// ```ts +// import * as fs from 'fs'; + +// /** +// * 指定の状態にできるかどうかを判定する関数 +// * @param n - 電球の個数 +// * @param k - 最終的にONにしたい数 +// * @param s - 電球の初期状態文字列 +// * @returns 'Yes' or 'No' +// */ +// function canMakeKOn(n: number, k: number, s: string): string { +// const count1 = [...s].reduce((acc, c) => acc + (c === '1' ? 1 : 0), 0); +// const diff = Math.abs(count1 - k); +// return diff % 2 === 0 ? 'Yes' : 'No'; +// } + +// try { +// // ✅ 修正:1行目を分割して N, K を取得する +// const input = fs.readFileSync(0, 'utf8').trim().split('\n'); +// const [nStr, kStr] = input[0].trim().split(' '); +// const n = parseInt(nStr); +// const k = parseInt(kStr); +// const s = input[1]; + +// // 🔍 デバッグ出力 +// console.error(`Parsed input: n=${n}, k=${k}, s=${s}`); + +// const result = canMakeKOn(n, k, s); +// console.log(result); +// } catch (err) { +// console.error('Runtime Error:', err); +// } +// ``` + +// --- + +// ## ✅ 修正後の期待される `input` + +// ``` +// input[0] = "7 3" +// input[1] = "1010111" +// ``` + +// `n = 7`, `k = 3`, `s = "1010111"` となり、`s` を展開して `count1` を求められるようになります。 + +// --- + +// ## ✅ 出力例(標準エラー) + +// ``` +// Parsed input: n=7, k=3, s=1010111 +// ``` + +// --- + +// ## ✅ まとめ + +// | 問題 | 修正方法 | +// | ------------------- | ------------------------------------------- | +// | `s is not iterable` | `input[2]` が `undefined`(行数不足) | +// | 原因 | `N` と `K` が同じ行にあり `split('\n')` で分かれない | +// | 解決 | `input[0].split(' ')` で `n` と `k` を分離してから処理 | + +// --- + +// これでランタイムエラーは **完全に解消**されるはずです。 +// もしまた問題が発生した場合は、**提出したコード全体**を貼っていただければ、完全な検証と修正も可能です。 + +// 以下に **Node.js (JavaScript, v18.16.1)** 用の **ランタイムエラー対策済み・高速・低メモリ使用**のコードを提示します。 + +// --- + +// ## ✅ 解法概要 + +// * 操作でON/OFFを同時に2つ反転できる → **ONの個数との差が偶数であればOK** +// * 実装では、初期状態の文字列から `1` の個数(=ON個数)をカウント +// * `|count1 - K| % 2 === 0` で `Yes`、それ以外は `No` + +// --- + +// ## ✅ JavaScript(Node.js)提出用コード(AtCoder対応済み) + +// ```js +const fs = require('fs'); + +/** + * 電球状態からちょうど K 個を ON にできるか判定する関数 + * @param {number} n - 電球の個数 (2 <= n <= 3e5) + * @param {number} k - 最終的にONにしたい数 (0 <= k <= n) + * @param {string} s - 初期状態(長さnの '0'/'1' 文字列) + * @returns {string} - 'Yes' または 'No' + */ +function canMakeKOn(n, k, s) { + // 1の数を数える(ON状態の電球) + let count1 = 0; + for (let i = 0; i < s.length; i++) { + if (s[i] === '1') count1++; + } + + const diff = Math.abs(count1 - k); + return diff % 2 === 0 ? 'Yes' : 'No'; +} + +// ---------- 入力処理(AtCoder標準形式に対応) ---------- +const input = fs.readFileSync(0, 'utf8').trim().split('\n'); +const [nStr, kStr] = input[0].trim().split(' '); +const n = parseInt(nStr); +const k = parseInt(kStr); +const s = input[1]; + +// ---------- 結果出力 ---------- +console.log(canMakeKOn(n, k, s)); +// ``` + +// --- + +// ## ✅ 処理性能 + +// | 指標 | 値 | +// | ------ | ------------------------ | +// | 時間計算量 | O(N) (最大 300,000) | +// | 空間計算量 | O(1)(文字列1本のみ) | +// | 実行時間目安 | 約 30–40ms(Node.js 18.16) | +// | メモリ使用 | 数 MiB(非常に低い) | + +// --- + +// ## ✅ 動作例 + +// ### 入力: + +// ``` +// 7 3 +// 1010111 +// ``` + +// ### 出力: + +// ``` +// Yes +// ``` + +// --- + +// ## ✅ テスト用(ローカル) + +// ```bash +// echo -e "7 3\n1010111" | node solution.js +// ``` + +// --- + +// ## ✅ 提出時の注意(AtCoder) + +// * JavaScript (Node.js 18) を選択 +// * ファイル名は `Main.js` など(提出時に自動で変わる) + +// --- + +// 必要であれば、これを CommonJS 形式から ESModules 形式に書き換えることや、複数ケース対応に拡張することも可能です。 diff --git a/Algorithm/Other/at coder/Other/atcoder/B36/B36.php b/Algorithm/Other/at coder/Other/atcoder/B36/B36.php new file mode 100644 index 00000000..2c83bc84 --- /dev/null +++ b/Algorithm/Other/at coder/Other/atcoder/B36/B36.php @@ -0,0 +1,96 @@ +