【Leetcode C++ 解題筆記】Divide & Conquer - part 2
【Leetcode C++ 解題筆記】Divide & Conquer - part 2
本筆記僅供個人學習用途,內容斟酌參考。
1. Convert Sorted Array to Binary Search Tree
Problem Source:https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/?envType=problem-list-v2&envId=divide-and-conquer
難度:Easy
題意大致上就是要你把一般常見的陣列轉換成 BST。
補充一下 BST 特性:
- 左子樹的 Nodes 都比 Root 還小;反之,右子樹都比 Root 還大。
- 子樹必須都是 BST。
解題思路:
用到 Divide & Conquer 的概念,每次把「目前」陣列片段選中間元素作為 root。
對 root 左側遞迴呼叫產生左子樹,右側也一樣,直到子陣列長度為 0 就結束。
範例程式碼:
1 | /** |
2. Majority Element
Problem Source:https://leetcode.com/problems/majority-element/description/?envType=problem-list-v2&envId=divide-and-conquer
難度:Easy
題目敘述:
給定長度為 n 的陣列 nums,回傳多數元素(the majority element)。
所謂多數元素就是元素出現超過 [n / 2] 次。你可以假設它都會出現在陣列中。
解題思路:
用 Divide & Conquer 做這題,一樣是二分法去做,主要處理 [left, mid] 和 [mid + 1, right] 這兩段區間。
終止條件是 if (left == right) return nums[left]; 表示元素個數剩一個,該元素必為多數元素,故回傳此值。
遞迴處理 lm 和 rm 的部分(左右區間)。
之後做合併動作,也直接做判斷:if (lm == rm) return lm; 表示左右多數元素相同。
如果不同,就需要去數 lm 跟 rm 共出現幾次:
1 | int c1 = count(nums.begin() + left, nums.begin() + right + 1, lm); |
範例程式碼:
1 | class Solution { |
3. Reverse Bits
Problem Source:https://leetcode.com/problems/reverse-bits/description/?envType=problem-list-v2&envId=divide-and-conquer
難度:Easy
題目敘述:
反轉 32 位元的偶數有號整數(uint)。
先以 8 位元舉例比較好懂,32 位元太長了。
假設原本數字是 0 0 0 1 0 1 1 0,二進位是從右開始為起始索引,最左邊是最終索引 7。
1 | Index :7 6 5 4 3 2 1 0 |
所謂反轉位元的意思就是把索引顛倒:
1 | Index :0 1 2 3 4 5 6 7 |
就這樣。
範例程式碼:
1 | class Solution { |
基本上這段程式碼就是不斷將 n 切割成兩部分,並且作互換(用 | OR 運算子互換)的動作,以此來達到反轉位元的效果。
4. Number of 1 Bits
Problem Source:https://leetcode.com/problems/number-of-1-bits/description/?envType=problem-list-v2&envId=divide-and-conquer
難度:Easy
題目敘述:
給定一個正整數 n,寫一個函數回傳其二進位表示法中的設定位數(也稱為漢明權重)。
set bits 指的是有 1 的位元數量,如 1011 就有 3 個 set bits,這個也稱為 Hamming weight。
範例程式碼講解:
雖然有 std::popcount(C++ 20) 或 __builtin_popcount()(看編譯器支不支援)可以用,但上述兩種方法若在某些競賽上使用可能不切實際。
所以我們來手刻一下 popcount。
在細講這個演算法之前,我們要先知道幾個 bitmasks:
1 | 0x55 = 01010101 |
第一行(n = (n & 0x55555555) + ((n >> 1) & 0x55555555);)用到 bitmasks 0x55,挑出 n 中所有在奇數位(bit 0, 2, 4, …)的 bit,再把 n 右移 1 位後(偶數位),也用相同遮罩挑出偶數位 bit,最後兩者相加。
這一層分治主要將 32 個 bit 分成 16 組,每組 2bit。
第二行(n = (n & 0x33333333) + ((n >> 2) & 0x33333333);)用到 bitmasks 0x33,將剛才算好的每 2bit 組做累加操作,把相鄰的兩個 2bit 組合成一個 4bit 組並累加 1 的數量。
然後這層分治也將 32bit 分成 8 組,每組 4bit,計算每組內 1 的個數。
第三行(n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);)用到 bitmasks 0x0f,將相鄰的兩個 4bit 組合成一個 8bit 組,同樣使用 bitmasks 分低 4bit 及高 4bit 相加。
該層分治再把 32bit 分成 4 組,每組 8bit,計算每組內 1 的個數。
第四行(n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);)也用到一樣的 bitmasks,只不過多加個 f,在二進位上就多了四個 f。
這層分治把將相鄰兩個 8bit 組合成 1 個 16bit 組,再累加各 8bit 中 1 的個數。
第五行(n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);)用到最後一個 bitmasks 0xff,將兩個 16bit 組合成整個 32bit 的組合,再將前後 16bit 中 1 的個數合併。
最終我們就從 32 bit 裡面得到所有 1 的數量了,為什麼是 32 bit 呢?因為題目要求 int,int 就剛好 32 bit。
完整範例程式碼:
1 | class Solution { |
5. Longest Nice Substring
Problem Source:https://leetcode.com/problems/longest-nice-substring/description/?envType=problem-list-v2&envId=divide-and-conquer
難度:Easy
題目敘述:
一字串 s 如果是 nice,那對於每個字母表中的每個字母都包含 s,它以大寫和小寫形式出現。
舉例來說,"abABB" 是 nice,因為 'A' 和 'a' 出現,並且 'B' 和 'b' 出現。然而,"abA" 不是 nice,因為 'b' 出現,而 'B' 沒有出現。
給定一個字串 s,回傳 s 中最長的、滿足 nice 條件的子字串。如果有多個,則回傳最早出現的子字串。如果沒有,則傳回空字串。
解題思路:
我們用資料結構 unordered_set <int> seen(s.begin(), s.end()); 來實作這題。
首先跑迴圈,內部判斷一個字串中的字元 c 有沒有其他相反的大小寫,如 A 要找到對應的小寫 a 才是一個 nice substring。(這邊要找他對應相反的大小寫,我們用 seen.find() 去找)
大小寫判斷可以額外寫一個 toggleCase() 函數去判斷,如:
1 | char toggleCase(char c){ |
如果 seen.find() 沒有找到,則回傳 end() 這個 iterator。
沒找到的話我們就要做遞迴去找了,這邊用分治法的概念將字串切兩半去找:
1 | string left = longestNiceSubstring(s.substr(0, i)); |
substr(0, i) 為從索引 0 開始,擷取長度 i 的子字串(不含索引 i 的字元),形成左半邊的子字串。
substr(i + 1) 從索引 i + 1 開始,擷取到字串尾的子字串,形成右半邊子字串。
最後回傳較長的子字串:return left.size() >= right.size() ? left : right。
最後的最後,如果我們前面判斷都沒有回傳 end() 的話,那就表示說整串字串完美符合 nice substring 的條件,就直接 return s。
完整範例程式碼:
1 | class Solution { |



