Leetcode 邁向千題的關卡,想要把所有題目刷過一遍,對於一位上班族來說就跟寫出來的程式沒有 bug 一樣困難,因此想要將刷題常用的觀念技巧與其對應題目整理出來,一方面可以整理自己思緒,也可以做為未來複習用。
這系列文章會一直持續下去,算是作為工程師職涯的隨身裝備。
在 leetcode 上有許多加法運算題目,差別在於 input 為各種資料結構,根據 leetcode 上的說法,這類型題目是 facebook 面試官愛考的題目,下面整理出四大類題目。
- String 加法
- Integer 加法
- Array 加法
- Linked list 加法
String 加法
這類題目基本上透過 digit-by-digit 的加法即可。
Leetcode #67 Add Binary
Input: a = "11", b = "1"
Output: "100"
方法 1 Bit-By-Bit computation
最直覺的作法。
如果 a_bit, b_bit 和 carry 加起來為 1 或 3,代表該 bit 為 1,另外一個方法為將三個數做 XOR ,這樣的方法更為簡潔。
class Solution {
public:
string addBinary(string a, string b) {
string result = "";
int a_pos = a.size() -1;
int b_pos = b.size() -1;
int a_bit, b_bit, carry = 0;
while (a_pos >= 0 || b_pos >= 0 || carry == 1)
{
a_bit = b_bit = 0;
if (a_pos >= 0) a_bit =
(a[a_pos--] == '1');
if (b_pos >= 0) b_bit =
(b[b_pos--] == '1');
result = static_cast<char>
(a_bit ^ b_bit ^ carry + '0')
+ result;
carry = a_bit + b_bit + carry >= 2;
}
return result;
}
};
方法 2 方法1加上 bit manipulation
如果進一步要求不能使用加法,該如何做到呢?
1 + 1 = 0b10 可以透過 XOR, AND 和 left shift 來完成:
- 透過 XOR 運算,可以得到 LSB 的答案
1 xor 1 = 0
- 透過 AND 運算,可以得到加法後進位的答案
(1 & 1) = 1
如果還需要考慮 carry ,處理起來會更複雜一點,可見下面完整程式碼。
class Solution {
public:
string addBinary(string a, string b) {
string result = "";
int a_pos = a.size() -1;
int b_pos = b.size() -1;
int a_bit, b_bit, carry = 0;
while (a_pos >= 0 || b_pos >= 0 || carry == 1)
{
a_bit = b_bit = 0;
if (a_pos >= 0) a_bit = a[a_pos--] == '1';
if (b_pos >= 0) b_bit = b[b_pos--] == '1';
int curr = a_bit ^ b_bit ^ carry;
result = static_cast<char>(curr + '0') + result;
carry = ((a_bit^b_bit) & carry) | (a_bit & b_bit);
}
return result;
}
};
Leetcode #415 Add Strings
直覺作法為 digit-by-digit 的處理,由兩個字串的最右邊 digit 開始,轉為 integer 進行加法,持續向左處理。
- 延伸練習: LC #43
Integer 加法
Leetcode #371 Sum of Two Integers
這題給你兩個整數,要你不使用任何加法,算出兩數相加的結果。
透過 XOR, AND, left shift 來相加兩個整數:
- XOR 可以將所有會進位的 bit 抹除。
- 透過 AND 運算可以標記出所有相加後會發生進位的位置,再透過 left shift (<< 1) 即可以得出相加後發生進位的結果。
將這兩步驟的結果相加即為原本兩數相加後的答案,但這題要求不使用任何加法。
竟然兩步驟的結果相加即為原本兩數相加,可以將這兩個步驟的結果當做另外的兩個整數,再次執行同樣的兩步驟,又可以得到新的一組整數,不斷重複執行,直到步驟 2 得到的結果為 0 ,代表將兩數的和都放到步驟 1 的結果了。
class Solution {
public:
int getSum(int a, int b) {
int sum = a;
while (b!=0)
{
sum = a^b;
b = (a&b) <<1;
a = sum;
}
return sum;
}
};
要特別注意一個 test case: a = -1, b = 1
,此時會編譯錯誤,原因為不能執行 left shift operation 在負數上,這是 undefined behavior (a 和 b 不斷運算後,最終會得到 a&b 變成負值,這個過程可以自行推導,這邊就不詳細說明)。
使用 long 型別的 bit mask 來避開這個問題,再經過好幾輪運算後,(a&b) 會變成負數中的最大值 (-2^31),此時再進行 left shift 會是 undefined behavior ,透過與 long 型別的 bit mask 做 AND 運算,(a&b) 會轉為 long type ,其值就不再是一個負數。
接著做完 left shift 後,結果會被 assign 給變數 b ,變數 b 是 integer 型別 ,此時運算結果又會由 long type 被轉為 int type ,超過 int type 的 bit 數就會被截斷,得到的結果將會是預期的答案。
class Solution {
public:
int getSum(int a, int b) {
int sum = a;
long mask = 0xFFFFFFFF;
while (b!=0)
{
sum = a^b;
b = ((a&b) & mask)<<1;
a = sum;
}
return sum;
}
};
Array 加法
Leetcode #066 Plus One
只有在 digit 為 9 的時候才需要特別處理,否則直接將 digit 加1即可。
Digit 為 9 的話,要將 digit 設為 0,並持續往後面的 digit 進行加法。
最後一種特殊狀況為每個 digit 都為 9 ,這時候需要在 array 開頭補上 1 。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int size = digits.size ();
for (int i=size-1; i>=0; i--)
{
if (digits[i] == 9)
{
digits[i] = 0;
}
else
{
digits[i]++;
return digits;
}
}
digits.insert (digits.begin(), 1);
return digits;
}
};
Leetcode #989 Add to Array-Form of Integer
由數值小的位置開始,逐個 digit 跟 K 相加,需注意有可能 K 比 Input array 還要大,處理完 input array ,還要檢查 K 是否還有數值。
Linked List 加法
Leetcode #369 Plus One Linked List
Linked list 缺點是只能依序走訪,必須想清楚可能狀況,才能夠在 O(N) 情況下完成。
解題演算法
- 建立一個哨兵節點
- 紀錄節點數值非9中最右邊的一個點,將該點加 1
- 若此點後面有 9 ,全部將其轉為 0
- 如果第二步驟找到的該點為哨兵節點,代表原始 linked list 上都是 9 ,需要回傳哨兵節點,否則回傳原始 linked list 的頭節點即可。
class Solution {
public:
ListNode* plusOne(ListNode* head) {
ListNode *sentinel = new ListNode(0);
sentinel->next = head;
ListNode *notNine = sentinel;
while (head != nullptr)
{
if (head->val != 9)
notNine = head;
head = head->next;
}
notNine->val++;
while (notNine->next != nullptr)
{
notNine = notNine->next;
notNine->val = 0;
}
if (sentinel->val == 1) {
return sentinel;
}
else
{
return sentinel->next;
}
}
};
Leetcode #002 Add Two Numbers
這題相當簡單,就是 digit-by-digit 的加法操作,可以特別注意 edge case ,例如 input 為 :
Linked List1 = [ 9, 9 ]
Linked List2 = [ 9, 9, 9, 9, 9]
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int carry = 0;
ListNode head(0);
ListNode *curr = &head;
while (l1 != nullptr || l2 != nullptr || carry != 0)
{
int sum = 0;
if (l1 != nullptr)
{
sum += l1->val;
l1 = l1->next;
}
if (l2 != nullptr)
{
sum += l2->val;
l2 = l2->next;
}
if (carry > 0)
{
sum += carry;
}
ListNode *newNode = new ListNode (sum % 10);
carry = sum/10;
curr->next = newNode;
curr = curr->next;
}
return head.next;
}
};
Leetcode #445 Add Two Numbers II
方法1
- 走訪兩個 linked list 知道其長度。
- 再次走訪兩個 linked list 來製造將作為答案的 Linked list ,相加節點數值並存下來。
- 走訪 step 2 所完成的 Linked list ,當
node->next
的值大於 10,當前節點就加 1,接著走到 next ,將數值對 10 取餘數。
Time Complexity
- O (m+n)
- O ( max (m, n) )
- O ( max (m, n) )
方法2
- 將兩個 linked list 進行反轉操作
- 依序走訪 linked list 並進行加法即可,跟 #002 一樣做法。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverseList (l1);
l2 = reverseList (l2);
int carry = 0;
ListNode *curr = nullptr;
while (l1 != nullptr || l2 != nullptr || carry)
{
int sum = 0;
if (l1 != nullptr)
{
sum += l1->val;
l1 = l1->next;
}
if (l2 != nullptr)
{
sum += l2->val;
l2 = l2->next;
}
sum += carry;
ListNode *newNode = new ListNode (sum%10);
newNode->next = curr;
curr = newNode;
carry = sum/10;
}
return curr;
}
private:
ListNode* reverseList (ListNode *head)
{
ListNode *prev = nullptr, *nxt;
while (head != nullptr)
{
nxt = head->next;
head->next = prev;
prev = head;
head = nxt;
}
return prev;
}
};
Time Complexity
- O (m+n)
- O ( max (m, n) )
Updated on 2021-05-29 22:26:01 星期六