Skip to content

Commit 8ff3d5b

Browse files
authored
Merge pull request #3 from o-p/0005
Add #5 Longest Palindromic Substring
2 parents 67faac1 + 7fb1410 commit 8ff3d5b

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../problems/0005-longest-palindromic-substring
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../problems/0005-longest-palindromic-substring

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
2+
"name": "practice-leetcode",
3+
"license": "MIT",
4+
"author": {
5+
"name": "Chris Chu",
6+
"email": "op.github.io@gmail.com",
7+
"url": "https://github.com/o-p"
8+
},
29
"devDependencies": {
310
"@babel/core": "^7.12.9",
411
"@babel/preset-env": "^7.12.7",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lps from './longest-palindromic-substring';
2+
3+
test.each`
4+
s | expected
5+
${'babad'} | ${'bab'} }
6+
${'cbbd'} | ${'bb'}
7+
${'a'} | ${'a'}
8+
${'ac'} | ${'a'}
9+
${'aacabdkacaa'} | ${'aca'}
10+
`('Longest Palindromic Substring: ($s)', ({ s, expected }) =>
11+
expect(lps(s)).toBe(expected)
12+
);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// 1. 會有兩種可能狀況, 以 abc 為例
2+
// > abcba
3+
// > abccba
4+
//
5+
// 2. 跟 #3 不同, 沒辦法在找到最近的 match 後就放棄多餘的部分
6+
//
7+
// 3. 可能方案 => 從最長字串開始向內查 雙數測 abccba 單數測 abcba
8+
//
9+
// 4. 頻繁把字串反轉比較慢, 所以先把字串反轉好一份等著比對
10+
//
11+
// 5. 流程:
12+
// - 決定鏡像的位置
13+
// - 從鏡像位置開始最長可容忍長度開始向內比對
14+
15+
// @lc code=start
16+
const longestPalindrome = stepsAndMirror;
17+
18+
// @lc code=end
19+
20+
// [FAILED TRIAL]
21+
// 1. 先翻轉一次 cbbd => dbbc
22+
// 2. 正向跟反向交錯:
23+
// ...cbbd ....cbbd ....cbbd ....cbbd ....cbbd ....cbbd ....cbbd
24+
// dbbc... ..dbbc.. ...dbbc. ....dbbc .....dbb ......db .......d
25+
// _______ ________ ________ ________ ________ ________ ________
26+
// ...c... ........ .....b.. .....bb. ......b. ........ .......d
27+
//
28+
// 結論: 這種做法會死在反轉字串相同但中間有間隔的 case:
29+
// - 正向: aacabdkacaa
30+
// - 反向: aacakdbacaa
31+
// 得到 aaca 但應該要是 aca
32+
//
33+
// 後記: 這個 case 會遇到的問題在討論中 Approach 1: Longest Common Substring 的 common mistake
34+
function reverseThenMatch(s: string): string {
35+
function findMatches(a: Array<string|null>, b: Array<string|null>): Array<string|null> {
36+
return a.map((char, i): string|null =>
37+
typeof char === 'string'
38+
&& typeof b[i] === 'string'
39+
&& char === b[i] ? char : null
40+
)
41+
}
42+
43+
function longestMatches(a: Array<string|null>, b: Array<string|null>): string {
44+
return findMatches(a, b).reduce(({ longest, current }, value: string|null) => {
45+
if (typeof value === 'string') {
46+
current += value;
47+
return {
48+
longest: current.length > longest.length ? current : longest,
49+
current,
50+
}
51+
}
52+
return {
53+
longest,
54+
current: '',
55+
}
56+
}, { longest: '', current: '' })['longest']
57+
}
58+
59+
function recursiveFindLongest(a: Array<string|null>, b: Array<string|null>, longest = '') {
60+
if (a.length) {
61+
const matches = longestMatches(a, b)
62+
return recursiveFindLongest(
63+
a.slice(1),
64+
b,
65+
matches.length > longest.length ? matches : longest
66+
)
67+
}
68+
return longest
69+
}
70+
71+
const forward = [...Array(s.length).fill(null), ...s]
72+
return recursiveFindLongest(forward, [...forward].reverse())
73+
}
74+
75+
// 1. 位移鏡像點,逐格前進
76+
// 2. 透過鏡像點,回傳最長字串
77+
//
78+
// c b b d c b b d c b b d c b b d c b b d c b b d c b b d
79+
// ^ ^ ^ ^ ^ ^ ^
80+
// _______ _______ _______ _______ _______ _______ _______
81+
// c b b b b d
82+
//
83+
// 3. 結論: pass, 但效能不彰
84+
// - 速度 26.71%
85+
// - 記憶 5.59%
86+
// 思考 - 完全沒用到 cache 等常見方式
87+
//
88+
// 後記: 這後來翻討論, 應該類似 Approach 4: Expand Around Center
89+
// 效率是 O(n^2), 但只需要固定記憶體空間, 這邊不確定是否實作有問題...
90+
function stepsAndMirror(s: string, position: number = 0, longest: string = ''): string {
91+
if (position > s.length) {
92+
return longest
93+
}
94+
95+
const mergeMirroredChars = (str: string, pos: number, distance: number = 0, word: string = ''): string => {
96+
const index1 = Math.floor(pos - distance)
97+
const index2 = Math.ceil(pos + distance)
98+
99+
if (index1 >= 0 && index2 < str.length) {
100+
const first = str[index1];
101+
const second = str[index2]
102+
if (index1 === index2) return mergeMirroredChars(str, pos, distance + 1, first)
103+
if (first === second) return mergeMirroredChars(str, pos, distance + 1, `${first}${word}${second}`)
104+
}
105+
return word
106+
}
107+
108+
const word = mergeMirroredChars(s, position)
109+
return stepsAndMirror(s, position + .5, word.length > longest.length ? word : longest);
110+
}
111+
112+
export default longestPalindrome;

0 commit comments

Comments
 (0)