二分查找与旋转排序数组 算法题

一. 二分搜索(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;
    }
};
Search a 2D matrix i

 

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;
    }
};
Search a 2D matrix ii

 

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;
    }
};
Search for a 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;
        }
    }
};
Search insert position

 

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;
        }
    }
};
find bad version

 

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;
    }
};
Sqrt(x)

 

 

 

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;
    }
};
find peak element

 

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];
    }
};
Find Minimum in rotated sorted array

 

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;
    } 
};
Search in rotated sorted array

 

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;
        }
        
    }
};
Median of Two sorted arrays

 

 

 

 

你可能感兴趣的:(二分查找与旋转排序数组 算法题)