leetcode Ch1-Search
一、 Binary Search
1 int binarySearch(vector<int> &array, int target) 2 { 3 int lo = 0, hi = array.size() - 1; 4 while (lo <= hi) 5 { 6 int mid = lo + (hi - lo) / 2; 7 if (array[mid] > target) 8 hi = mid - 1; 9 else if (array[mid] < target) 10 lo = mid + 1; 11 else return mid; 12 } 13 return -1; 14 }
注意是 while(lo<=hi)
当然,也不是绝对的,这只是我的习惯写法。while里是 < 还是 <= 取决于hi/right 的初值和赋值。
/首先要把握下面几个要点: //right=n-1 => while(left <= right) => right=middle-1; //right=n => while(left < right) => right=middle;
ref to july
1 class Solution 2 { 3 public: 4 int searchInsert(vector<int> &nums,int target) 5 { 6 int lo=0,hi=nums.size()-1; 7 while(lo<=hi) 8 { 9 int mid=lo+(hi-lo)/2; 10 if(nums[mid]>target) 11 hi=mid-1; 12 else if(nums[mid]<target) 13 lo=mid+1; 14 else return mid; 15 } 16 return lo; 17 } 18 };
当循环结束时,如果没有找到目标元素,那么lo一定停在恰好比目标大的元素的index上,hi一定停在恰好比目标小的index上.
【简单理解的话,因为如果找不到目标元素,那么退出循环时必定是lo>hi的。此时lo指向大于target的元素,hi指向小于target的元素】
详细查看自己以前写的分析: ref
三、
1 class Solution 2 { 3 public: 4 vector<int> searchRange(vector<int> &nums,int target) 5 { 6 vector<int> res(2,-1); 7 int lo=0,hi=nums.size()-1; 8 while(lo<=hi) 9 { 10 int mid=lo+(hi-lo)/2; 11 if(nums[mid]>target) 12 hi=mid-1; 13 else if(nums[mid]<target) 14 lo=mid+1; 15 else 16 { 17 res[0]=lowerbound(nums,lo,mid,target); 18 res[1]=upperbound(nums,mid,hi,target)-1; 19 return res; //千万别遗漏 20 } 21 } 22 return res; 23 } 24 int upperbound(vector<int> &nums,int left,int right,int target) 25 { 26 int lo=left,hi=right; 27 while(lo<=hi) 28 { 29 int mid=lo+(hi-lo)/2; 30 if(nums[mid]>target) 31 hi=mid-1; 32 else if(nums[mid]<=target) 33 lo=mid+1; 34 } 35 return lo; 36 } 37 int lowerbound(vector<int> &nums,int left,int right,int target) 38 { 39 int lo=left,hi=right; 40 while(lo<=hi) 41 { 42 int mid=lo+(hi-lo)/2; 43 if(nums[mid]>=target) 44 hi=mid-1; 45 else if(nums[mid]<target) 46 lo=mid+1; 47 } 48 return hi+1; 49 } 50 };
注意Line17,18处,范围分别缩小成(lo,mid)和(mid,hi)了。
upperbound和lowerbound的原理详见以前的分析,第1题后面那一大段总结。
#2: upperBound返回的是第一个大于target元素的位置;lowerBound返回的是第一个小于等于target元素的位置。
所以对于一个nums中有的元素,返回其range应该是 [lowerBound, upperBound - 1]
另外,还有一点需要特别注意:Line19千万不能遗漏,如果遗漏了就成了TLE。
update 15.8.21
上述的upperBound和lowerBound中的“大于”,“小于等于”是库函数里的定义。我可以不用管那么多,就分别用lowerBound和upperBound分别返回target在数组中的第一个和最后一个的位置即可。清晰自然。
1 vector<int> searchRange(vector<int>& nums, int target) { 2 vector<int> result(2, -1); 3 int lo = 0, hi = nums.size() - 1; 4 while (lo <= hi) { 5 int mid = lo + (hi - lo) / 2; 6 if (nums[mid] < target) { 7 lo = mid + 1; 8 } else if (nums[mid] > target) { 9 hi = mid - 1; 10 } else { 11 result[0] = lowerBound(nums, target); 12 result[1] = upperBound(nums, target); 13 return result; 14 } 15 } 16 return result; 17 } 18 19 int upperBound(vector<int>& nums, int target) { 20 int lo = 0, hi = nums.size() - 1; 21 while (lo <= hi) { 22 int mid = lo + (hi - lo) / 2; 23 if (nums[mid] <= target) { 24 lo = mid + 1; 25 } else { 26 hi = mid - 1; 27 } 28 } 29 return lo - 1; 30 } 31 int lowerBound(vector<int>& nums, int target) { 32 int lo = 0, hi = nums.size() - 1; 33 while (lo <= hi) { 34 int mid = lo + (hi - lo) / 2; 35 if (nums[mid] < target) { 36 lo = mid + 1; 37 } else { 38 hi = mid - 1; 39 } 40 } 41 return hi + 1; 42 }
在upperBound函数里,当nums[mid] == target时就当没看到,继续把lo向右移动。因此当退出while循环时,lo所指向的就是最后一个元素的位置+1处。返回lo - 1即为最后一个target的位置;
同理,lowerBound里,当nums[mid] == target时就当没看到,继续把hi向左移动。因此当退出while循环时,hi所指向的就是第一个元素的位置-1处。返回hi + 1即为第一个target的位置。
2. Binary Search [lintcode]
1 class Solution { 2 public: 3 int binarySearch(vector<int> &array, int target) { 4 int lo=0,hi=array.size()-1; 5 while(lo<=hi) 6 { 7 int mid=lo+(hi-lo)/2; 8 if(array[mid]>target) 9 hi=mid-1; 10 else if(array[mid]<target) 11 lo=mid+1; 12 else 13 { 14 return lowerbound(array,lo,mid,target); 15 } 16 } 17 return -1; 18 } 19 int lowerbound(vector<int> &array,int left,int right,int target) 20 { 21 int lo=left,hi=right; 22 while(lo<=hi) 23 { 24 int mid=lo+(hi-lo)/2; 25 if(array[mid]>=target) 26 hi=mid-1; 27 else lo=mid+1; 28 } 29 return hi+1; 30 } 31 };
一开始以为是普通的binary search,后来发现它是要返回第一个target的位置(即有可能有duplicates)。借助lowerbound函数即可。
四、
1. Search in Rotated Sorted Array
O(logN):
1 class Solution 2 { 3 public: 4 int search(vector<int> &nums,int target) 5 { 6 int lo=0,hi=nums.size()-1; 7 while(lo<=hi) 8 { 9 int mid=lo+(hi-lo)/2; 10 if(nums[mid]==target) return mid; 11 if(nums[mid]<nums[hi])//说明右半段有序 12 { 13 if(target>nums[mid] && target<=nums[hi]) 14 lo=mid+1; 15 else hi=mid-1; 16 } 17 else //说明左半段有序 18 { 19 if(target<nums[mid] && target>=nums[lo]) 20 hi=mid-1; 21 else lo=mid+1; 22 } 23 } 24 return -1; 25 } 26 };
另一种方法:利用下面3. Find Minimum先把min的index找出来,然后根据target在哪个范围来对其进行binarySearch。
2. Search in Rotated Sorted Array II
1 class Solution { 2 public: 3 bool search(vector<int>& nums, int target) { 4 for(int i=0;i<nums.size();i++) 5 if(nums[i]==target) return true; 6 return false; 7 } 8 };
由于允许有duplicates,会导致没有办法像I中那样根据A[mid]和A[left]、A[right]的比较来确定是哪一半有序,应该在哪一半查找。
导致最坏时间复杂度变为O(n)。因此用最简单的遍历来实现就可以。
而普通情况下为O(logN),只在最坏情况下为O(N)的,可参考该问题下第一个答案。
3. Find Minimum in Rotated Sorted Array
code 1:
1 class Solution { 2 public: 3 int findMin(vector<int> &nums) { 4 int lo = 0, hi = nums.size() - 1; 5 int target = nums[hi]; 6 while (lo + 1 < hi) { 7 int mid = lo + (hi - lo) / 2; 8 if (nums[mid] > target) { 9 lo = mid; 10 } else { 11 hi = mid; 12 } 13 } 14 return min(nums[lo], nums[hi]); 15 } 16 };
NCH版本的binarySearch。用这种版本的binarySearch的好处是,在lo和hi中必有一个是最终结果,所以只需要在这两个里比较一下即可。而如果用我之前的BinarySearch版本,需要纠结和考虑究竟是返回lo还是hi。
用这种版本的BinarySearch需要注意的地方在于
1. while (lo + 1 < hi)
2. lo = mid; hi = mid;
另外,在退出while循环时,lo在左边,紧接着hi在lo右边,hi = lo + 1。并不是之前那样hi小lo大。
code 2:
1 class Solution { 2 public: 3 int findMin(vector<int> &nums) { 4 int lo = 0, hi = nums.size() - 1; 5 int target = nums[hi]; 6 while (lo <= hi) { 7 int mid = lo + (hi - lo) / 2; 8 if (nums[mid] > target) { 9 lo = mid + 1; 10 } else { 11 hi = mid - 1; 12 } 13 } 14 return nums[lo]; 15 } 16 };
4. Find Minimum in Rotated Sorted Array II
可以直接线性遍历一遍,最坏时间复杂度最快也只能是O(N).
五、
1 class Solution 2 { 3 public: 4 bool searchMatrix(vector<vector<int>> &matrix, int target) { 5 if(matrix.empty()) return -1; 6 int m = matrix.size(), n = matrix[0].size(); 7 int lo = 0, hi = m * n - 1; 8 while(lo <= hi) { 9 int mid = lo + (hi - lo) / 2; 10 int x = mid / n; 11 int y = mid % n; 12 if (matrix[x][y] < target) { 13 lo = mid + 1; 14 } else if (matrix[x][y] > target) { 15 hi = mid - 1; 16 } else return true; 17 } 18 return false; 19 } 20 };
一开始写的很复杂, 先找行数再找列数,代码长又易出错。后来参考soulmach,将二维问题转化为一维问题,瞬间简洁。
另外,以后要注意coding style了。
2. Search a 2D Matrix II [lintcode]
1 class Solution { 2 public: 3 int searchMatrix(vector<vector<int> > &matrix, int target) { 4 if (matrix.empty()) return 0; 5 int count = 0; 6 int m = matrix.size(), n = matrix[0].size(); 7 int row = 0, col = n - 1; 8 while (row < m && col >= 0) { 9 if (matrix[row][col] < target) { 10 row++; 11 } else if (matrix[row][col] > target) { 12 col--; 13 } else { 14 count++; 15 row++; //或者 col--也行 16 } 17 } 18 return count; 19 } 20 };
时间复杂度O(m+n). 这也是剑指offer上一道题。思路就是以右上角元素作为起点,向左下方走。每次可以删掉一行或一列。
六、
1. Sqrt(x)
1 class Solution 2 { 3 public: 4 int mySqrt(int x) { 5 if(x < 2) return x; 6 int lo = 1, hi = x; 7 while(lo <= hi) { 8 int mid = lo + ((hi - lo) >> 1); 9 if(mid < x / mid) { 10 lo = mid + 1; 11 } else if (mid > x / mid){ 12 hi = mid - 1; 13 } else return mid; 14 } 15 return hi; 16 } 17 };
2. Pow(x, n)
1 class Solution{ 2 public: 3 double myPow(double x, int n) { 4 return (n > 0? power(x , n) : 1.0 / power(x , -n)); 5 } 6 double power(double x, int n) { 7 if (n == 0) return 1; 8 double v = power(x , n / 2); 9 if (n % 2 == 1) return (v * v * x); 10 else return (v * v); 11 } 12 };
七、
1. First Bad Version [lintcode]
1 class Solution { 2 public: 3 int findFirstBadVersion(int n) { 4 int lo = 1, hi = n; 5 while (lo + 1 < hi) { 6 int mid = lo + (hi - lo) / 2; 7 if (VersionControl::isBadVersion(mid) == false) { 8 lo = mid; 9 } else { 10 hi = mid; 11 } 12 } 13 if (VersionControl::isBadVersion(lo) == true) { 14 return lo; 15 } else { 16 return hi; 17 } 18 return -1; 19 } 20 };
1 class Solution { 2 public: 3 int findPeakElement(vector<int> &nums) { 4 int lo = 0, hi = nums.size() - 1; 5 while (lo + 1 < hi) { 6 int mid = lo + (hi - lo) / 2; 7 if (nums[mid] < nums[mid - 1]) { 8 hi = mid; 9 } else if (nums[mid] < nums[mid + 1]) { 10 lo = mid; 11 } else {// nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1] 12 return mid; 13 } 14 } 15 return nums[lo] < nums[hi] ? hi : lo; 16 } 17 };
能进入Line 5的while循环说明至少有3个元素。
八、
1. Remove Duplicates from Sorted Array
1 class Solution { 2 public: 3 int removeDuplicates(vector<int> &nums) { 4 if (nums.size() < 2) { 5 return nums.size(); 6 } 7 int index = 1; 8 for (int i = 1; i < nums.size(); i++) { 9 if (nums[i] != nums[i - 1]) { 10 nums[index++] = nums[i]; 11 } 12 } 13 return index; 14 } 15 };
2. Remove Duplicates from Sorted Array II
code 1: [推荐]
1 class Solution { 2 public: 3 int removeDuplicates(vector<int> &nums) { 4 int n = nums.size(); 5 int k = 2; 6 if (n <= k) { 7 return n; 8 } 9 int index = 1, cnt = 1; 10 for (int i = 1; i < n; i++) { 11 if (nums[i] != nums[i - 1]) { 12 cnt = 1; 13 nums[index++] = nums[i]; 14 } else { 15 if (cnt < k) { 16 cnt++; 17 nums[index++] = nums[i]; 18 } 19 } 20 } 21 return index; 22 } 23 };
该代码是通用版代码。k表示单个元素最多能允许duplicate的个数。将k改为1即可用于上一题。
code 2:
1 class Solution { 2 public: 3 int removeDuplicates(vector<int> &nums) { 4 int n = nums.size(); 5 int k = 2; 6 if (n <= k) { 7 return n; 8 } 9 int index = 1, j = 1; 10 int cnt = 1; 11 while (j < n) { 12 if (nums[j] != nums[j - 1]) { 13 cnt = 1; 14 nums[index++] = nums[j]; 15 } else { 16 if (cnt < k) { 17 nums[index++] = nums[j]; 18 cnt++; 19 } 20 } 21 j++; 22 } 23 return index; 24 } 25 };
1 class Solution { 2 public: 3 void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) { 4 int i = m - 1, j = n - 1, k = m + n - 1; 5 while (i >= 0 && j >= 0) { 6 nums1[k--] = (nums1[i] >= nums2[j] ? nums1[i--] : nums2[j--]); 7 } 8 while (j >= 0) { 9 nums1[k--] = nums2[j--]; 10 } 11 } 12 };
十、 Median of Two Sorted Arrays
1 class Solution { 2 public: 3 double findMedianSortedArrays(vector<int> &nums1, vector<int> &nums2) { 4 int m = nums1.size(), n = nums2.size(); 5 if ((m + n) % 2 == 1) { 6 return findKth(nums1, 0, m - 1, nums2, 0, n - 1, (m + n) / 2 + 1); 7 } else { 8 return (findKth(nums1, 0 ,m - 1, nums2, 0, n - 1, (m + n) / 2) 9 + findKth(nums1, 0, m - 1, nums2, 0, n - 1, (m + n) / 2 + 1)) / 2.0; 10 } 11 } 12 int findKth(vector<int> &nums1, int aL, int aR, vector<int> &nums2, int bL, int bR, int k) { 13 if (aL > aR) { 14 return nums2[bL + k - 1]; 15 } 16 if (bL > bR) { 17 return nums1[aL + k - 1]; 18 } 19 int aMid = (aL + aR) / 2; 20 int bMid = (bL + bR) / 2; 21 if (nums1[aMid] < nums2[bMid]) { 22 if (k <= (aMid - aL + bMid - bL + 1)) { 23 return findKth(nums1, aL, aR, nums2, bL, bMid - 1, k); 24 } else { 25 return findKth(nums1, aMid + 1, aR, nums2, bL, bR, k - (aMid - aL + 1)); 26 } 27 } else { 28 if (k <= (aMid - aL + bMid - bL + 1)) { 29 return findKth(nums1, aL, aMid - 1, nums2, bL, bR, k); 30 } else { 31 return findKth(nums1, aL, aR, nums2, bMid + 1, bR, k - (bMid - bL + 1)); 32 } 33 } 34 } 35 };
每次取两数组的中间点进行比较。
若A数组的中间点的值 < B数组的中间点的值,则
如果k很小,则剔除B数组的后半段;( 这里的k很小指的是:k <= (A中点以左的长度 + B中点以左的长度 + 1))
如果k很大,则剔除A数组的前半段;
同理,若A数组的中间点的值 > B数组的中间点的值,也类似地讨论。
ref 有讲解。
需要注意的点:
Line22 & 28 : 严格一致。
Line 23,25,29,31: 原则就是,当k小时,就去掉较大的数组的较大的半段(后半段);当k大时,就去掉较小的数组的较小的半段(前半段)。
Line21 : <或<=均可。
十一、 三步翻转法:
1. Recover Rotated Sorted Array
1 class Solution { 2 public: 3 void recoverRotatedSortedArray(vector<int> &nums) { 4 int i = 0; 5 for (; i < nums.size() - 1; i++) { 6 if (nums[i] > nums[i + 1]) { 7 break; 8 } 9 } 10 reverse(nums, 0, i); 11 reverse(nums, i + 1, nums.size() - 1); 12 reverse(nums, 0, nums.size() - 1); 13 } 14 void reverse(vector<int> &nums, int start, int end) { 15 while (start < end) { 16 int tmp = nums[start]; 17 nums[start] = nums[end]; 18 nums[end] = tmp; 19 start++; 20 end--; 21 } 22 } 23 };
注意:必须用自己实现的reverse函数,不能用sort,因为sort的时间复杂度是O(nlogn),而题目要求是O(n);
1 class Solution { 2 public: 3 string rotateString(string A, int offset) { 4 int len = A.length(); 5 if (len <= 0) { 6 return A; 7 } 8 offset = offset % len; 9 reverse(A, 0, len - 1 - offset); 10 reverse(A, len - offset, len - 1); 11 reverse(A, 0, len - 1); 12 return A; 13 } 14 void reverse(string &str, int start, int end) { 15 while (start < end) { 16 int tmp = str[start]; 17 str[start] = str[end]; 18 str[end] = tmp; 19 start++; 20 end--; 21 } 22 } 23 };
注意小细节:
(1). offset需要取余; (2). 当len == 0 时需要作特殊处理。corner case.
3. Rotate Array
1 class Solution { 2 public: 3 void rotate(vector<int>& nums, int k) { 4 k = k % nums.size(); 5 reverse(nums, 0, nums.size() - 1 - k); 6 reverse(nums, nums.size() - k, nums.size() - 1); 7 reverse(nums, 0, nums.size() - 1); 8 } 9 void reverse(vector<int> &nums, int start, int end) { 10 while (start < end) { 11 int tmp = nums[start]; 12 nums[start] = nums[end]; 13 nums[end] = tmp; 14 start++; 15 end--; 16 } 17 } 18 };
和上一题一样。
方法一:空间O(n), 时间O(n)
1 class Solution { 2 public: 3 void reverseWords(string &s) { 4 string result; 5 for (int i = s.size() - 1; i >= 0;) { 6 while (i >= 0 && s[i] == ' ') { 7 i--; 8 } 9 if (i < 0) { 10 break; 11 } 12 if (!result.empty()) { 13 result += ' '; 14 } 15 string word; 16 while (i >= 0 && s[i] != ' ') { 17 word += s[i]; 18 i--; 19 } 20 reverse(word.begin(), word.end()); 21 result += word; 22 } 23 s = result; 24 } 25 };
注意:Line 9 千万不能少,这句代码的意义在于,抛弃开头的那些leading spaces。
方法二:空间O(1) [in-place], 时间O(n).
1 class Solution { 2 public: 3 void reverseWords(string &s) { 4 reverse(s, 0, s.size() - 1); 5 int index = 0; 6 for (int i = 0; i < s.size(); i++) { 7 if (s[i] != ' ') { 8 if (index != 0) { 9 s[index++] = ' '; 10 } 11 int j = i; 12 while (j < s.size() && s[j] != ' ') { 13 s[index++] = s[j++]; 14 } 15 reverse(s, index - (j - i), index - 1); 16 i = j; 17 } 18 } 19 s.resize(index); 20 } 21 void reverse(string &str, int start, int end) { 22 while (start < end) { 23 int tmp = str[start]; 24 str[start] = str[end]; 25 str[end] = tmp; 26 start++; 27 end--; 28 } 29 } 30 };
5. Reverse Words in a String II
1 class Solution { 2 public: 3 void reverseWords(string &s) { 4 reverse(s, 0, s.size() - 1); 5 int index = 0; 6 for (int j = 0; j <= s.size(); j++) { 7 if (j == s.size() || s[j] == ' ') { 8 reverse(s, index, j - 1); 9 index = j + 1; 10 } 11 } 12 } 13 void reverse(string &str, int start, int end) { 14 while (start < end) { 15 int tmp = str[start]; 16 str[start] = str[end]; 17 str[end] = tmp; 18 start++; 19 end--; 20 } 21 } 22 };
注意:这里让j从0一直循环到s.size(), 原因在于将“遇到空格”和“遇到句末”统一到一个if里,使代码简洁。