Skip to content

Commit a2151f1

Browse files
committed
✨ feat: implement '@algorithm.ts/manacher'
1 parent b617ec5 commit a2151f1

File tree

8 files changed

+280
-0
lines changed

8 files changed

+280
-0
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Package | Description
6565
[@algorithm.ts/isap][] | ISAP algorithm. #MaxFlow, #NetworkFlow.
6666
[@algorithm.ts/knuth-shuffle][] | Knuth-Shuffle algorithm.
6767
[@algorithm.ts/lower-bound][] | Find the index of first elements which greater or equals than the target element.
68+
[@algorithm.ts/manacher][] | The manacher algorithm for solving palindrome string problems.
6869
[@algorithm.ts/mcmf][] | MCMF algorithm. #MinCostMaxFlow, #NetworkFlow.
6970
[@algorithm.ts/priority-queue][] | Priority Queue (heap).
7071
[@algorithm.ts/sliding-window][] | Sliding window algorithm.
@@ -90,6 +91,7 @@ algorithm.ts is [MIT licensed](https://github.com/guanghechen/algorithm.ts/blob/
9091
[@algorithm.ts/isap]: ./packages/isap
9192
[@algorithm.ts/knuth-shuffle]: ./packages/knuth-shuffle
9293
[@algorithm.ts/lower-bound]: ./packages/lower-bound
94+
[@algorithm.ts/manacher]: ./packages/manacher
9395
[@algorithm.ts/mcmf]: ./packages/mcmf
9496
[@algorithm.ts/priority-queue]: ./packages/priority-queue
9597
[@algorithm.ts/sliding-window]: ./packages/sliding-window

Diff for: packages/manacher/README.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<header>
2+
<h1 align="center">
3+
<a href="https://github.com/guanghechen/algorithm.ts/tree/main/packages/manacher#readme">@algorithm.ts/manacher</a>
4+
</h1>
5+
<div align="center">
6+
<a href="https://www.npmjs.com/package/@algorithm.ts/manacher">
7+
<img
8+
alt="Npm Version"
9+
src="https://img.shields.io/npm/v/@algorithm.ts/manacher.svg"
10+
/>
11+
</a>
12+
<a href="https://www.npmjs.com/package/@algorithm.ts/manacher">
13+
<img
14+
alt="Npm Download"
15+
src="https://img.shields.io/npm/dm/@algorithm.ts/manacher.svg"
16+
/>
17+
</a>
18+
<a href="https://www.npmjs.com/package/@algorithm.ts/manacher">
19+
<img
20+
alt="Npm License"
21+
src="https://img.shields.io/npm/l/@algorithm.ts/manacher.svg"
22+
/>
23+
</a>
24+
<a href="#install">
25+
<img
26+
alt="Module Formats: cjs, esm"
27+
src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
28+
/>
29+
</a>
30+
<a href="https://github.com/nodejs/node">
31+
<img
32+
alt="Node.js Version"
33+
src="https://img.shields.io/node/v/@algorithm.ts/manacher"
34+
/>
35+
</a>
36+
<a href="https://github.com/facebook/jest">
37+
<img
38+
alt="Tested with Jest"
39+
src="https://img.shields.io/badge/tested_with-jest-9c465e.svg"
40+
/>
41+
</a>
42+
<a href="https://github.com/prettier/prettier">
43+
<img
44+
alt="Code Style: prettier"
45+
src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
46+
/>
47+
</a>
48+
</div>
49+
</header>
50+
<br/>
51+
52+
53+
A typescript implementation of the **manacher** algorithm.
54+
55+
Manacher is a linear time algorithm for listing all the palindromes that appear
56+
at the start of a given string.
57+
58+
If you are interested in this algorithm, you can check [here][manacher].
59+
60+
61+
## Install
62+
63+
* npm
64+
65+
```bash
66+
npm install --save @algorithm.ts/manacher
67+
```
68+
69+
* yarn
70+
71+
```bash
72+
yarn add @algorithm.ts/manacher
73+
```
74+
75+
* deno
76+
77+
```typescript
78+
import manacher from 'https://raw.githubusercontent.com/guanghechen/algorithm.ts/main/packages/manacher/src/index.ts'
79+
```
80+
81+
## Usage
82+
83+
* A solution of https://leetcode.com/problems/palindrome-partitioning-ii/
84+
85+
```typescript
86+
import manacher from '@algorithm.ts/manacher'
87+
88+
export function minCut(s: string): number {
89+
const N: number = s.length
90+
const f: number[] = manacher(s)
91+
const dp: Uint32Array = new Uint32Array(N)
92+
93+
for (let i = 1; i < N; ++i) {
94+
let answer: number = i < f[i] * 2 ? 0 : dp[i - 1] + 1
95+
if (answer > 0) {
96+
for (let k = 1; k < i; ++k) {
97+
if (i - k < f[i + k] * 2) {
98+
answer = Math.min(answer, dp[k - 1] + 1)
99+
}
100+
}
101+
}
102+
dp[i] = answer
103+
}
104+
return dp[N - 1]
105+
}
106+
```
107+
108+
109+
## Related
110+
111+
* [最长回文子串 Manacher 算法][manacher]
112+
113+
114+
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/main/packages/manacher#readme
115+
[manacher]: https://me.guanghechen.com/post/algorithm/string/manacher/

Diff for: packages/manacher/__test__/manacher.spec.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import manacher from '../src'
2+
3+
test('manacher', function () {
4+
const data: Array<{
5+
input: Parameters<typeof solution1>
6+
answer: ReturnType<typeof solution1>
7+
}> = [
8+
{
9+
input: ['aab'],
10+
answer: 1,
11+
},
12+
{
13+
input: ['a'],
14+
answer: 0,
15+
},
16+
{
17+
input: ['ab'],
18+
answer: 1,
19+
},
20+
{
21+
input: ['abaacaada'],
22+
answer: 2,
23+
},
24+
{
25+
input: [
26+
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
27+
],
28+
answer: 1,
29+
},
30+
]
31+
32+
for (const { input, answer } of data) {
33+
expect(solution1(...input)).toEqual(answer)
34+
}
35+
})
36+
37+
/**
38+
* @see https://leetcode.com/problems/palindrome-partitioning-ii
39+
*/
40+
function solution1(text: string): number {
41+
const N: number = text.length
42+
const f: number[] = manacher(text)
43+
const dp: Uint32Array = new Uint32Array(N)
44+
45+
for (let i = 1; i < N; ++i) {
46+
let answer: number = i < f[i] * 2 ? 0 : dp[i - 1] + 1
47+
if (answer > 0) {
48+
for (let k = 1; k < i; ++k) {
49+
if (i - k < f[i + k] * 2) {
50+
answer = Math.min(answer, dp[k - 1] + 1)
51+
}
52+
}
53+
}
54+
dp[i] = answer
55+
}
56+
return dp[N - 1]
57+
}

Diff for: packages/manacher/package.json

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@algorithm.ts/manacher",
3+
"version": "1.0.18",
4+
"description": "The manacher algorithm for solving palindrome string problems",
5+
"author": {
6+
"name": "guanghechen",
7+
"url": "https://github.com/guanghechen/"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/guanghechen/algorithm.ts.git",
12+
"directory": "packages/manacher"
13+
},
14+
"homepage": "https://github.com/guanghechen/algorithm.ts/tree/main/packages/manacher#readme",
15+
"keywords": [
16+
"algorithm",
17+
"manacher",
18+
"palindrome"
19+
],
20+
"main": "lib/cjs/index.js",
21+
"module": "lib/esm/index.js",
22+
"types": "lib/types/index.d.ts",
23+
"source": "src/index.ts",
24+
"license": "MIT",
25+
"engines": {
26+
"node": ">= 14.15.0"
27+
},
28+
"files": [
29+
"lib/",
30+
"!lib/**/*.js.map",
31+
"!lib/**/*.d.ts.map",
32+
"package.json",
33+
"CHANGELOG.md",
34+
"LICENSE",
35+
"README.md"
36+
],
37+
"scripts": {
38+
"build": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js",
39+
"prebuild": "rimraf lib/ && tsc -p tsconfig.src.json --emitDeclarationOnly",
40+
"prepublishOnly": "cross-env ROLLUP_SHOULD_SOURCEMAP=false yarn build",
41+
"test": "cross-env TS_NODE_FILES=true jest --config ../../jest.config.js --rootDir ."
42+
}
43+
}

Diff for: packages/manacher/src/index.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Find the longest palindrome length centered at each position in the given
3+
* text string within the complexity of $O(N)$.
4+
*
5+
* Note: len is an array with a length of $2N-1$, which represents the radius
6+
* of the palindrome:
7+
*
8+
* - len[2i] is equal to the radius of the longest palindrome centered at
9+
* the position (i, i)
10+
*
11+
* - len[2i+1] is equal to the radius of the longest palindrome centered at
12+
* the position (i, i+1)
13+
*
14+
* ## Examples
15+
*
16+
* 'abbab'
17+
*
18+
* ==>
19+
*
20+
* len[3] = 2 // abba
21+
* len[6] = 2 // bab
22+
*
23+
* @param text
24+
* @param len
25+
* @param N
26+
*
27+
* @see https://me.guanghechen.com/post/algorithm/string/manacher/
28+
*/
29+
export function manacher(text: string): number[] {
30+
const N = text.length
31+
const _size = N * 2 - 1
32+
const len: number[] = new Array(_size)
33+
34+
len[0] = 1
35+
for (let i = 1, j = 0; i < _size; ++i) {
36+
const p: number = i >> 1
37+
const q: number = i - p
38+
const r: number = ((j + 1) >> 1) + len[j] - 1
39+
40+
let L = r < q ? 0 : Math.min(r - q + 1, len[(j << 1) - i])
41+
while (p > L - 1 && q + L < N && text[p - L] === text[q + L]) L += 1
42+
len[i] = L
43+
44+
// Update j
45+
if (q + L - 1 > r) j = i
46+
}
47+
return len
48+
}
49+
50+
export default manacher

Diff for: packages/manacher/tsconfig.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../tsconfig",
3+
"include": ["src", "script", "__test__"]
4+
}

Diff for: packages/manacher/tsconfig.src.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.settings",
3+
"compilerOptions": {
4+
"declarationDir": "lib/types",
5+
"rootDir": "src"
6+
},
7+
"include": ["src"]
8+
}

Diff for: tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@algorithm.ts/isap": ["packages/isap/src"],
1717
"@algorithm.ts/knuth-shuffle": ["packages/knuth-shuffle/src"],
1818
"@algorithm.ts/lower-bound": ["packages/lower-bound/src"],
19+
"@algorithm.ts/manacher": ["packages/manacher/src"],
1920
"@algorithm.ts/mcmf": ["packages/mcmf/src"],
2021
"@algorithm.ts/priority-queue": ["packages/priority-queue/src"],
2122
"@algorithm.ts/sliding-window": ["packages/sliding-window/src"],

0 commit comments

Comments
 (0)