Skip to content

Commit 34a6542

Browse files
committed
Add: Add 2025/11/1
1 parent 6e2ea05 commit 34a6542

File tree

5 files changed

+223
-9
lines changed

5 files changed

+223
-9
lines changed

3208-Alternating Groups II/Note.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ for (let i = 1; i < n + k - 1; i++) {
8787
}
8888
```
8989

90-
9190
## 時間複雜度
9291

9392
- 遍歷循環的時間從 `1` 遍歷到 `n+k−1` 的單一迴圈,其中 `n` 為陣列長度。且遍歷內部的操作都是常數時間的。故時間複雜度為 $O(n + k)$。
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# 3217. Delete Nodes From Linked List Present in Array
2+
3+
You are given an array of integers `nums` and the `head` of a linked list.
4+
Return the `head` of the modified linked list after removing all nodes from the linked list that have a value that exists in `nums`.
5+
6+
**Constraints:**
7+
8+
- `1 <= nums.length <= 10^5`
9+
- `1 <= nums[i] <= 10^5`
10+
- All elements in `nums` are unique.
11+
- The number of nodes in the given list is in the range `[1, 10^5]`.
12+
- `1 <= Node.val <= 10^5`
13+
- The input is generated such that there is at least one node in the linked list that has a value not present in `nums`.
14+
15+
## 基礎思路
16+
17+
本題要求我們從一條單向鏈結串列中,**移除所有其值存在於指定陣列 `nums` 中的節點**,並回傳修改後的鏈首(`head`)。
18+
19+
在思考解法時,我們需要特別注意幾個重點:
20+
21+
- `nums` 與鏈結串列的長度皆可達 $10^5$,若在每次節點檢查時用線性搜尋(如 `nums.includes(val)`)會造成 $O(n^2)$ 的效能災難;
22+
- 我們需確保刪除節點時能正確維護鏈結結構,尤其是刪除鏈首或連續節點時;
23+
- 由於節點值與 `nums[i]` 均介於 `[1, 10^5]`,可利用**固定長度的存在表(presence array)**進行快速查詢;
24+
- 設計上應避免過多動態配置(如 `Set``Map`)帶來的額外開銷。
25+
26+
為了解決這個問題,我們採取以下策略:
27+
28+
- **建立存在表(Presence Array)**:以 `Uint8Array` 儲存 `nums` 中每個值的出現情況,使查詢成本降至 $O(1)$;
29+
- **使用虛擬頭節點(Dummy Node)**:方便統一處理刪除鏈首節點的情況;
30+
- **雙指標遍歷**:利用 `previousNode``currentNode` 指標逐一檢查節點,若該值存在於 `nums`,則跳過該節點;
31+
- **固定記憶體訪問模式**:僅進行必要的節點連接操作,避免多餘的屬性存取以提升效能。
32+
33+
此設計確保整體時間與空間皆維持在線性等級,滿足題目上限約束。
34+
35+
## 解題步驟
36+
37+
### Step 1:處理空鏈結情況
38+
39+
若輸入的 `head``null`,代表鏈表為空,直接回傳 `null`
40+
41+
```typescript
42+
// 若輸入鏈結串列為空,直接回傳 null
43+
if (head === null) {
44+
return null;
45+
}
46+
```
47+
48+
### Step 2:建立存在表以快速查詢
49+
50+
使用 TypedArray `Uint8Array` 建立固定長度的存在表,索引即為數值,值為 `1` 代表該數出現在 `nums` 中。
51+
52+
```typescript
53+
// 題目上限:節點值與 nums[i] 皆不超過 100000
54+
const MAX_VALUE = 100000;
55+
56+
// 建立固定長度的存在表(值為 1 表示該數存在於 nums 中)
57+
const presenceArray = new Uint8Array(MAX_VALUE + 1);
58+
```
59+
60+
### Step 3:標記 nums 中的所有數值
61+
62+
透過傳統 `for` 迴圈遍歷 `nums`,將每個數值標記於存在表中。
63+
64+
```typescript
65+
// 將 nums 中出現的值標記於 presenceArray 中
66+
for (let index = 0; index < nums.length; index++) {
67+
const value = nums[index];
68+
// 題目保證數值範圍合法,故無需邊界檢查
69+
presenceArray[value] = 1;
70+
}
71+
```
72+
73+
### Step 4:建立虛擬頭節點以簡化刪除流程
74+
75+
為處理刪除鏈首節點的情況,我們新增一個虛擬頭節點,指向原始 `head`
76+
77+
```typescript
78+
// 建立虛擬頭節點(dummy node)以簡化刪除操作
79+
const dummyNode = new ListNode(0, head);
80+
```
81+
82+
### Step 5:初始化雙指標
83+
84+
使用 `previousNode` 指向上個保留節點,`currentNode` 逐一遍歷整條鏈表。
85+
86+
```typescript
87+
// 初始化雙指標:previousNode 起點為 dummy,currentNode 起點為 head
88+
let previousNode: ListNode = dummyNode;
89+
let currentNode: ListNode | null = head;
90+
```
91+
92+
### Step 6:遍歷鏈表並刪除指定值節點
93+
94+
當前節點值存在於 `nums`(presenceArray 對應索引為 1)時,跳過該節點;否則保留並前進。
95+
96+
```typescript
97+
// 遍歷整條鏈表,刪除存在於 nums 中的節點
98+
while (currentNode !== null) {
99+
// 預先快取 next 節點以避免重複屬性訪問
100+
const nextNode = currentNode.next as ListNode | null;
101+
const valueToCheck = currentNode.val;
102+
103+
if (presenceArray[valueToCheck] === 1) {
104+
// 該節點值存在於 nums 中,需刪除:讓 previousNode 指向 nextNode
105+
previousNode.next = nextNode;
106+
// currentNode 移動到下一節點,previousNode 不變
107+
currentNode = nextNode;
108+
} else {
109+
// 該節點值不在 nums 中,保留並同步推進兩指標
110+
previousNode = currentNode;
111+
currentNode = nextNode;
112+
}
113+
}
114+
```
115+
116+
### Step 7:返回更新後的鏈首
117+
118+
最終回傳虛擬頭節點的 `next`,即為刪除後的新鏈首。
119+
120+
```typescript
121+
// 返回刪除後的新鏈首(跳過虛擬節點)
122+
return dummyNode.next;
123+
```
124+
125+
## 時間複雜度
126+
127+
- 構建存在表:遍歷 `nums` 長度為 $m$ → $O(m)$
128+
- 遍歷鏈結串列:長度為 $n$ → $O(n)$
129+
- 查表操作為常數時間 $O(1)$。
130+
- 總時間複雜度為 $O(m + n)$。
131+
132+
> $O(m + n)$
133+
134+
## 空間複雜度
135+
136+
- 存在表 `presenceArray` 需固定 $O(k)$ 空間,其中 $k = 10^5$(常數上限)。
137+
- 其餘僅使用常數額外變數。
138+
- 總空間複雜度為 $O(k)$,可視為 $O(1)$ 常數級別。
139+
140+
> $O(1)$
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Definition for singly-linked list.
3+
* class ListNode {
4+
* val: number
5+
* next: ListNode | null
6+
* constructor(val?: number, next?: ListNode | null) {
7+
* this.val = (val===undefined ? 0 : val)
8+
* this.next = (next===undefined ? null : next)
9+
* }
10+
* }
11+
*/
12+
13+
function modifiedList(nums: number[], head: ListNode | null): ListNode | null {
14+
// Fast path: empty list
15+
if (head === null) {
16+
return null;
17+
}
18+
19+
// Problem constraint upper bound for node/nums values
20+
const MAX_VALUE = 100000;
21+
22+
// Create typed presence array. Index i = 0...MAX_VALUE; value 1 means present in nums.
23+
const presenceArray = new Uint8Array(MAX_VALUE + 1);
24+
25+
// Mark presence for each number in nums (use classic for loop to avoid iterator overhead)
26+
for (let index = 0; index < nums.length; index++) {
27+
const value = nums[index];
28+
// Under the problem constraints value is guaranteed to be in [1, MAX_VALUE],
29+
// so we skip extra bounds-checks here for speed.
30+
presenceArray[value] = 1;
31+
}
32+
33+
// Dummy node simplifies head removals
34+
const dummyNode = new ListNode(0, head);
35+
36+
// Local aliases to reduce repeated property lookups in the hot loop
37+
let previousNode: ListNode = dummyNode;
38+
let currentNode: ListNode | null = head;
39+
40+
// Traverse the list, removing nodes whose values are present
41+
while (currentNode !== null) {
42+
// Cache next pointer once for this iteration to avoid repeated .next property access
43+
const nextNode = currentNode.next as ListNode | null;
44+
const valueToCheck = currentNode.val;
45+
46+
if (presenceArray[valueToCheck] === 1) {
47+
// Node should be removed: bypass currentNode by linking previousNode to nextNode
48+
previousNode.next = nextNode;
49+
// currentNode becomes nextNode (previousNode stays unchanged)
50+
currentNode = nextNode;
51+
} else {
52+
// Node should be kept: advance previousNode and currentNode
53+
previousNode = currentNode;
54+
currentNode = nextNode;
55+
}
56+
}
57+
58+
// Return the possibly new head
59+
return dummyNode.next;
60+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Definition for singly-linked list.
3+
* class ListNode {
4+
* val: number
5+
* next: ListNode | null
6+
* constructor(val?: number, next?: ListNode | null) {
7+
* this.val = (val===undefined ? 0 : val)
8+
* this.next = (next===undefined ? null : next)
9+
* }
10+
* }
11+
*/
12+
13+
function modifiedList(nums: number[], head: ListNode | null): ListNode | null {
14+
15+
}

3289-The Two Sneaky Numbers of Digitville/Note.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ Return an array of size two containing the two numbers (in any order), so peace
1818
本題要求在一個應該包含 `0``n - 1` 各一次的整數列表中,找出那兩個偷偷重複出現的數字。
1919
題目保證:
2020

21-
* 整體長度為 `n + 2`(比預期多出 2 個元素);
22-
* 共有且僅有兩個數字出現兩次。
21+
- 整體長度為 `n + 2`(比預期多出 2 個元素);
22+
- 共有且僅有兩個數字出現兩次。
2323

2424
在思考解法時,我們需要掌握幾個重點:
2525

26-
* 每個數字介於 `0``n - 1`,範圍固定;
27-
* 恰有兩個數字重複出現兩次;
28-
* 其餘數字皆出現一次,因此我們可透過「出現次數」辨識重複者。
26+
- 每個數字介於 `0``n - 1`,範圍固定;
27+
- 恰有兩個數字重複出現兩次;
28+
- 其餘數字皆出現一次,因此我們可透過「出現次數」辨識重複者。
2929

3030
為了解決這個問題,我們可以採取以下策略:
3131

32-
* **建立頻率表**:使用雜湊結構(如 `Map`)來記錄每個數字出現次數;
33-
* **偵測重複出現**:在每次更新頻率時檢查是否等於 2,若是則紀錄該數;
34-
* **輸出結果**:題目保證正好兩個重複數,返回長度為 2 的陣列即可。
32+
- **建立頻率表**:使用雜湊結構(如 `Map`)來記錄每個數字出現次數;
33+
- **偵測重複出現**:在每次更新頻率時檢查是否等於 2,若是則紀錄該數;
34+
- **輸出結果**:題目保證正好兩個重複數,返回長度為 2 的陣列即可。
3535

3636
此法邏輯直觀、實作簡潔,且在題目限制下能以線性時間完成。
3737

0 commit comments

Comments
 (0)