meyr543
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    6
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Leetcode刷題學習筆記--Binary Search ## Introduction binary search的精隨就是在一個排序過的數列裡面,找出所要的值。因為每次都切半,所以可以時間複雜度達到O(logN)。相較於暴力破解法,例如答案在1~N之內,==如果測試函數可以讓你知道數字是往更大或更小的方向,就可以用binary search。== > 1. remove special case(答案不在可能的範圍內) > 因為繼續使用binary search,如果答案比範圍內的還大,會得到left = sz。(還可以判斷) > 如果答案比範圍內的還小,會得到left = 0。(無法判斷) > 1. 定義出答案可能出現的範圍left和right。 > 因為計算mid的方法,right必須是範圍最大值+1。 > 2. 決定中間點,mid = left + (righ - left) / 2; > 避免有overflow的問題。 > 3. ==判斷中間點是否超過要搜尋的值==,例如**278**是用isBadVersion(), 668是看有多少數值超過目前的值。 > 4. 更新left, right。==通常right為錯誤的那一邊==。所以判斷完中間點是錯的,需要把區間往下則right = mid,如果是對的,則區間往上,left = mid +1; > 5. 直到left < right不成立為止。 > 最後left會等於right。 > 6. 最後結果會在上一次的right上。所以要根據題目決定要取目前這一個(left)還是left - 1的結果。 ## Code Snippet ```cpp= int left = 0; int right = arr.size() - 1; // 或是 right = arr.size(); 端看最後一個值是不是已經確定了 while(left < right) { int mid = left + (right - left) / 2; // 避免overflow // 如果要拿mid來計算, // 值域會超過int(4)則改用long(8) int val = ...; // 使用mid計算出新的值。 // 如果計算結果會overflow則可改用long(8) if(val == target) // 依題目需求決定要步要用 else if(val > target) // 或是使用 val >= target right = mid; else left = mid + 1; } ``` ## Corner Case ### 當長度為1 left = mid = right,都會指向同一個element。 ![](https://i.imgur.com/VxdYOCv.png) ### 當長度為2 left = mid, right = mid + 1, 只有left和mid一樣。所以竟可能拿mid和right來判斷。 ![](https://i.imgur.com/UX18d8g.png) ## Special Case 當使用 while(left<right)的時候,最左邊和最右邊的答案不會檢查。所以當答案有可能在這兩個之外,必須在while之外做額外的檢查。 case 1 : 如果答案比index 0還小,則最後left/right會停在node 0而離開while loop,但是沒檢查node 0。 ![](https://i.imgur.com/COSzae9.png) case 2 : 如果答案比index 5還大,則最後left/right會停在node 5而離開while loop,但是沒檢查node 5。 ![](https://i.imgur.com/d8Wwwnj.png) 這種情況必須視題目而定。如果答案一定是在node0~node5之間,那就直接return沒關係。如果除了node0~node5之外還有額外的答案,那就必須檢查left/right使否正確。 ## 從答案的範圍,用binary search 猜測是否正確。 另一種binary search的應用。只知道答案的範圍,使用binary search來逼近正確的答案。如下的code: ```cpp int left = ANS_MAX, right = ANS_MIN; while(left < right) { int mid = left + (right - left) / 2; // 根據題目計算由mid得到的結果 // 和題目給的條件去比較,並更新left, right } ``` ### case 1 : 當搜尋範圍就是比較的範圍 例如 : 從已排列好的array中,找出4的數字。 ![](https://i.imgur.com/SFswoi1.png) 題目: + [34. Find First and Last Position of Element in Sorted Array](/u7dufNJ-SAq7z0WmS-Semw?both#34-Find-First-and-Last-Position-of-Element-in-Sorted-Array) ### case 2 : 當搜尋範圍和比較的範圍不一樣 ### case 2-1 : 比較範圍是遞增數列 ### case 2-2 : 比較範圍是遞減數列 搜尋的範圍和比較的範圍不一樣,且比較的範圍是遞減數列。 ![](https://i.imgur.com/SyCu8hQ.png) 題目: + [1760. Minimum Limit of Balls in a Bag](/u7dufNJ-SAq7z0WmS-Semw?both#1760-Minimum-Limit-of-Balls-in-a-Bag) + [875. Koko Eating Bananas](/u7dufNJ-SAq7z0WmS-Semw?both#875-Koko-Eating-BananasMedium) + [275. H-Index II]() ## Search Range 針對binary search,首先一定要有一個搜尋範圍。通常使用left和right來表示欲搜尋的上下界。 + 因為我們都是用mid來測試條件是否成立。所以mid要可以達到答案範圍內的每個值。 + 如果,left = 1, right = 30,且mid = left + (right - left) / 2,則mid的範圍是mid = 1(left = 1, right = 2)..., mid = 29(left = 29, right = 30),mid就無法達到30。 + 所以要讓mid達到上界,則right = 31。mid = 30(left = 30, right = 31) + 因為答案不可能達到(上界+1),所以最後可以判斷這個(left == 上界+1)排除掉。 ```cpp= // setup search range int left = 1, right = 31; while(left < right) { int mid = left + (right - left) / 2; int result{0}; // test mid // update the search range if(result >= k) right = mid; else left = mid + 1; } // 因為結果不可能出現31 return left == 31 ? -1 : left; ``` 例如:[1870. Minimum Speed to Arrive on Time](https://leetcode.com/problems/minimum-speed-to-arrive-on-time/) ```cpp= class Solution { // speed : 1, 2, 3, [4], 5, 6, 7, 8, 9, ... // hour : 10, 9, 9, 8, 8, 8, 7, 7, 7, ... // > <= public: int minSpeedOnTime(vector<int>& dist, double hour) { int sz = dist.size(); if(hour <= sz - 1) return -1; int left = 1, right = 1e9 + 1; while(left < right) { int mid = left + (right - left) / 2; int need{0}; for(int i = 0; i < sz - 1; ++i) need += (dist[i] + (mid - 1)) / mid; double fneed = need + dist.back() * 1.0 / mid; if(fneed <= hour) right = mid; else left = mid + 1; } return left == 1e9 + 1 ? -1 : left; } }; ``` ## Update the Search Range 有了search range,就可以得出mid,這時就必須測試mid是否在範圍之內,然後更新left或是right來縮小搜尋範圍。==通常mid得出的結果必須是遞增或是遞減數列==,這樣才有機會使用binary search。 例如, mid的範圍如下,可以得到測試結果rtn為遞減數列。 ```cpp // mid = 1, 2, 3, 4, 5, 6, 7, 8, 9, // rtn = 9, 9, 8, 8, 7, 7, 6, 6, 5 ``` 再根據題目,通常會限制rtn為某個數,假設是7,因為mid通常在前一個, ```cpp // mid = 1, 2, 3, 4, 5, 6, 7, 8, 9, // rtn = 9, 9, 8, 8, [7], 7, 6, 6, 5 // > <= // mid ``` 所以根據上面的圖示可以寫出 ```cpp= int k = 7; if(rtn <= k) right = mid; else left = mid + 1 return left; ``` 最後答案就是left。 另一種情況是 ```cpp // mid = 1, 2, 3, 4, 5, 6, 7, 8, 9, // rtn = 9, 9, 8, 8, 7, [7], 6, 6, 5 // >= < // mid ``` 可以寫成 ```cpp int k = 7; if(rtn < k) right = mid; else left = mid + 1; return left - 1; ``` 必須返回left - 1,因為最後left == right,會是下一個。 ## Maximum-Minimum or Minimum-Maximum problem 一個題目要求尋找一個答案的最小值,但是這個值是某個運算的最大值,通常都是binary search的題目。 例如:[2560. House Robber IV](https://leetcode.com/problems/house-robber-iv/description/) 其中,robber的capability的定義是偷過的金額中==最大值==。但是必須返回某個條件下(最少必需偷k個房子)的==最小值==。Minimum capability(which is maximum of all stealed money) ```cpp= class Solution { // capability(maximum) 偷的金額中最大的 // [2, 3, 5, 9], k = 2(minimum) 最少要偷兩間 // 回傳 最小(minimum)的capability。 min-max problem --> binary search? // capability 2 ~ 9 // mid = capability : 2, 3, 4, 5, 6, 7, 8, 9 // k 1 1 [2] 2 (為了符合capability, 最多能偷的house數) // < >= public: int minCapability(vector<int>& nums, int k) { auto [minv, maxv] = minmax_element(nums.begin(), nums.end()); int left = *minv, right = *maxv; while(left < right) { int mid = left + (right - left) / 2; int count = 0; for(int i = 0; i < nums.size(); ++i) { if(nums[i] <= mid) { count++; i++; } } if(count >= k) right = mid; else left = mid + 1; } return left; } }; ``` ## Example ### [278. First Bad Version(Easy)](https://leetcode.com/problems/first-bad-version/) 找出第一個錯誤的版本。一定會有一個錯誤的版本。 n = 1, [1]:只有一個版本,則一定是錯誤版本。 n = 2, [1,2]:有兩種情況[X,X]都是錯誤版本和[O,X]一對一錯。 n = 3, [1,2,3]:有三種情況[XXX][OXX][OOX] 使用binary search,right一定會錯誤版本。 ```cpp= int firstBadVersion(int n) { int left = 1, right = n, mid; while(right > left) { mid = left + (right - left) / 2; if(isBadVersion(mid)) right = mid; // right為錯誤版本 else left = mid + 1; } return right; // 因為是找錯誤版本 } ``` 算中間點有兩種算法 ```cpp= mid = (left + right) / 2; ``` 或是 ```cpp= mid = left + (right - left) / 2; ``` 答案都會是一樣,差別在於如果left和right很大,則left + right有可能overflow,但是第二種方法不會。 ==right一定是指向錯誤的版本==。所以code才會寫成以下的樣子。而且最後一定是==return right==。 ```cpp= if(isBadVersion(mid)) right = mid; // right為錯誤版本 else left = mid + 1; // 下一個版本。 ``` ---- c++的STL有提供binary search的function。 lower_bound和upper_bound,都是用binary search來找目標值,差別在於 > lower_bound : 返回==等於或大於==目標值的最小位置。 > upper_bound : 返回==大於==目標值的最小位置。 ![](http://bajamircea.github.io/assets/2018-08-09-lower-bound/01-lower_bound.png) **返回值為iterator**。 所以取值必須用 ```cpp= value = *it; ``` 在array中的index必須用 ```cpp= idx = it - nums.begin(); ``` ### [668. Kth Smallest Number in Multiplication Table(Hard)](https://leetcode.com/problems/kth-smallest-number-in-multiplication-table/) 在一個乘法表裡面(width(n) x height(m),都是1開頭遞增)找出第k個最小的數字。 例如:n = 3, m = 3, k = 5,其中第5個最小的數字為3。 ![leetcode668_example](https://assets.leetcode.com/uploads/2021/05/02/multtable1-grid.jpg) 解題思路: > 1. 在 1 ~ m*n的數字中,找出一個數字,剛好小於這個數字的數量等於k。=> binary search。 > 2. 因為數字都是倍數關係,所以可以用 ==mid / i==來算出小於mid的數量。 ![](https://i.imgur.com/BUZlChl.png) ![](https://i.imgur.com/EPKeJUX.png) ```cpp= int findKthNumber(int m, int n, int k) { // 第一個和最後一個不用算 if(k == 1 || k == m * n) return k; int left = 1, right = m * n; while (left < right) { int mid = left + (right - left) / 2, cnt = 0; // 計算比mid小的數值有多少個 for (int i = 1; i <= m; ++i) { cnt += (mid > n * i) ? n : (mid / i); } // 比較小,區間往上,所以left一定是下一個數 if (cnt < k) left = mid + 1; // 大於等於,區間往下,所以right = mid else right = mid; } return right; } ``` ### [540. Single Element in a Sorted Array(Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/) 給你一個排列過的整數數列nums,其中每個數都是兩兩一對,除了一個數只有一個,問你怎麼找出那一個數。題目又說希望執行效率事O(logN)。 例如: nums = [1,1,2,3,3,4,4,8,8],output = 2。 解題思路 > 1. 一開始看到就想到XOR,這樣就可以找到那個數,但是時間是O(N)。 > 2. 看到O(logN),直覺就想到binary search。但是怎麼判斷範圍要往前還是往後是個問題。 > 3. 先想一下special case。 > 3-1. 題目說長度從1開始,因為兩兩一對只有一個是單獨,所以長度一定是奇數。 > 3-2. 長度 == 1,那不用判斷一定是那一個。 > 3-3. 長度 == 3,那不是第一個,就是最後一個。 > 3-4. 長度 == 7,可以發現要找的數字,和mid和前後關係不太依樣。 ![](https://i.imgur.com/zZ2moYk.png) > 4. 判斷完mid之後,可以把數列變短,問題又是一樣。 照以上的思緒,程式碼如下: ```cpp= int singleNonDuplicate(vector<int>& nums) { auto n = nums.size(); if(n == 1) return nums[0]; int left = 0, right = n - 1, mid; while(left < right) { if(right - left <= 3) { if(nums[left] != nums[left + 1]) return nums[left]; if(nums[right] != nums[right - 1]) return nums[right]; } mid = left + ((right - left) >> 1); if(mid % 2) { if(nums[mid] == nums[mid + 1]) right = mid - 1; else left = mid + 1; } else { if(nums[mid] != nums[mid + 1]) right = mid; else left = mid; } } return 0; } ``` ### [300. Longest Increasing Subsequence(Medium)](https://leetcode.com/problems/longest-increasing-subsequence/) 給你一個vector<int> nums找出,這個當中找出最長的strictly increasing subsequence. + subsequence的定義是,在nums中的前後順序依樣,但是可以不用連續。 + strictly increasing,每個數字都比前一個大。 > 1. 一開始的想法是,每看到一個數字就去map找比他還小的數字 > 2. 然後,更新dp[i]和比他還小的數字連起來最長。 > 3. 這個方法效率不好O(NlogN)。 ```cpp= int lengthOfLIS(vector<int>& nums) { auto n = nums.size(); if(n == 1) return 1; map<int, vector<int>> m; vector<int> dp(n, 1); int maxlen{1}; m[nums[0]].push_back(0); for(int i = 0; i < n; ++i) { for(auto& item : m) { if(item.first >= nums[i]) break; for(auto& idx : item.second) { dp[i] = max(dp[i], dp[idx] + 1); } } maxlen = max(maxlen, dp[i]); m[nums[i]].push_back(i); } return maxlen; } ``` > 網路上的解法 > 1. 使用一個vector來記錄走過的subsequence > 2. 找出lower_bound(大於等於)換掉 > 3. 不然就是推到最後面。 ```cpp= int lengthOfLIS(vector<int>& nums) { vector<int> v; for(auto& n : nums) { auto it = lower_bound(v.begin(), v.end(), n); if(it == v.end()) v.push_back(n); else *it = n; } return v.size(); } ``` ### [875. Koko Eating Bananas(Medium)](https://leetcode.com/problems/koko-eating-bananas/) 請參考原題目。 > 1. 這題思考很久,最後看到題目提示是用binary search才恍然大悟。 > 2. 因為速度k 和最後多少個小時h吃完成線性關係。也就是速度越快,所用的h越少,所以可以用binary search來完成。 > 3. 定義left和right。因為piles最大值為1e9,h最小值為1,所以left = 1, right = 1e9 > 4. 計算k的時候需要無條件進位,等於是(b + mid - 1)/ mid > 5. 最後是希望最小的k,所以設定right >= h ![](https://i.imgur.com/MO22Jjr.png) > 2022/04/25 > 1. 因為吃的速度越快,需要的時間就越短 > 2. 但是koko偏好吃慢一點, 如下圖所示就是2的位置。 > 3. 計算時間使用[無條件進位](https://hackmd.io/3I17t2spT6GDLFRftfEOZA?view#%E7%84%A1%E6%A2%9D%E4%BB%B6%E9%80%B2%E4%BD%8D)。 ![](https://i.imgur.com/LcrRi06.png) ```cpp= int minEatingSpeed(vector<int>& piles, int h) { int left = 1, right = 1e9; while(left < right) { int mid = left + (right - left) / 2; int t = 0; for(auto& n : piles) t += (n + mid - 1) / mid; if(t <= h) right = mid; else left = mid + 1; } return left; } ``` ### [1539. Kth Missing Positive Number](https://leetcode.com/problems/kth-missing-positive-number/) 給你一個vector<int>為嚴格遞增序列(strictly increasing order),找出第k個missing number。 ```cpp= int findKthPositive(vector<int>& arr, int k) { // 把right設定成arr.size(); 是因為當 k 比最大還大時 // right還可以等於arr.size(); int left = 0, right = arr.size(); while(left < right) { int mid = left + (right - left) / 2; int val = arr[mid] - (mid + 1); // 等於也要,因為也在arr[mid - 1] ~ arr[mid] 中間的範圍內。 if(val >= k) right = mid; else left = mid + 1; } return right + k; } ``` ### [1608. Special Array With X Elements Greater Than or Equal X](https://leetcode.com/problems/special-array-with-x-elements-greater-than-or-equal-x/) 找出一個數x,使得在vector<int> nums中,有x個數大於等於x。 ex: Input: nums = [0,4,3,0,4] Output: 3 Explanation: There are 3 values that are greater than or equal to 3. > 1. 因為下面的程式使用了只有當 mid == cnt,才return正確值,反則return -1。所以mid一定要走訪到所有可能的答案[1~sz]。 > 2. 當length = 2的情況,left = mid,所以right必須要等於sz + 1,才可以讓mid走訪所有可能的答案。 ```cpp int specialArray(vector<int>& nums) { int sz = nums.size(); int left = 1, right = sz + 1; while(left < right) { int mid = left + (right - left) / 2; int cnt = 0; int cnt = count_if(nums.begin(), nums.end(), [&](int& a) { return a >= mid; }); // 因為用了mid == cnt 才return, // 所以mid的位置就很重要,因為當length == 2時,mid == left // 所以right的位置要+1 if(mid == cnt) return mid; else if(mid > cnt) right = mid; else left = mid + 1; } return -1; } ``` ### [34. Find First and Last Position of Element in Sorted Array](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/) 在一個排序的數列中,找出target的左右位置。 > 1. 根據以下的圖,可以得知,在做binary search的比較關係。 ![](https://i.imgur.com/SFswoi1.png) > 2. 有兩個special case > 2-1. 當數字出現在第一個,這個不會影響判斷 ![](https://i.imgur.com/ymBFlst.png) > 2-2. 當數字出現在最後一個位置。 ![](https://i.imgur.com/q5JSwPu.png) > 因為我們都是拿mid來比較,會造成, mid只會停在倒數第二個的位置。 ![](https://i.imgur.com/uXCqukG.png) > 所以必須把right的位置往後移一個,讓mid有機會判斷到最後一個位置的數字。 ![](https://i.imgur.com/wyRf58s.png) > 並且答案為 left - 1,因為最後會停在 > target的位置。 ```cpp vector<int> searchRange(vector<int>& nums, int target) { int sz = nums.size(); if(sz == 0 || target < nums[0] || target > nums[sz - 1]) return {-1, -1}; vector<int> rtn(2); int left = 0, right = sz - 1; while(left < right) { int mid = left + (right - left) / 2; if(nums[mid] >= target) right = mid; else left = mid + 1; } rtn[0] = nums[left] == target ? left : -1; left = 0, right = sz; while(left < right) { int mid = left + (right - left) / 2; if(nums[mid] > target) right = mid; else left = mid + 1; } rtn[1] = nums[left - 1] == target ? left - 1 : -1; return rtn; } ``` ### [1760. Minimum Limit of Balls in a Bag](https://leetcode.com/problems/minimum-limit-of-balls-in-a-bag/) operatin的定義是,可以對數字進行切割成兩個。切割的兩個數字可以自己決定。 所有的operation次數不可以超過maxOperations。想辦法讓最大的數字最小化。 > 1. 使用binary serach假設答案是某個數字。因為切割為多次,penalty就會越小。如下圖所示: ![](https://i.imgur.com/VB6oHDr.png) ```cpp int minimumSize(vector<int>& nums, int maxOperations) { int left = 1, right = *max_element(nums.begin(), nums.end()); while(left < right) { int mid = left + (right - left) / 2; int cnt = 0; for(auto& n : nums) cnt += (n - 1) / mid; if(cnt <= maxOperations) right = mid; else left = mid + 1; } return left; } ``` 2022/11/4 ```cpp // 使用binary search。則mid要搜尋什麼? // 1. 找penalty? // 2. 找ops (好像不太容易) // 把 left , mid, right所表達的意義遞增排序,就可以得到ops為遞減排序 // penalty : 1, 2, 3, 4, 5, 6, 7 // ops : 8, 7, 6, 5, 5, [5], 4 // 假設maxOperations為5,並選到了[5]這一個, // 因為往左還會有更小的penalty,所以使用小於等於,繼續往左移動 // 相當於是lower_bound的概念。 public: int minimumSize(vector<int>& nums, int maxOperations) { int left = 1, right = *max_element(nums.begin(), nums.end()); while(left < right) { // mid 為欲達成的penalty,則所需的ops為? // 因為ops越多,penalty越少 // ops 1 2 3 4 [5] 6 7 8 <-- 因為這個mid得到的ops次數 = 5 // p 9 8 7 6 [6] 5 4 3 <-- mid int mid = left + (right - left) / 2; int ops = 0; for(auto& n : nums) ops += (n - 1) / mid; if(ops <= maxOperations) right = mid; else left = mid + 1; } return left; } ``` ### [275. H-Index II](https://leetcode.com/problems/h-index-ii/) ```cpp int hIndex(vector<int>& citations) { int sz = citations.size(); int left = 0, right = sz + 1; // h index : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... // cnt : 8, 7, 7, 6, 6, 5, 5, 4, 3, 2, ... // >= < // h-index : h篇論文,每個都被引用至少h次 // 所以h[越小],[越多]論文被引用至少h次 while(left < right) { int mid = left + (right - left) / 2; int cnt = 0; for(auto& n : citations) if(n!= 0 && n >= mid) cnt++; if(cnt < mid) right = mid; else left = mid + 1; } return left - 1; } ``` 2022/11/4 ```cpp // 使用binary search。則mid要搜尋什麼? // 1. 找penalty? // 2. 找ops (好像不太容易) // 把 left , mid, right所表達的意義遞增排序,就可以得到ops為遞減排序 // 因為要找maxOperations,且越大越好,所以等於是找小於等於那一邊 public: int minimumSize(vector<int>& nums, int maxOperations) { int left = 1, right = *max_element(nums.begin(), nums.end()); while(left < right) { // mid 為欲達成的penalty,則所需的ops為? // 因為ops越多,penalty越少 // ops 1 2 3 4 [5] 6 7 8 <-- 因為這個mid得到的ops次數 = 5 // p 9 8 7 6 [6] 5 4 3 <-- mid int mid = left + (right - left) / 2; int ops = 0; for(auto& n : nums) ops += (n - 1) / mid; if(ops <= maxOperations) right = mid; else left = mid + 1; } return left; } ``` ### [658. Find K Closest Elements](https://leetcode.com/problems/find-k-closest-elements/) 給以一個排列過的遞增數列,找出K個最接近X的數,結果也要遞增排序。 > 這題一看直覺就是用sort來排序,但是效率比較差O(NlogN + KlogK + K) > 比較好的解法是使用binary search。只要找出left,則整個數列就會出來。所以使用binary search找出答案array的left在哪裡。 > 所以我們可以列出四種狀況 ![](https://i.imgur.com/Dm6iAwr.png) > case1 : x在left的左邊 > case2 : x在left和right的中間,但是比較靠近left > case3 : x在left和right的中間,但是比較靠近right > case4 : x在right的右邊 > 其中,case1和case2必須把array往左邊移動。因為必須讓x在中間,case3和case4必須把array往右邊移動。 > 上面的情況是left和right不一樣的時候,使用兩種判斷式都可以正確的移動array。 > 另外一種情況是數字有重複,則使用abs來判斷就會失效。如下圖。 ![](https://i.imgur.com/IFzpVBc.png) > 使用abs,三種情況都會是一樣。但是如果是使用 ```cpp if(x - arr[mid] < arr[mid + k] - x) { } ``` 或是使用 ```cpp if(arr[mid] - x > x - arr[mid + k]) { } ``` > 因為 x - arr[mid] 和 arr[mid + k] - x,會有==正負的差別==,所以可以判斷出需要往左還是往右移動。 > 相等的時候為什麼是向左移動? 因為我們是使用arr[mid + k]。 > arr[mid + k] 是在k之外的一個數值,如果相等表示需要向左移動。 ```cpp vector<int> findClosestElements(vector<int>& arr, int k, int x) { int left = 0, right = arr.size() - k; while (left < right) { int mid = (left + right) / 2; if (x - arr[mid] > arr[mid + k] - x) left = mid + 1; // 向右移動 else right = mid; // 向左移動 } // arr.begin() + left + k 不會取 return vector<int>(arr.begin() + left, arr.begin() + left + k); } ``` 因為binary search只找了N-K的範圍長度。 return的答案占了K長度的vector。 | Time | Space | | -------- | -------- | | $O(log(N-K))$ | $O(K)$ | ### [1898. Maximum Number of Removable Characters](https://leetcode.com/problems/maximum-number-of-removable-characters/description/) 給你兩個字串s和p,其中p為s的subsequence。在給你一個vector<int> removeable,表示可以要依序刪除的index。試問最多依序可以刪幾個char還可以保持p為s的subsequence。 > 2022/11/07 > 一開始做這一題完全沒想法,看了答案之後覺得是一題好題目。 ```cpp class Solution { // 圖1, mid為刪除char的個數,j為s中有p的個數 // mid = 0, 1, [2], 3, 4, 5, 6, 7, 8, ... // j = 4, 4, 4, 3, 3, 3, 2, 2, 1, ... // >= < // 圖2, 說明為什了是map[i] >= mid // string index 0, 1, 2, [3], 4, 5, 6, 7, 8 // mid = 3, 刪除3個(0, 1, 2), 保留6個(3, 4, 5, 6, 7, 8) // <--刪除-| |------保留------> // < >= // 0, 1, 2 // removable : 3, 1, 0 // 0, 1, 2, [3], 4, 5 // m : 2 1 M 0 M M , M : 表示不會被刪除 public: int maximumRemovals(string s, string p, vector<int>& removable) { int sz = s.size(); vector<int> map(sz, INT_MAX); for(int i = 0; i < removable.size(); ++i) map[removable[i]] = i; int left = 0, right = removable.size() + 1; while(left < right) { int mid = left + (right - left) / 2, j = 0; for(int i = 0; i < s.size() && j < p.size(); ++i) { // 圖2 if(map[i] >= mid && s[i] == p[j]) ++j; } //圖1 if(j >= p.size()) left = mid + 1; else right = mid; } return left - 1; // 圖1, 從上圖會選到p.size() - 1那一個,所以要選前一個 } }; ``` ### [792. Number of Matching Subsequences](https://leetcode.com/problems/number-of-matching-subsequences/description/) 找出vector<string> words中,會是s的subsequence的個數。 > 1. 一開始我是用trie結果TLE > 2. 先建立每個char會出現的index,方便之後尋找。 > 3. 使用 int x = -1, 則使用upper_bound第一次會找到第一個。 > 4. 依序找出每個char會出現的位置。這邊也有Greedy的意味在。因為每次只會找到大於x的最小值。 ```cpp int numMatchingSubseq(string s, vector<string>& words) { // 建立char和index的對應vector vector<vector<int>> alpha(26); for(int i = 0; i < s.size(); ++i) alpha[s[i] - 'a'].push_back(i); int rtn = 0; for(auto& word : words) { int found = true; int x = -1; //** 很重要,因為第一次會找到第一個 for(auto& c : word) { auto it = upper_bound(alpha[c - 'a'].begin(), alpha[c - 'a'].end(), x); if(it == alpha[c - 'a'].end()) {found = false; break;} else x = *it; } if(found) rtn++; } return rtn; } ``` ### [2517. Maximum Tastiness of Candy Basket](https://leetcode.com/problems/maximum-tastiness-of-candy-basket/description/) ```cpp= class Solution { public: int maximumTastiness(vector<int>& price, int k) { sort(price.begin(), price.end()); int left = 0, right = price.back() - price.front() + 1; while(left < right) { // 基於這個tastiness之下可以選到最多的candies int mid = left + (right - left) / 2; int cnt{1}; for(int i = 1, j = 0; i < price.size(); ++i) { // 因為排序過,所以最小差值只會發生在 // 目前的(i)和選到的candy最大值(j)之間 if(price[i] - price[j] >= mid) { cnt++; j = i; } } if(cnt >= k) left = mid + 1; else right = mid; } return left - 1; } }; // [13, 5, 1, 8, 21, 2], k = 3 // [1, 2, 5, 8, 13, 21] after sorting // search range : 0, 21 - 1 // // [1, 2, 5, 8, 13, 21] // t : 10 因為排序過,最小的差值只會出現在鄰近兩個之間。 // tastiness : 0, 1, 2, 3, [4], 5, 6, // cnt : 5, 4, 4, 3, 3, 2, 1 tastiness越大,可以選的candy越少。 // >= < ``` ### [2448. Minimum Cost to Make Array Equal](https://leetcode.com/problems/minimum-cost-to-make-array-equal/description/) 給你一個vector<int> nums和cost,每對一個數字nums[i]加一或是減一的行為需要cost[i],試問讓nums數字一樣的最小cost。 > 0. 有想到用binary search但是對於二次曲線不知道怎麼用。參考[lee215](https://leetcode.com/problems/minimum-cost-to-make-array-equal/solutions/2734162/java-c-python-binary-search/)的解答。 > 1. 首先想到讓vector的數字一樣,那個數字一定是在最小值和最大值之間。 ```cpp int left = 0, right = *max_element(nums.begin(), nums.end()); ``` > 2. 如果x軸是平均數,y軸是cost則會得到一個二次向上凹的曲線。一般的binary search就不適用。 > 3. 必須針對 mid, mid+1來判斷目前在曲線的那個部分。 ```cpp= long long y1 = f(mid), y2 = f(mid + 1); if(y1 < y2) right = mid; else left = mid + 1; ``` + 完整解答。 ```cpp= class Solution { public: long long minCost(vector<int>& nums, vector<int>& cost) { long long l = 1, r = *max_element(nums.begin(), nums.end()), res = LONG_MAX, x; auto f = [&](int x) { long long res{}; for(int i = 0; i < nums.size(); ++i) res += 1L * abs(nums[i] - x) * cost[i]; return res; }; while(l < r) { x = l + (r - l) / 2; long long y1 = f(x), y2 = f(x + 1); res = min({res, y1, y2}); if(y1 < y2) r = x; else l = x + 1; } return res; } // time : O(N) + O(logk * (2N)) = O(N + 2NlogK) = O(NlogK) // N : size of nums // k : the difference between minimal and maximum value // space : O(1) }; ``` ### [1851. Minimum Interval to Include Each Query](https://leetcode.com/problems/minimum-interval-to-include-each-query/description/) > 1. 一開始我也打算使用binary search來解,但是卡住了。因為我用queries來尋找interval,但是這樣會有問題。 > 2. 反過來使用intervals來尋找覆蓋到的queries就簡單多了。 > 3. 先對intervals的長度來排序。 > 4. 從最小的intervals取出來,看看是否有覆蓋的queries,有的話就跟新ans。 ```cpp= class Solution { public: vector<int> minInterval(vector<vector<int>>& intervals, vector<int>& queries) { // O(NlogN) sort(intervals.begin(), intervals.end(), [](vector<int>& a, vector<int>& b){ // O(NlogN) return a[1] - a[0] < b[1] - b[0]; // 從短到長排序 }); // O(MlogM) set<pair<int, int>> st; for(int i = 0; i < queries.size(); ++i) // O(QlogQ) st.insert(make_pair(queries[i], i)); // 把queries從小帶大排序 //O(N(logM + MlogM)) vector<int> ans(queries.size(), -1); for(int i = 0; i < intervals.size() && !st.empty(); ++i) { int s = intervals[i][0], e = intervals[i][1]; int len = e - s + 1; auto it = st.lower_bound({s, 0}); while(it != st.end() && it->first <= e) { ans[it->second] = len; advance(it, 1); st.erase(prev(it)); } } return ans; } }; // time :O(NlogN + MlogM + NMlogM) // sapce : O(M) ``` ### [852. Peak Index in a Mountain Array](https://leetcode.com/problems/peak-index-in-a-mountain-array/description/) > 0. 根據montain array的定義,arr.front()和arr.back()不會是peak。 > 1. 在peak的右邊一定滿足,arr[i] > arr[i + 1] > 2. 在peak的左邊一定滿足,arr[i] < arr[i + 1] > 3. 所以可以使用上面任一個條件來判斷。 + 使用peak左側來判斷。 ```cpp= while(left < right) { int mid = left + (right - left) / 2; if(arr[mid] < arr[mid + 1]) left = mid + 1; else right = mid; } return left; ``` + 使用peak右側來判斷 ```cpp= class Solution { public: int peakIndexInMountainArray(vector<int>& arr) { int sz = arr.size(); if(sz == 3) return 1; int left = 0, right = sz - 1; while(left < right) { int mid = left + (right - left) / 2; if(arr[mid] > arr[mid + 1]) right = mid; else left = mid + 1; } return left; } }; // * // 1, 2, 3, 4, 5, 4, 3, 2, 1 // ------------- // -------------- // arr[i] > arr[i + 1] // arr[i] < arr[i + 1] // case1 : 4, 5 , left = mid + 1 = answer // l/m r // case2 : 5, 4, right = left = 5 = answer // l/m r ``` + 這邊用左邊判斷取left,合理。但是用右邊判斷為什麼不用left - 1? + 這邊沒有target,所以之前的分析都不成立? + 只和自己的左邊和右邊比較,來更新left和right。 + 使用arr[mid] > arr[mid + 1] 來判斷會得到以下關係。 ``` // 0,10,5,2 // F, T,T,T ``` + 使用arr[mid] < arr[mid + 1] 來判斷會得到以下關係 ``` // 0,10,5,2 // T, F,F,F ``` + 可以發現不管是使用哪個判斷式,最後會收斂在0,10也就是狀態轉換的邊界,所以答案不用left - 1。 + 後來想想這種pattern其實和[278-First-Bad-VersionEasy](https://hackmd.io/u7dufNJ-SAq7z0WmS-Semw#278-First-Bad-VersionEasy)一樣,都是TTFFFF,找出TF的交界,答案會是left。 ### [2141. Maximum Running Time of N Computers](https://leetcode.com/problems/maximum-running-time-of-n-computers/) > 1. 參考官方解答。 > 2. 假設現在可以跑target的時間,則n個computer必須要有 n * target的電量。 > 3. 多少電量可以分配? 大於等於target的電池沒辦法額外使用? 假設n個電腦可以跑到target的時間,那這個電池剩下的電量也就無法使用。所以 ```cpp extra += min(target, batteries[i]); ``` > 4. 判斷式為 extra >= n * target,則可以得到以下的關係 ``` target : 1, 2, 3, [4], 5 result : T T T T F ``` > 因為要找出True的最大值,從上面的例子就是4,所以就可以得到以下的判斷式。 ```cpp= if(extra >= n * target) left = target + 1; else right = target; ``` > 5. 從上圖得知,最後結果為 left - 1。 ```cpp= class Solution { public: long long maxRunTime(int n, vector<int>& batteries) { long sum = accumulate(batteries.begin(), batteries.end(), 0L); // O(M) long left = 1, right = sum / n + 1; // K : range from left to right while(left < right) { // O(logK * M) long target = left + (right - left) / 2; long extra{}; for(auto& b : batteries) extra += min((long)b, target); if(extra >= (long)(n * target)) left = target + 1; else right = target; } return left - 1; } // time : O(M + MlogK) = O(MlogK) // space : O(1) }; // target : 1, 2, 3, 4, 5 // result : T T T T F ``` ###### tags: `leetcode` `刷題`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully