Skip to content

Commit de1f1ed

Browse files
committed
leetcode 14. Longest Common Prefix
1 parent 603506c commit de1f1ed

5 files changed

Lines changed: 697 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
**部分和問題(Subset Sum Problem)をDPで解く方法**について、処理の流れを**図解付きで丁寧に解析・説明**します。
2+
3+
---
4+
5+
## 🎯 問題の再掲
6+
7+
> 整数列 `a = [a₁, a₂, ..., aₙ]` の中からいくつかを選んで、ちょうど合計 `K` を作れるか?
8+
> 作れるなら `Yes`、作れなければ `No` を出力。
9+
10+
---
11+
12+
## 🧠 解法の基本(DPテーブル)
13+
14+
### 定義
15+
16+
* `dp[i][j] = true`
17+
**先頭から `i` 個目までの要素を使って、合計 `j` を作ることができるか?**
18+
19+
---
20+
21+
## ✅ 入力例
22+
23+
```
24+
N = 3, K = 6
25+
a = [1, 2, 3]
26+
```
27+
28+
---
29+
30+
## 🪄 初期状態
31+
32+
最初は「**何も選ばなければ合計は0**」のみが成立。
33+
34+
```
35+
dp[0][0] = true
36+
それ以外の dp[0][j] = false (1 <= j <= K)
37+
```
38+
39+
```
40+
i=0:
41+
j → 0 1 2 3 4 5 6
42+
[T F F F F F F]
43+
```
44+
45+
---
46+
47+
## 🔁 1ステップ目:a\[0] = 1 を使う/使わない
48+
49+
前の行 `dp[0][j]` の情報をもとに `dp[1][j]` を更新
50+
51+
* `dp[0][0] = true`
52+
53+
* `dp[1][0] = true`(1を使わない)
54+
* `dp[1][1] = true`(1を使う → 0+1)
55+
56+
```
57+
i=1:
58+
j → 0 1 2 3 4 5 6
59+
[T T F F F F F]
60+
```
61+
62+
---
63+
64+
## 🔁 2ステップ目:a\[1] = 2 を使う/使わない
65+
66+
* `dp[1][0] = true`
67+
68+
*`dp[2][0] = true`(使わない)
69+
*`dp[2][2] = true`(使う)
70+
71+
* `dp[1][1] = true`
72+
73+
*`dp[2][1] = true`(使わない)
74+
*`dp[2][3] = true`(使う)
75+
76+
```
77+
i=2:
78+
j → 0 1 2 3 4 5 6
79+
[T T T T F F F]
80+
```
81+
82+
---
83+
84+
## 🔁 3ステップ目:a\[2] = 3 を使う/使わない
85+
86+
* `dp[2][0] = true`
87+
88+
*`dp[3][0] = true`
89+
*`dp[3][3] = true`
90+
91+
* `dp[2][1] = true`
92+
93+
*`dp[3][1] = true`
94+
*`dp[3][4] = true`
95+
96+
* `dp[2][2] = true`
97+
98+
*`dp[3][2] = true`
99+
*`dp[3][5] = true`
100+
101+
* `dp[2][3] = true`
102+
103+
*`dp[3][3] = true`
104+
*`dp[3][6] = true`**ここが最終的に重要!**
105+
106+
```
107+
i=3:
108+
j → 0 1 2 3 4 5 6
109+
[T T T T T T T]
110+
```
111+
112+
---
113+
114+
## ✅ 最終判定
115+
116+
* `dp[3][6] == true`**Yes**(合計6を作ることが可能)
117+
118+
---
119+
120+
## 🧾 DPテーブル全体の図(3行 × 7列)
121+
122+
```
123+
行: i = 選んだ要素数(0~3)
124+
列: j = 目標合計(0~K)
125+
126+
j → 0 1 2 3 4 5 6
127+
i=0: T F F F F F F
128+
i=1: T T F F F F F
129+
i=2: T T T T F F F
130+
i=3: T T T T T T T
131+
```
132+
133+
---
134+
135+
## 🔄 遷移図(概念)
136+
137+
```txt
138+
[dp[0][0] = true]
139+
├─(使わない)→ dp[1][0]
140+
└─(使う+1) → dp[1][1]
141+
├─(使わない)→ dp[2][1]
142+
└─(使う+2) → dp[2][3]
143+
├─(使わない)→ dp[3][3]
144+
└─(使う+3) → dp[3][6] ← Yes!
145+
```
146+
147+
---
148+
149+
## 💡 注意点
150+
151+
* `a[i]` は 1〜100 なので、`K=2000` のときでも範囲内に収まる。
152+
* 状態数は `N × K`、最大でも `20 × 2000 = 40000` なので余裕。
153+
154+
---
155+
156+
## 🔚 結論
157+
158+
* DPテーブル `dp[i][j]` により部分和を段階的に構築。
159+
* 最終的な `dp[N][K]` を見て Yes/No を判定。
160+
* 各ステップで「選ぶ or 選ばない」の2択を網羅的に考える。
161+
162+
---
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// はい、\*\*DP(動的計画法)\*\*を用いた部分和問題の解法について説明し、**Node.js (JavaScript) + `fs`入力対応版**でコードを提供します。
2+
3+
// ---
4+
5+
// ## 🧠 基本方針(DP)
6+
7+
// ### 問題
8+
9+
// 与えられた整数列 `a = [a1, a2, ..., aN]` の中からいくつかの要素を選んで、その合計を `K` にできるか判定する。
10+
11+
// ---
12+
13+
// ## 🧮 DPの定義
14+
15+
// 以下のような2次元配列 `dp[i][j]` を定義します:
16+
17+
// > `dp[i][j] = true` とは、
18+
// > 「先頭から `i` 個目までの要素の中から選んで合計が `j` にできる」ことを意味します。
19+
20+
// ---
21+
22+
// ## 🧩 遷移
23+
24+
// 1. 初期状態:
25+
26+
// ```js
27+
// dp[0][0] = true
28+
// ```
29+
30+
// 2. 遷移式(各a\[i]を使うか使わないか):
31+
32+
// ```js
33+
// dp[i+1][j] |= dp[i][j] // a[i] を使わない場合
34+
// dp[i+1][j+a[i]] |= dp[i][j] // a[i] を使う場合(j+a[i] <= K のときのみ)
35+
// ```
36+
37+
// ---
38+
39+
// ## 💻 JavaScript実装(Node.js + fs入力)
40+
41+
// ```javascript
42+
const fs = require('fs');
43+
44+
// 入力の読み込み
45+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
46+
const [N, K] = input[0].split(' ').map(Number);
47+
const A = input[1].split(' ').map(Number);
48+
49+
/**
50+
* DPを用いて部分和がKになるかを判定する関数
51+
* @param {number[]} a - 入力の整数列
52+
* @param {number} k - 目標とする合計値
53+
* @returns {string} - "Yes" または "No"
54+
*/
55+
function isSubsetSumDP(a, k) {
56+
const n = a.length;
57+
58+
// dp[i][j] = i番目まで見てjを作れるか
59+
const dp = Array.from({ length: n + 1 }, () => Array(k + 1).fill(false));
60+
dp[0][0] = true; // 何も選ばなければ和は0
61+
62+
for (let i = 0; i < n; i++) {
63+
for (let j = 0; j <= k; j++) {
64+
if (dp[i][j]) {
65+
dp[i + 1][j] = true; // a[i]を使わない
66+
if (j + a[i] <= k) {
67+
dp[i + 1][j + a[i]] = true; // a[i]を使う
68+
}
69+
}
70+
}
71+
}
72+
73+
return dp[n][k] ? "Yes" : "No";
74+
}
75+
76+
// 実行と出力
77+
console.log(isSubsetSumDP(A, K));
78+
// ```
79+
80+
// ---
81+
82+
// ## 🧠 時間・空間計算量
83+
84+
// * **時間計算量**:`O(N × K)`
85+
// 最大で `20 × 2000 = 40000` → 非常に高速
86+
// * **空間計算量**:`O(N × K)` → 2次元配列を用いたため `~160,000` 個のboolean
87+
88+
// ※ `dp[i][j]` の「`i`が不要(直前しか見ない)」なので、`1次元配列`で `O(K)` に圧縮可能です。
89+
90+
// ---
91+
92+
// ## ✅ 入力例と出力例
93+
94+
// 入力:
95+
96+
// ```
97+
// 3 6
98+
// 1 2 3
99+
// ```
100+
101+
// 出力:
102+
103+
// ```
104+
// Yes
105+
// ```
106+
107+
// ---
108+
109+
// ## ✅ 圧縮版(1次元配列で高速化)
110+
111+
// 1次元配列にしてさらにメモリを削減したい場合:
112+
113+
// ```js
114+
function isSubsetSumDP1D(a, k) {
115+
let dp = Array(k + 1).fill(false);
116+
dp[0] = true;
117+
118+
for (const val of a) {
119+
const next = [...dp]; // 今回の遷移後の配列をコピー
120+
for (let j = 0; j <= k - val; j++) {
121+
if (dp[j]) next[j + val] = true;
122+
}
123+
dp = next;
124+
}
125+
126+
return dp[k] ? "Yes" : "No";
127+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# LeetCode のテストケースは
2+
# `class Solution` にメソッドを定義する形式が必要です。
3+
4+
# なので、次のように修正します:
5+
6+
# ## ✅ LeetCode形式(`class Solution`)
7+
8+
# ```python
9+
import time
10+
import tracemalloc
11+
from typing import List, Tuple
12+
13+
class Solution:
14+
def longestCommonPrefix(self, strs: List[str]) -> str:
15+
"""
16+
LeetCode用 最長共通接頭辞関数
17+
18+
:param strs: List[str] - 文字列のリスト
19+
:return: str - 最長共通接頭辞。共通部分がなければ空文字。
20+
"""
21+
if not strs:
22+
return ""
23+
24+
prefix = strs[0]
25+
26+
for s in strs[1:]:
27+
while not s.startswith(prefix):
28+
prefix = prefix[:-1]
29+
if not prefix:
30+
return ""
31+
return prefix
32+
33+
def run_with_performance_log(strs: List[str]) -> Tuple[str, float, int]:
34+
"""
35+
処理時間とメモリ消費量を計測し、最長共通接頭辞を求める
36+
37+
:param strs: List[str] - 文字列リスト
38+
:return: Tuple[str, float, int]
39+
- 最長共通接頭辞(str)
40+
- 処理時間(ミリ秒、float)
41+
- メモリ消費量(KB、int)
42+
"""
43+
solution = Solution()
44+
45+
tracemalloc.start()
46+
start_time = time.perf_counter()
47+
48+
result = solution.longestCommonPrefix(strs)
49+
50+
end_time = time.perf_counter()
51+
_, peak = tracemalloc.get_traced_memory()
52+
tracemalloc.stop()
53+
54+
time_elapsed_ms = (end_time - start_time) * 1000 # ミリ秒
55+
memory_usage_kb = peak // 1024 # KB単位に変換
56+
57+
return result, time_elapsed_ms, memory_usage_kb
58+
59+
# サンプル実行
60+
if __name__ == "__main__":
61+
test_cases = [
62+
["flower", "flow", "flight"],
63+
["dog", "racecar", "car"]
64+
]
65+
66+
for i, case in enumerate(test_cases, 1):
67+
result, time_ms, mem_kb = run_with_performance_log(case)
68+
print(f"Test Case {i}: {case}")
69+
print(f"Longest Common Prefix: '{result}'")
70+
print(f"Time: {time_ms:.3f} ms")
71+
print(f"Memory: {mem_kb} KB")
72+
print("=" * 40)
73+
# ```
74+
75+
# ---
76+
77+
# ## ✅ 実行結果(例)
78+
79+
# ```
80+
# Test Case 1: ['flower', 'flow', 'flight']
81+
# Longest Common Prefix: 'fl'
82+
# Time: 0.030 ms
83+
# Memory: 0 KB
84+
# ========================================
85+
# Test Case 2: ['dog', 'racecar', 'car']
86+
# Longest Common Prefix: ''
87+
# Time: 0.025 ms
88+
# Memory: 0 KB
89+
# ========================================
90+
# ```
91+
92+
# ---
93+
94+
# ## LeetCode用注意点:
95+
96+
# * `Solution`クラスにメソッドを書く必要があります。
97+
# * 引数や戻り値は LeetCode標準形式。
98+
99+
# ---

0 commit comments

Comments
 (0)