From eaf6692b4438171b4d096ece3995b26187313ccb Mon Sep 17 00:00:00 2001 From: Shyam-Chen Date: Thu, 17 Oct 2024 09:54:18 +0800 Subject: [PATCH] 223rd Commit --- README.md | 25 ++- algorithms/dynamic-programming/README.md | 52 ++++- algorithms/linked-list/CircularLinkedList.ts | 45 ++++ algorithms/linked-list/LinkedList.ts | 47 ++++ algorithms/linked-list/ListNode.ts | 9 + algorithms/linked-list/README.md | 207 ++++++++++++++++++ .../{binary-search => searching}/README.md | 0 .../binarySearch.ts | 0 .../283. Move Zeroes/moveZeroes.bench.ts | 22 -- .../283. Move Zeroes/moveZeroes.test.ts | 12 +- src/page-3/283. Move Zeroes/moveZeroes.ts | 32 +-- 11 files changed, 385 insertions(+), 66 deletions(-) create mode 100644 algorithms/linked-list/CircularLinkedList.ts create mode 100644 algorithms/linked-list/LinkedList.ts create mode 100644 algorithms/linked-list/ListNode.ts create mode 100644 algorithms/linked-list/README.md rename algorithms/{binary-search => searching}/README.md (100%) rename algorithms/{binary-search => searching}/binarySearch.ts (100%) delete mode 100644 src/page-3/283. Move Zeroes/moveZeroes.bench.ts diff --git a/README.md b/README.md index 1519af3..7f85964 100644 --- a/README.md +++ b/README.md @@ -66,19 +66,34 @@ $ pnpm bench twoSum.bench.ts 解題前應該學會的思路與技巧 -- 鏈結串列 (Linked List) +- [鏈結串列 (Linked List)](./algorithms/linked-list/README.md) - 堆疊 (Stack) - 佇列 (Queue) - 雜湊表 (Hash Table) +- 堆積 (Heap) - 樹 (Tree) - 二元樹 (Binary Tree) - 二元搜尋樹 (Binary Search Tree) -- 堆積 (Heap) - [圖 (Graph)](./algorithms/graph/README.md) -- [二分搜尋 (Binary Search)](./algorithms/binary-search/README.md) -- 回溯 (Backtracking) +- 字典樹 (Trie) +- 排序 (Sorting) + - 冒泡排序 (Bubble Sort) + - 選擇排序 (Selection Sort) + - 插入排序 (Insertion Sort) + - 合併排序 (Merge Sort) + - 快速排序 (Quick Sort) + - 堆積排序 (Heap Sort) +- 搜尋 (Searching) + - [二分搜尋 (Binary Search)](./algorithms/searching/README.md) - 動態規劃 (Dynamic Programming) - - [0-1 背包問題 (0-1 Knapsack Problem)](./algorithms/dynamic-programming/README.md) + - [0-1 背包問題 (0-1 Knapsack Problem)](./algorithms/dynamic-programming/README.md#0-1-背包問題-0-1-knapsack-problem) + - 完全背包問題 (Unbounded Knapsack Problem) + - 多維背包問題 (Multidimensional Knapsack Problem) +- 貪婪 (Greedy) + - 分數背包問題 (Fractional Knapsack Problem) +- 回溯 (Backtracking) +- 分治 (Divide and Conquer) +- 位元操作 (Bit Manipulation) ## Basic - LeetCode 75 diff --git a/algorithms/dynamic-programming/README.md b/algorithms/dynamic-programming/README.md index 1b57d1b..fcc3c41 100644 --- a/algorithms/dynamic-programming/README.md +++ b/algorithms/dynamic-programming/README.md @@ -1,5 +1,34 @@ # 動態規劃 (Dynamic Programming) +費波那契數:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144... + +```ts +F[0] = 0; +F[1] = 1; +if (n >= 2) F[n] = F[n - 1] + F[n - 2]; +``` + +```ts +function fibonacci(n: number): number { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} +``` + +一維動態規劃 (1D Dynamic Programming) + +```ts +function fibonacci(n: number): number { + const dp = [0, 1]; + + for (let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + + return dp[n]; +} +``` + ## 0-1 背包問題 (0-1 Knapsack Problem) 問題:給定 n 個物品,每個物品有一定的重量和價值,並給定一個背包的容量。你需要在不超過背包容量的情況下選擇物品,使得所選物品的總價值最大。 @@ -9,6 +38,20 @@ - `values[]`: 物品的價值 - `capacity`: 背包的容量 +每個物品的編號 (`i`) 及其對應的重量和價值: + +| i | weights[i - 1] | values[i - 1] | +| --: | -------------: | ------------: | +| 1 | 1 | 5 | +| 2 | 2 | 11 | +| 3 | 3 | 15 | + +遞推公式: + +```ts +dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - weights[i - 1]] + values[i - 1]); +``` + ```ts function knapsack(weights: number[], values: number[], capacity: number): number { const n = weights.length; @@ -19,7 +62,6 @@ function knapsack(weights: number[], values: number[], capacity: number): number for (let i = 1; i <= n; i++) { for (let c = 0; c <= capacity; c++) { if (weights[i - 1] <= c) { - // 遞推公式 dp[i][c] = Math.max( dp[i - 1][c], // 不選擇物品 dp[i - 1][c - weights[i - 1]] + values[i - 1], // 選擇物品 @@ -61,3 +103,11 @@ const values = [5, 11, 15]; const capacity = 4; console.log(knapsack(weights, values, capacity)); // 20 ``` + +## 完全背包問題 (Unbounded Knapsack Problem) + +遞推公式: + +```ts +dp[c] = Math.max(dp[c], dp[c - weights[i - 1]] + values[i - 1]); +``` diff --git a/algorithms/linked-list/CircularLinkedList.ts b/algorithms/linked-list/CircularLinkedList.ts new file mode 100644 index 0000000..34dea10 --- /dev/null +++ b/algorithms/linked-list/CircularLinkedList.ts @@ -0,0 +1,45 @@ +import { ListNode } from './ListNode'; + +export class CircularLinkedList { + head: ListNode | null; + + constructor() { + this.head = null; + } + + // 在尾端新增節點並形成環狀鏈結串列 + append(value: T): void { + const newNode = new ListNode(value); + + if (this.head === null) { + // 如果是第一個節點,將其設為頭節點,並指向自己 + this.head = newNode; + newNode.next = this.head; // 環狀結構 + } else { + let current = this.head; + + // 找到最後一個節點(指向頭節點的節點) + while (current.next !== null && current.next !== this.head) { + current = current.next; + } + + current.next = newNode; + newNode.next = this.head; // 使其成為環狀結構 + } + } + + // 印出環狀鏈結串列 + print(): void { + if (this.head === null) return; + + let current = this.head; + const values: T[] = []; + + do { + values.push(current.value); + current = current.next as ListNode; + } while (current !== this.head); + + console.log(`${values.join(' -> ')} -> (回到頭)`); + } +} diff --git a/algorithms/linked-list/LinkedList.ts b/algorithms/linked-list/LinkedList.ts new file mode 100644 index 0000000..3bfd9d4 --- /dev/null +++ b/algorithms/linked-list/LinkedList.ts @@ -0,0 +1,47 @@ +import { ListNode } from './ListNode'; + +export class LinkedList { + head: ListNode | null; + + constructor() { + this.head = null; + } + + // 新增節點到鏈結串列的尾端 + append(value: T): void { + const newNode = new ListNode(value); + + if (this.head === null) { + // 如果鏈結串列為空,則將新節點設為頭節點 + this.head = newNode; + } else { + let current = this.head; + + while (current.next !== null) { + current = current.next; + } + + current.next = newNode; + } + } + + // 將節點插入到鏈結串列的開頭 + prepend(value: T): void { + const newNode = new ListNode(value); + newNode.next = this.head; + this.head = newNode; + } + + // 印出鏈結串列中的所有值 + print(): void { + let current = this.head; + const values: T[] = []; + + while (current !== null) { + values.push(current.value); + current = current.next; + } + + console.log(values.join(' -> ')); + } +} diff --git a/algorithms/linked-list/ListNode.ts b/algorithms/linked-list/ListNode.ts new file mode 100644 index 0000000..e4316cc --- /dev/null +++ b/algorithms/linked-list/ListNode.ts @@ -0,0 +1,9 @@ +export class ListNode { + value: T; + next: ListNode | null; + + constructor(value: T, next: ListNode | null = null) { + this.value = value; + this.next = next; + } +} diff --git a/algorithms/linked-list/README.md b/algorithms/linked-list/README.md new file mode 100644 index 0000000..ef32850 --- /dev/null +++ b/algorithms/linked-list/README.md @@ -0,0 +1,207 @@ +# 鏈結串列 (Linked List) + +## 單向鏈結串列 (Singly Linked List) + +```ts +class ListNode { + value: T; + next: ListNode | null; + + constructor(value: T, next: ListNode | null = null) { + this.value = value; + this.next = next; + } +} +``` + +```ts +const A = new ListNode('A'); +const B = new ListNode('B'); +const C = new ListNode('C'); +const D = new ListNode('D'); +const E = new ListNode('E'); + +A.next = B; +B.next = C; +C.next = D; +D.next = E; + +const values: string[] = []; + +let current: ListNode | null = A; + +while (current !== null) { + values.push(current.value); + current = current.next; +} + +console.log(values.join(' -> ')); +// A -> B -> C -> D -> E +``` + +```mermaid +flowchart LR + A((A)) + B((B)) + C((C)) + D((D)) + E((E)) + + A --> B + B --> C + C --> D + D --> E +``` + +定義鏈結串列類別: + +```ts +class LinkedList { + head: ListNode | null; + + constructor() { + this.head = null; + } + + // 新增節點到鏈結串列的尾端 + append(value: T): void { + const newNode = new ListNode(value); + + if (this.head === null) { + // 如果鏈結串列為空,則將新節點設為頭節點 + this.head = newNode; + } else { + let current = this.head; + + while (current.next !== null) { + current = current.next; + } + + current.next = newNode; + } + } + + // 將節點插入到鏈結串列的開頭 + prepend(value: T): void { + const newNode = new ListNode(value); + newNode.next = this.head; + this.head = newNode; + } + + // 印出鏈結串列中的所有值 + print(): void { + let current = this.head; + const values: T[] = []; + + while (current !== null) { + values.push(current.value); + current = current.next; + } + + console.log(values.join(' -> ')); + } +} +``` + +```ts +const linkedList = new LinkedList(); + +linkedList.append('A'); +linkedList.append('B'); +linkedList.append('C'); +linkedList.prepend('D'); + +linkedList.print(); +// D -> A -> B -> C +``` + +```mermaid +flowchart LR + A((A)) + B((B)) + C((C)) + D((D)) + + A --> B + B --> C + D --> A +``` + +## 環狀鏈結串列 (Circular Linked List) + +```ts +class CircularLinkedList { + head: ListNode | null; + + constructor() { + this.head = null; + } + + // 在尾端新增節點並形成環狀鏈結串列 + append(value: T): void { + const newNode = new ListNode(value); + + if (this.head === null) { + // 如果是第一個節點,將其設為頭節點,並指向自己 + this.head = newNode; + newNode.next = this.head; // 環狀結構 + } else { + let current = this.head; + + // 找到最後一個節點(指向頭節點的節點) + while (current.next !== null && current.next !== this.head) { + current = current.next; + } + + current.next = newNode; + newNode.next = this.head; // 使其成為環狀結構 + } + } + + // 印出環狀鏈結串列 + print(): void { + if (this.head === null) return; + + let current = this.head; + const values: T[] = []; + + do { + values.push(current.value); + current = current.next! as ListNode; + } while (current !== this.head); + + console.log(`${values.join(' -> ')} -> (回到頭)`); + } +} +``` + +```mermaid +flowchart LR + A((A)) + B((B)) + C((C)) + D((D)) + E((E)) + + A --> B + B --> C + C --> D + D --> E + E --> A +``` + +## 雙向鏈結串列 (Doubly Linked List) + +```mermaid +flowchart LR + A((A)) + B((B)) + C((C)) + D((D)) + E((E)) + + A <--> B + B <--> C + C <--> D + D <--> E +``` diff --git a/algorithms/binary-search/README.md b/algorithms/searching/README.md similarity index 100% rename from algorithms/binary-search/README.md rename to algorithms/searching/README.md diff --git a/algorithms/binary-search/binarySearch.ts b/algorithms/searching/binarySearch.ts similarity index 100% rename from algorithms/binary-search/binarySearch.ts rename to algorithms/searching/binarySearch.ts diff --git a/src/page-3/283. Move Zeroes/moveZeroes.bench.ts b/src/page-3/283. Move Zeroes/moveZeroes.bench.ts deleted file mode 100644 index c22a037..0000000 --- a/src/page-3/283. Move Zeroes/moveZeroes.bench.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { bench } from 'vitest'; - -import { moveZeroes, moveZeroes2 } from './moveZeroes'; - -describe('283. Move Zeroes', () => { - // fastest - bench('moveZeroes', () => { - const nums1 = [0, 1, 0, 3, 12]; - moveZeroes(nums1); - - const nums2 = [0]; - moveZeroes(nums2); - }); - - bench('moveZeroes2', () => { - const nums1 = [0, 1, 0, 3, 12]; - moveZeroes2(nums1); - - const nums2 = [0]; - moveZeroes2(nums2); - }); -}); diff --git a/src/page-3/283. Move Zeroes/moveZeroes.test.ts b/src/page-3/283. Move Zeroes/moveZeroes.test.ts index ece7582..5752236 100644 --- a/src/page-3/283. Move Zeroes/moveZeroes.test.ts +++ b/src/page-3/283. Move Zeroes/moveZeroes.test.ts @@ -1,4 +1,4 @@ -import { moveZeroes, moveZeroes2 } from './moveZeroes'; +import { moveZeroes } from './moveZeroes'; describe('283. Move Zeroes', () => { test('moveZeroes', () => { @@ -10,14 +10,4 @@ describe('283. Move Zeroes', () => { moveZeroes(nums2); expect(nums2).toStrictEqual([0]); }); - - test('moveZeroes2', () => { - const nums1 = [0, 1, 0, 3, 12]; - moveZeroes2(nums1); - expect(nums1).toStrictEqual([1, 3, 12, 0, 0]); - - const nums2 = [0]; - moveZeroes2(nums2); - expect(nums2).toStrictEqual([0]); - }); }); diff --git a/src/page-3/283. Move Zeroes/moveZeroes.ts b/src/page-3/283. Move Zeroes/moveZeroes.ts index aedb847..ef067f8 100644 --- a/src/page-3/283. Move Zeroes/moveZeroes.ts +++ b/src/page-3/283. Move Zeroes/moveZeroes.ts @@ -4,36 +4,14 @@ type MoveZeroes = (nums: number[]) => void; * Accepted */ export const moveZeroes: MoveZeroes = (nums) => { - let [left, right] = [0, 0]; - - while (right < nums.length) { - if (nums[right] === 0) { - right += 1; - } else { - const temp = nums[right]; - nums[right] = nums[left]; - nums[left] = temp; - - left += 1; - right += 1; - } - } -}; - -/** - * Accepted - */ -export const moveZeroes2: MoveZeroes = (nums) => { - let lastNonZeroFoundAt = 0; + let nonZeroIndex = 0; // This will track the index where the next non-zero number should go + // Traverse the array with the current pointer for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { - nums[lastNonZeroFoundAt] = nums[i]; - lastNonZeroFoundAt += 1; + // If the current element is not 0, swap it with the element at nonZeroIndex + [nums[nonZeroIndex], nums[i]] = [nums[i], nums[nonZeroIndex]]; + nonZeroIndex += 1; // Move the non-zero pointer to the next position } } - - for (let i = lastNonZeroFoundAt; i < nums.length; i++) { - nums[i] = 0; - } };