算法总结—二分搜索与旋转排序数组
一. 二分搜索(Binary Search)模板及其理解
1.通用模板,解决start, end, mid, <(<=), >(>=)等问题
http://www.lintcode.com/en/problem/binary-search/
class Solution { public: /** * @param nums: The integer array. * @param target: Target number to find. * @return: The first position of target. Position starts from 0. */ int binarySearch(vector<int> &array, int target) { // write your code here if(array.size() == 0){ return -1; } int start = 0, end = array.size() - 1; while(start + 1 < end){ int mid = start + (end - start) / 2; if(array[mid] == target){ end = mid; }else if(array[mid] < target){ start = mid; } else{ end = mid; } } if(array[start] == target){ return start; } if(array[end] == target){ return end; } return -1; } };
注意事项:
1). start + 1 < end (最后会剩下start, end两项);
2). mid = start + (end - start) / 2; (避免出现start + end越界情况);
3). A[mid] 依次比较 ==, <, >;
4). A[start], A[end]与target比较。 (对应first / last position问题,对应的比较顺序不用)
2. 对二分搜索的理解
1) 字面意义的二分搜索,对有序数组取中间值,比较并折半删除
2)first / last position问题,利用模板可以很好地解决
3)只有能判断解在某部分集合里,就可以将规模缩小(不一定是简单的在中间位置左侧或右侧),若每次规模近似缩小一半,仍然是二分的思路;
即将一个O(n)问题通过O(1)操作转化成一个O(k/2)的问题。
4)当复杂度要求O(logn)时,往往可以考虑二分搜索
二 常见问题
2.1 基础二分问题变种
1)Search a 2D matrix i
https://leetcode.com/problems/search-a-2d-matrix/
思路:将二维矩阵看做线性序列,则为典型二分搜索问题,所以只需要线性序列与二维数组小标的对应
即: mid / n , mid % n
class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { int m = matrix.size(), n = matrix[0].size(); int start = 0, end = m*n - 1; while(start + 1 < end){ int mid = start + (end - start) / 2; if(matrix[mid/n][mid%n] == target){ return 1; } else if(matrix[mid/n][mid%n] < target){ start = mid; } else{ end = mid; } } if (matrix[start/n][start%n] == target) { return 1; } if (matrix[end/n][end%n] == target) { return 1; } return 0; } };
2)Search a 2D matrix ii
https://leetcode.com/problems/search-a-2d-matrix-ii/
思路:根据矩阵的性质,从左下角向右上寻找。
如果target大于当前元素,当前列上方元素可删除,所以向右走一步;
如果target小于当前元素,当前行右方元素可删除,所以向上走一步。
class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { int m = matrix.size(); int n = matrix[0].size(); int i = m - 1, j = 0; while(i >= 0 && j < n){ if(matrix[i][j] == target){ return 1; } else if(matrix[i][j] < target){ j++; } else{ i--; } } return 0; } };
2.2 first/last position 问题
1)Search for a range
https://leetcode.com/problems/search-for-a-range/
思路:使用两遍模板,分别找到first 和 last position,则找到对应区间
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if(nums.size() == 0){ return vector<int>{0,0}; } int start = 0, end = nums.size() - 1; vector<int> range; // search for left bound while(start + 1 < end){ int mid = start + (end - start) / 2; if(nums[mid] == target){ end = mid; } else if(nums[mid] < target){ start = mid; } else{ end = mid; } } if(nums[start] == target){ range.push_back(start); } else if(nums[end] == target){ range.push_back(end); } else{ range.push_back(-1); } start = 0; end = nums.size() - 1; // search for right bound while(start + 1 < end){ int mid = start + (end - start) / 2; if(nums[mid] == target){ start = mid; } else if(nums[mid] < target){ start = mid; } else{ end = mid; } } if(nums[end] == target){ range.push_back(end); } else if(nums[start] == target){ range.push_back(start); } else{ range.push_back(-1); } return range; } };
2) Search insert position
https://leetcode.com/problems/search-insert-position/
思路:在数组中找到第一个大于等于target的位置(find first position)
class Solution { public: int searchInsert(vector<int>& nums, int target) { if(nums.size() == 0){ return 0; } int start = 0,end = nums.size() - 1; while(start + 1 < end){ int mid = start + (end - start) / 2; if(nums[mid] == target){ end = mid; } else if(nums[mid] < target){ start = mid; } else{ end = mid; } } if(nums[start] >= target){ return start; } else if(nums[end] >= target){ return end; } else{ return end + 1; } } };
3) Find bad version
https://leetcode.com/problems/first-bad-version/
思路:找到第一个bad version(first position)
// Forward declaration of isBadVersion API. bool isBadVersion(int version); class Solution { public: int firstBadVersion(int n) { if(n == 0){ return 0; } int start = 1, end = n; while(start + 1 < end){ int mid = start + (end - start) / 2; if(isBadVersion(mid)){ end = mid; } else { start = mid; } } if(isBadVersion(start)){ return start; } if(isBadVersion(end)){ return end; } } };
4) Sqrt(x)
https://leetcode.com/problems/sqrtx/
思路:找到最后一个平方小于等于x的数
class Solution { public: int mySqrt(int x) { long long start = 0, end = x; while(start + 1 < end){ long long mid = start + (end - start) / 2; if(mid*mid == x){ return mid; } else if(mid * mid < x){ start = mid; } else{ end = mid; } } if(end * end <= x){ return end; } return start; } };
2.3 较复杂二分搜索问题(包含旋转排序数组内搜索)
这几个问题仍然采用二分思想,但二分后删除部分集合的调节并不明显,往往需要通过画图帮助理解。
同时此类问题很多也可以转换成增加条件的first position / last position问题,从而使用二分模板。
1) Find peak element
https://leetcode.com/problems/find-peak-element/
思路:因为题目假设了num[-1] = num[n] = - 所以对于nums[mid]来讲,其与左右元素之间无非只有三种情况:
(1) nums[mid-1] < nums[mid] && nums[mid] > nums[mid+1] ,此时mid即为peak element;
(2) nums[mid-1] < nums[mid] && nums[mid] < nums[mid+1],此时删除数组左半部分仍然可保证有peak element, mid = start;
(3)nums[mid-1] > nums[mid] && nums[mid] > nums[mid+1], 此时删除数组左半部分仍然可保证有peak element,mid = end;
(4)在波谷位置,随意左右均可。
class Solution { public: int findPeakElement(vector<int>& nums) { int start = 0, end = nums.size() - 1; while(start + 1 < end ){ int mid = start + (end - start) / 2; if(nums[mid - 1] < nums[mid] && nums[mid] > nums[mid+1]){ return mid; } else if(nums[mid - 1] < nums[mid] && nums[mid] < nums[mid + 1]){ start = mid; } else{ end = mid; } } if(nums[start] > nums[end]){ return start; } return end; } };
2) Find Minimum in Rotated Sorted Array
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
思路: 根据旋转排序数组性质,问题转化为find first element which is bigger than nums[nums.size() - 1];
class Solution { public: int findMin(vector<int>& nums) { int start = 0, end = nums.size() - 1; int target = nums[nums.size() - 1]; while(start + 1 < end){ int mid = start + (end - start) / 2; if(nums[mid] > target){ start = mid; } else{ end = mid; } } if(nums[start] < target){ return nums[start]; } return nums[end]; } };
3) Search in Rotated Sorted Array
https://leetcode.com/problems/search-in-rotated-sorted-array/
思路:分情况进行“二分”的删除区间操作
首先区分target与nums[nums.size() - 1]的关系
其次在不同区间内,根据target与nums[mid]的关系进行相关删除区间操作。
class Solution { public: int search(vector<int>& nums, int target) { if(nums.size() == 0){ return -1; } int start = 0, end = nums.size() - 1; int temp = nums[end]; while(start + 1 < end){ int mid = start + (end - start) / 2; if(nums[mid] == target){ return mid; } if(temp == target){ return nums.size() - 1; } if(temp < target){ if(nums[mid] < target && nums[mid] > temp){ start = mid; } else{ end = mid; } } else{ if(nums[mid] > target && nums[mid] < temp){ end = mid; } else{ start = mid; } } } if(nums[start] == target){ return start; } if(nums[end] == target){ return end; } return -1; } };
4) Median of Two Sorted Arrays
https://leetcode.com/problems/median-of-two-sorted-arrays/
思路:取两个数组中长度较小的那个,利用归并排序的思想,我们从a和b中一共拿k个数看下假设 length(a) < length(b)
从a中取pa = min(k / 2, length(a))个元素,从b中取pb = k – pa个元素;
如果a[pa - 1] < b [pb - 1], 说明a取少了,第k个在a后半部分和b前半部分,反之同理。
class Solution { public: int findk(vector<int>& nums1, vector<int>& nums2, int k){ if(nums1.size() > nums2.size()){ return findk(nums2, nums1, k); } if(nums1.size() == 0){ return nums2[k-1]; } if(k == 1){ return min(nums1[0], nums2[0]); } int len = nums1.size(); int p1 = min(k/2, len); int p2 = k - p1; vector<int> nums11(nums1.begin() + p1, nums1.end()); vector<int> nums21(nums2.begin(), nums2.begin() + p2); vector<int> nums12(nums1.begin(), nums1.begin() + p1); vector<int> nums22(nums2.begin() + p2, nums2.end()); return (nums1[p1 - 1] < nums2[p2 - 1]) ? findk( nums11, nums21 , k - p1) : findk( nums12, nums22, k - p2); } double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int m = nums1.size(), n = nums2.size(); if( (m + n) % 2 == 1){ return findk(nums1, nums2, (m + n + 1) / 2); } else{ return (findk(nums1, nums2, (m + n) / 2) + findk(nums1, nums2, (m + n) / 2 + 1) ) / 2.0; } } };