Skip to content

Commit 1a155c6

Browse files
committed
Add: 2. Add Two Numbers
1 parent 3430f43 commit 1a155c6

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

2-Add Two Numbers/Note.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# 2. Add Two Numbers
2+
3+
You are given two non-empty linked lists representing two non-negative integers.
4+
The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
5+
6+
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
7+
8+
**Constraints:**
9+
10+
- The number of nodes in each linked list is in the range `[1, 100]`.
11+
- `0 <= Node.val <= 9`
12+
- It is guaranteed that the list represents a number that does not have leading zeros.
13+
14+
## 基礎思路
15+
16+
本題要求將兩個反向儲存的數字(以鏈結串列表示)加總,並回傳結果同樣以反向儲存的鏈結串列表示。
17+
這就像是在紙上做「直式加法」,從個位數開始逐位計算。
18+
19+
為了完成這件事,我們可以採用以下策略:
20+
21+
- 每次同時讀取兩個串列當前位的數字進行相加,若某一方已走到底則補 0。
22+
- 使用一個變數 `carry` 紀錄每次相加是否產生進位。
23+
- 將每位加總結果建立成新節點,依序串接到結果串列後方。
24+
- 為了簡化操作,使用一個「虛擬頭節點」來建立結果串列,避免特殊處理第一個節點。
25+
- 加總結束後,若仍有進位,須額外補上一節點。
26+
27+
這些操作都能在一次遍歷中完成,空間上只需要建一條新的串列,額外變數使用極少,因此效率也很高。
28+
29+
## 解題步驟
30+
31+
### Step 1:處理空串列的邊界情況
32+
33+
若其中一條輸入串列為空,則結果等於另一條串列。
34+
35+
```typescript
36+
// 處理其中一條串列為空的邊界情況
37+
if (l1 === null) {
38+
return l2;
39+
}
40+
if (l2 === null) {
41+
return l1;
42+
}
43+
```
44+
45+
### Step 2:建立虛擬頭節點與初始化尾指標
46+
47+
使用 dummy head 方便後續統一插入節點的邏輯,避免判斷是否為第一個節點。
48+
49+
```typescript
50+
// 以虛擬頭節點簡化結果串列的建立,並維護尾指標以便追加新節點
51+
const dummyHeadNode = new ListNode(0);
52+
let resultTailNode = dummyHeadNode;
53+
```
54+
55+
### Step 3:初始化指標與進位變數
56+
57+
設置用來遍歷兩條輸入串列的指標,並初始化進位變數為 0。
58+
59+
```typescript
60+
// 初始化走訪指標與進位(處理位數進位)
61+
let list1Pointer: ListNode | null = l1;
62+
let list2Pointer: ListNode | null = l2;
63+
let carryOver = 0;
64+
```
65+
66+
### Step 4:遍歷兩條串列,逐位計算並建立新節點
67+
68+
此步驟進行主邏輯:同步走訪兩串列,計算每一位的總和與進位,並建立對應節點加入結果串列。
69+
70+
```typescript
71+
// 同步走訪兩串列直到所有位元處理完畢
72+
while (list1Pointer !== null || list2Pointer !== null) {
73+
// 取得當前位數字(較短的串列視為 0)
74+
const digitFromList1 = list1Pointer ? list1Pointer.val : 0;
75+
const digitFromList2 = list2Pointer ? list2Pointer.val : 0;
76+
77+
// 計算兩位數字加上前一次進位的總和
78+
const sumOfDigits = digitFromList1 + digitFromList2 + carryOver;
79+
80+
// 以新節點記錄本位數值,並更新進位
81+
if (sumOfDigits >= 10) {
82+
resultTailNode.next = new ListNode(sumOfDigits - 10);
83+
carryOver = 1;
84+
} else {
85+
resultTailNode.next = new ListNode(sumOfDigits);
86+
carryOver = 0;
87+
}
88+
89+
// 推進尾指標與輸入串列的走訪指標
90+
resultTailNode = resultTailNode.next!;
91+
if (list1Pointer !== null) {
92+
list1Pointer = list1Pointer.next;
93+
}
94+
if (list2Pointer !== null) {
95+
list2Pointer = list2Pointer.next;
96+
}
97+
}
98+
```
99+
100+
### Step 5:處理最終進位(若有)
101+
102+
若最後仍有進位,代表需補上一個進位節點。
103+
104+
```typescript
105+
// 若處理完後仍有進位,將其以新節點補到尾端
106+
if (carryOver !== 0) {
107+
resultTailNode.next = new ListNode(carryOver);
108+
}
109+
```
110+
111+
### Step 6:回傳實際結果的頭節點
112+
113+
跳過 dummy 頭節點,回傳真正的加總結果。
114+
115+
```typescript
116+
// 回傳實際結果頭節點(跳過虛擬頭)
117+
return dummyHeadNode.next;
118+
```
119+
120+
## 時間複雜度
121+
122+
- 需要同時走訪兩條鏈結串列,遍歷過程中每個節點只處理一次。
123+
- 每次節點操作(加總、建立節點、串接、指標移動)皆為常數時間。
124+
- 總時間複雜度為 $O(n)$,其中 $n$ 為兩條輸入串列中較長者的節點數。
125+
126+
> $O(n)$
127+
128+
## 空間複雜度
129+
130+
- 在整個加總過程中僅使用了固定數量的指標與變數,因此額外使用的空間為常數。
131+
- 此外建立了一條新的結果鏈結串列,其長度最多為兩條輸入串列長度中較大的那一個加 1。
132+
- 總空間複雜度為 $O(n)$,其中 $n$ 為輸出鏈結串列的節點數。
133+
134+
> $O(n)$

2-Add Two Numbers/answer.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | null {
14+
// Handle edge cases when one of the lists is empty
15+
if (l1 === null) {
16+
return l2;
17+
}
18+
if (l2 === null) {
19+
return l1;
20+
}
21+
22+
// Use a dummy head to simplify list construction,
23+
// and maintain a tail pointer for appending new nodes
24+
const dummyHeadNode = new ListNode(0);
25+
let resultTailNode = dummyHeadNode;
26+
27+
// Initialize traversal pointers and carry for digit overflow
28+
let list1Pointer: ListNode | null = l1;
29+
let list2Pointer: ListNode | null = l2;
30+
let carryOver = 0;
31+
32+
// Traverse both lists until all digits are processed
33+
while (list1Pointer !== null || list2Pointer !== null) {
34+
// Get current digit values (0 if one list is shorter)
35+
const digitFromList1 = list1Pointer ? list1Pointer.val : 0;
36+
const digitFromList2 = list2Pointer ? list2Pointer.val : 0;
37+
38+
// Compute sum of digits plus carry from previous step
39+
const sumOfDigits = digitFromList1 + digitFromList2 + carryOver;
40+
41+
// Create a new node for the current digit,
42+
// and update the carry accordingly
43+
if (sumOfDigits >= 10) {
44+
resultTailNode.next = new ListNode(sumOfDigits - 10);
45+
carryOver = 1;
46+
} else {
47+
resultTailNode.next = new ListNode(sumOfDigits);
48+
carryOver = 0;
49+
}
50+
51+
// Advance the tail pointer and move forward in input lists
52+
resultTailNode = resultTailNode.next!;
53+
if (list1Pointer !== null) {
54+
list1Pointer = list1Pointer.next;
55+
}
56+
if (list2Pointer !== null) {
57+
list2Pointer = list2Pointer.next;
58+
}
59+
}
60+
61+
// If a carry remains after processing both lists, append it
62+
if (carryOver !== 0) {
63+
resultTailNode.next = new ListNode(carryOver);
64+
}
65+
66+
// Return the actual head of the result list (skip dummy node)
67+
return dummyHeadNode.next;
68+
}

0 commit comments

Comments
 (0)