Skip to content

Commit 50ef142

Browse files
committed
leetcode 5. Longest Palindromic Substring
1 parent 9e60650 commit 50ef142

3 files changed

Lines changed: 230 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function longestPalindrome(s: string): string {
2+
if (s.length <= 1) return s;
3+
4+
let start = 0;
5+
let end = 0;
6+
7+
const expandAroundCenter = (left: number, right: number): void => {
8+
while (left >= 0 && right < s.length && s[left] === s[right]) {
9+
left--;
10+
right++;
11+
}
12+
// [left+1, right-1] が現在の回文
13+
if (right - left - 1 > end - start) {
14+
start = left + 1;
15+
end = right - 1;
16+
}
17+
};
18+
19+
for (let i = 0; i < s.length; i++) {
20+
expandAroundCenter(i, i); // 奇数長の回文中心
21+
expandAroundCenter(i, i + 1); // 偶数長の回文中心
22+
}
23+
24+
return s.slice(start, end + 1);
25+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function longestPalindromDP(s: string): string {
2+
const n = s.length;
3+
if (n < 2) return s;
4+
5+
// dp[i][j] = s[i..j] が回文かどうか
6+
const dp: boolean[][] = Array.from({ length: n }, () => Array(n).fill(false));
7+
8+
let start = 0;
9+
let maxLen = 1;
10+
11+
// 1文字は必ず回文
12+
for (let i = 0; i < n; i++) {
13+
dp[i][i] = true;
14+
}
15+
16+
// 長さ2以上の部分文字列を検討
17+
for (let len = 2; len <= n; len++) {
18+
for (let i = 0; i <= n - len; i++) {
19+
const j = i + len - 1;
20+
21+
if (s[i] === s[j]) {
22+
if (len === 2) {
23+
dp[i][j] = true;
24+
} else {
25+
dp[i][j] = dp[i + 1][j - 1];
26+
}
27+
28+
if (dp[i][j] && len > maxLen) {
29+
start = i;
30+
maxLen = len;
31+
}
32+
}
33+
}
34+
}
35+
36+
return s.substring(start, start + maxLen);
37+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
以下は、**TypeScript** を使って「最長回文部分文字列」を求める解法です。中心展開法(expand around center)を使って、**O(N²)** の時間計算量で実装できます。
2+
3+
---
4+
5+
### ✅ コード(TypeScript)
6+
7+
```ts
8+
function longestPalindrome(s: string): string {
9+
if (s.length <= 1) return s;
10+
11+
let start = 0;
12+
let end = 0;
13+
14+
const expandAroundCenter = (left: number, right: number): void => {
15+
while (left >= 0 && right < s.length && s[left] === s[right]) {
16+
left--;
17+
right++;
18+
}
19+
// [left+1, right-1] が現在の回文
20+
if (right - left - 1 > end - start) {
21+
start = left + 1;
22+
end = right - 1;
23+
}
24+
};
25+
26+
for (let i = 0; i < s.length; i++) {
27+
expandAroundCenter(i, i); // 奇数長の回文中心
28+
expandAroundCenter(i, i + 1); // 偶数長の回文中心
29+
}
30+
31+
return s.slice(start, end + 1);
32+
}
33+
```
34+
35+
---
36+
37+
### 🔍 解説
38+
39+
#### 🔸中心展開法とは?
40+
41+
文字列の各インデックス `i` を中心として、左右に文字が対称かどうかを確認して最長の回文を探す方法です。回文には以下の2パターンがあります:
42+
43+
* 奇数長(例: `"aba"`) → 中心は1文字
44+
* 偶数長(例: `"abba"`)→ 中心は2文字
45+
46+
#### 🔸処理フロー図(例: `"babad"`
47+
48+
```
49+
i = 0: 中心 = "b" → "bab" が回文
50+
i = 1: 中心 = "a" → "aba" が回文
51+
i = 2: 中心 = "b" → "bab" が回文
52+
i = 3: 中心 = "a" → "a"
53+
...
54+
最長は "bab" または "aba"
55+
```
56+
57+
---
58+
59+
### 🧪 テスト
60+
61+
```ts
62+
console.log(longestPalindrome("babad")); // "bab" or "aba"
63+
console.log(longestPalindrome("cbbd")); // "bb"
64+
console.log(longestPalindrome("a")); // "a"
65+
console.log(longestPalindrome("ac")); // "a" or "c"
66+
```
67+
68+
---
69+
70+
### ⏱️ 計算量
71+
72+
* 時間計算量:O(N²)
73+
* 空間計算量:O(1)
74+
75+
---
76+
77+
**動的計画法(DP)を用いた TypeScript 実装**
78+
この方法は、部分文字列 `s[i..j]` が回文かどうかを記録し、そこから最長の回文部分文字列を探索します。
79+
80+
---
81+
82+
## ✅ TypeScript 実装(動的計画法)
83+
84+
```ts
85+
function longestPalindrome(s: string): string {
86+
const n = s.length;
87+
if (n < 2) return s;
88+
89+
// dp[i][j] = s[i..j] が回文かどうか
90+
const dp: boolean[][] = Array.from({ length: n }, () => Array(n).fill(false));
91+
92+
let start = 0;
93+
let maxLen = 1;
94+
95+
// 1文字は必ず回文
96+
for (let i = 0; i < n; i++) {
97+
dp[i][i] = true;
98+
}
99+
100+
// 長さ2以上の部分文字列を検討
101+
for (let len = 2; len <= n; len++) {
102+
for (let i = 0; i <= n - len; i++) {
103+
const j = i + len - 1;
104+
105+
if (s[i] === s[j]) {
106+
if (len === 2) {
107+
dp[i][j] = true;
108+
} else {
109+
dp[i][j] = dp[i + 1][j - 1];
110+
}
111+
112+
if (dp[i][j] && len > maxLen) {
113+
start = i;
114+
maxLen = len;
115+
}
116+
}
117+
}
118+
}
119+
120+
return s.substring(start, start + maxLen);
121+
}
122+
```
123+
124+
---
125+
126+
## 🔍 処理概要
127+
128+
### 🧠 dp\[i]\[j] の定義:
129+
130+
* `dp[i][j] = true` なら `s[i..j]` が回文
131+
132+
### 🔄 状態遷移:
133+
134+
* `s[i] !== s[j]``dp[i][j] = false`
135+
* `s[i] === s[j]`
136+
137+
* 長さ2 → `dp[i][j] = true`
138+
* 長さ3以上 → `dp[i][j] = dp[i+1][j-1]`
139+
140+
---
141+
142+
## 🧪 例: `"babad"` の場合
143+
144+
```
145+
i=0, j=2 -> "bab" => dp[0][2] = dp[1][1] = true → 回文
146+
i=1, j=3 -> "aba" => dp[1][3] = dp[2][2] = true → 回文
147+
最長は "bab" または "aba"
148+
```
149+
150+
---
151+
152+
## ⏱️ 計算量
153+
154+
* 時間計算量:`O(N²)`
155+
* 空間計算量:`O(N²)`(2次元配列)
156+
157+
---
158+
159+
## ✅ テスト例
160+
161+
```ts
162+
console.log(longestPalindrome("babad")); // "bab" または "aba"
163+
console.log(longestPalindrome("cbbd")); // "bb"
164+
console.log(longestPalindrome("a")); // "a"
165+
console.log(longestPalindrome("ac")); // "a" または "c"
166+
```
167+
168+
---

0 commit comments

Comments
 (0)