Skip to content

Commit 4515eb4

Browse files
committed
leetcode 20. Valid Parentheses Stack
1 parent 0fe5697 commit 4515eb4

7 files changed

Lines changed: 948 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
2+
---
3+
4+
# 問題の概要:
5+
6+
与えられた配列 `A = [a₁, a₂, ..., aₙ]` から、**いくつかの要素を選んで和を `K` にする**
7+
ただし、**同じ位置の要素は1回まで使える(0-1制約)**
8+
和が作れない場合は `-1` を出力し、
9+
作れる場合は**選んだ要素数が最小となる組み合わせ**を出力します。
10+
11+
---
12+
13+
# アルゴリズム:**動的計画法(0-1ナップサックDP)**
14+
15+
## DPテーブル定義
16+
17+
* `dp[i][k]`
18+
**「最初のi個までの要素から選んで、和をkにするときの最小個数」**
19+
20+
* `prev[i][k]`
21+
**「その状態に遷移する直前の和」**(復元用)
22+
23+
---
24+
25+
# 処理イメージ:図解
26+
27+
## 例題
28+
29+
```
30+
A = [1, 3, 2, 2, 1]
31+
K = 4
32+
```
33+
34+
## DPテーブル構築(状態遷移)
35+
36+
### 初期化
37+
38+
```
39+
dp[0][0] = 0 (和が0のときは0個選ぶ)
40+
dp[0][k≠0] = ∞ (初期は不可能)
41+
```
42+
43+
---
44+
45+
### 配列イメージ(i=0)
46+
47+
| k(和) | 0 | 1 | 2 | 3 | 4 |
48+
| ---------- | - | - | - | - | - |
49+
| dp\[0]\[k] | 0 |||||
50+
51+
---
52+
53+
### i=1(a₁=1)
54+
55+
* **使わない場合:** `dp[1][k] = dp[0][k]`
56+
* **使う場合:** `dp[1][k+1] = min(dp[1][k+1], dp[0][k] + 1)`
57+
58+
| k(和) | 0 | 1 | 2 | 3 | 4 |
59+
| ---------- | - | - | - | - | - |
60+
| dp\[1]\[k] | 0 | 1 ||||
61+
62+
---
63+
64+
### i=2(a₂=3)
65+
66+
* **使わない場合:** dpをそのまま
67+
* **使う場合:**
68+
69+
```
70+
dp[2][3] = min(dp[2][3], dp[1][0]+1) => 1個(3だけ選択)
71+
dp[2][4] = min(dp[2][4], dp[1][1]+1) => 2個(1+3)
72+
```
73+
74+
| k(和) | 0 | 1 | 2 | 3 | 4 |
75+
| ---------- | - | - | - | - | - |
76+
| dp\[2]\[k] | 0 | 1 || 1 | 2 |
77+
78+
---
79+
80+
### i=3(a₃=2)
81+
82+
```
83+
dp[3][2] = min(∞, dp[2][0]+1) => 1個(2)
84+
dp[3][3] = min(1, dp[2][1]+1) => 1個(既にあるのでそのまま)
85+
dp[3][4] = min(2, dp[2][2]+1) => 2個(2+2)
86+
```
87+
88+
| k(和) | 0 | 1 | 2 | 3 | 4 |
89+
| ---------- | - | - | - | - | - |
90+
| dp\[3]\[k] | 0 | 1 | 1 | 1 | 2 |
91+
92+
---
93+
94+
### i=4(a₄=2)
95+
96+
* 同様に更新されるが、`dp[3][k]`が最適なので更新なし(同じ値が入る)
97+
98+
---
99+
100+
### i=5(a₅=1)
101+
102+
* 使わない場合:dp\[4]\[k]をコピー
103+
* 使う場合:
104+
105+
```
106+
dp[5][1] = min(1, dp[4][0]+1) => 1
107+
dp[5][2] = min(1, dp[4][1]+1) => 1
108+
dp[5][3] = min(1, dp[4][2]+1) => 1
109+
dp[5][4] = min(2, dp[4][3]+1) => 2
110+
```
111+
112+
---
113+
114+
# 最終DPテーブル
115+
116+
| k(和) | 0 | 1 | 2 | 3 | 4 |
117+
| ---------- | - | - | - | - | - |
118+
| dp\[5]\[k] | 0 | 1 | 1 | 1 | 2 |
119+
120+
---
121+
122+
# 復元処理(`prev`配列を使う)
123+
124+
* `dp[5][4]=2` →「和4を作るため、`prev[5][4]=3`
125+
* `dp[5][3]=1` →「和3を作るため、`prev[5][3]=1`
126+
127+
### 復元した選択肢:
128+
129+
```
130+
4 → 3 → 1 → 0
131+
```
132+
133+
選んだ数:`3,1`
134+
(順番は逆でも良い)
135+
136+
---
137+
138+
# 処理フロー全体
139+
140+
```plaintext
141+
初期化 dp[0][0]=0
142+
143+
144+
for i=1..N:
145+
for k=0..K:
146+
- 使わない場合 dp[i+1][k]=dp[i][k]
147+
- 使う場合 dp[i+1][k+A[i]]=min(dp[i+1][k+A[i]], dp[i][k]+1)
148+
149+
150+
復元(prev配列から逆追跡)
151+
152+
153+
出力(個数 + 選んだ要素)
154+
```
155+
156+
---
157+
158+
# 図まとめ(状態遷移)
159+
160+
```
161+
i-1
162+
|
163+
┌──┴──┐
164+
使わない 使う
165+
↓ ↓
166+
dp[i][k] dp[i][k+A[i]] = min(dp[i][k+A[i]], dp[i-1][k]+1)
167+
```
168+
169+
---
170+
171+
# 計算量
172+
173+
| 項目 ||
174+
| ----- | -------- |
175+
| 時間計算量 | O(N × K) |
176+
| 空間計算量 | O(N × K) |
177+
178+
---
179+
180+
# メモリ・時間計測
181+
182+
```javascript
183+
const startTime = process.hrtime.bigint();
184+
...(処理)...
185+
const endTime = process.hrtime.bigint();
186+
console.error(`Time: ${(endTime - startTime) / 1e6} ms`);
187+
188+
const used = process.memoryUsage();
189+
console.error(`Memory: ${Math.round(used.heapUsed / 1024)} KB`);
190+
```
191+
192+
---
193+
194+
# まとめ
195+
196+
| 処理 | 内容 |
197+
| ------ | --------------- |
198+
| DP配列 | 和kを作る最小個数を保持 |
199+
| prev配列 | 選んだ要素を復元するための履歴 |
200+
| 復元処理 | 和Kから逆に辿る |
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// ご質問ありがとうございます。
2+
// 「**同じ位置の数字は複数回使えない場合**」は、\*\*部分和問題の典型的な制約付き版(0-1ナップサック問題)\*\*になります。
3+
4+
// ---
5+
6+
// ## 問題の違い:
7+
8+
// | 条件 | 使える数字 |
9+
// | ------ | -------------------------- |
10+
// | **前回** | 同じ数字は何度でも使える(無限個) |
11+
// | **今回** | 同じ\*\*位置の数字は1回だけ使える(0-1制約) |
12+
13+
// ---
14+
15+
// ## 解法:
16+
17+
// * 動的計画法(0-1ナップサック)
18+
// * `dp[i][k]` :最初の i 個の数字から選んで和が k になるときの最小個数
19+
// * **復元用に`prev`配列を持つ**
20+
21+
// ---
22+
23+
// ## コード(Node.js)
24+
25+
// ```javascript
26+
const fs = require('fs');
27+
28+
/**
29+
* 0-1制約付きの最小部分和問題
30+
* @param {number} N - 数列の長さ
31+
* @param {number} K - 目標の和
32+
* @param {number[]} A - 数列A
33+
* @returns {string} - 出力形式の文字列
34+
*/
35+
function minSubsetSum_01(N, K, A) {
36+
const INF = 1e9;
37+
const dp = Array.from({ length: N + 1 }, () => new Array(K + 1).fill(INF));
38+
const prev = Array.from({ length: N + 1 }, () => new Array(K + 1).fill(-1)); // 復元用
39+
40+
dp[0][0] = 0;
41+
42+
for (let i = 0; i < N; i++) {
43+
for (let k = 0; k <= K; k++) {
44+
// i番目の要素を使わない場合
45+
if (dp[i][k] < dp[i + 1][k]) {
46+
dp[i + 1][k] = dp[i][k];
47+
prev[i + 1][k] = k;
48+
}
49+
// i番目の要素を使う場合
50+
if (k + A[i] <= K) {
51+
if (dp[i][k] + 1 < dp[i + 1][k + A[i]]) {
52+
dp[i + 1][k + A[i]] = dp[i][k] + 1;
53+
prev[i + 1][k + A[i]] = k; // A[i]を使ってkからk+A[i]に遷移
54+
}
55+
}
56+
}
57+
}
58+
59+
if (dp[N][K] === INF) {
60+
return '-1\n';
61+
}
62+
63+
// 復元
64+
const res = [];
65+
let k = K;
66+
for (let i = N; i >= 1; i--) {
67+
if (prev[i][k] !== k) { // i番目の要素を使った場合
68+
res.push(A[i - 1]);
69+
k = prev[i][k];
70+
}
71+
}
72+
73+
return `${dp[N][K]}\n${res.join(' ')}\n`;
74+
}
75+
76+
// 入力 & 計測
77+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
78+
const [N, K] = input[0].split(' ').map(Number);
79+
const A = input[1].split(' ').map(Number);
80+
81+
const startTime = process.hrtime.bigint();
82+
const result = minSubsetSum_01(N, K, A);
83+
const endTime = process.hrtime.bigint();
84+
85+
process.stdout.write(result);
86+
87+
// パフォーマンス出力
88+
const used = process.memoryUsage();
89+
console.error(`Time: ${Number(endTime - startTime) / 1e6} ms`);
90+
console.error(`Memory: ${Math.round(used.heapUsed / 1024)} KB`);
91+
// ```
92+
93+
// ---
94+
95+
// ## 入力例と出力
96+
97+
// ### 入力
98+
99+
// ```
100+
// 5 4
101+
// 1 3 2 2 1
102+
// ```
103+
104+
// ### 出力例
105+
106+
// ```
107+
// 2
108+
// 1 3
109+
// ```
110+
111+
// ※順序は任意(`3 1`でもOK)
112+
113+
// ---
114+
115+
// ## 制約まとめ:
116+
117+
// * 各数字は**高々1回しか使えない**(同じ数字が複数あっても、同じ位置は1回だけ)
118+
// * 解法は **0-1ナップサックDP**
119+
120+
// ---
121+
122+
// ## 計算量:
123+
124+
// * 時間計算量:O(N × K)
125+
// * 空間計算量:O(N × K)
126+
127+
// ---
128+
129+
// ## メモリと実行時間:
130+
131+
// * `process.hrtime.bigint()`でナノ秒計測
132+
// * `process.memoryUsage()`でヒープ使用量出力
133+

0 commit comments

Comments
 (0)