LeetCode分类刷题(四):二分法(Binary Search)

二分查找算法虽然简单,却是笔试和面试题中出现的高频题,经常用来在有序的数组或者矩阵中查找某个特定的位置。

二分法简单介绍:

(1)算法定义:

  • 二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。二分查找法的时间复杂度是对数级别的,O(log2n)

(2)基本思想:

  • 假设数据是按升序排序的,对于给定值key,从序列的中间位置k开始比较,
  • 如果当前位置arr[k]值等于key,则查找成功;
  • 若key小于当前位置值arr[k],则在数列的前半段中查找,arr[low,mid-1];
  • 若key大于当前位置值arr[k],则在数列的后半段中继续查找arr[mid+1,high],
  • 直到找到为止,时间复杂度:O(log(n))。

(3)优缺点:

  • 优点是比较次数少,查找速度快,平均性能好;
  • 缺点是要求待查表为有序表,且插入删除困难;

LeetCode中关于排序的题目有以下四种类型题:

(一)二分法之普通排序数组相关题目:

(二)二分法之变形排序数组相关题目:

(三)二分法之排序矩阵相关题目:

(四)二分法之实例应用相关题目:


(一)二分法之普通排序数组相关题目:

704. Binary Search

  • Given a sorted (in ascending order) integer array nums of n elements and a target value, write a function to search target in nums. If target exists, then return its index, otherwise return -1.
  • 题目要求:给定一个排序的整数数组(升序)和一个要查找的整数target,用O(logn)的时间查找到target出现的下标(从0开始),如果target不存在于数组中,返回-1
  • 题目解析:很常规的思想,二分法的基本思想,如上面介绍。
  • 题目解答:
class Solution {
public:
    int search(vector& nums, int target) {
        int low = 0, high = nums.size() - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else return mid;
        }
        return -1;
    }
};

35. Search Insert Position

  • Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. You may assume no duplicates in the array.
  • 题目要求:给定一个target,和有序序列,如果target在序列中,则返回其索引,否则给出当插入target且不改变序列性质时插入的位置(索引)。
  • 题目解析:很常规的思想,二分法的基本思想,如上面介绍。
  • 题目解答:
class Solution {
public:
    int searchInsert(vector& nums, int target) {
        int low = 0, high = nums.size() - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else return mid;
        }
        return low;
    }
};

34. Find First and Last Position of Element in Sorted Array

  • Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value. Your algorithm's runtime complexity must be in the order of O(log n). If the target is not found in the array, return [-1, -1].
  • 题目要求:这道题让我们在一个有序整数数组中寻找相同目标值的起始和结束位置,而且限定了时间复杂度为O(logn)
  • 题目解析:使用两次二分查找法,第一次找到左边界,第二次调用找到右边界即可
  • 题目解答:
class Solution {
public:
    vector searchRange(vector& nums, int target) {
        vector res(2,-1);
        int n = nums.size(), low = 0, high = n - 1, mid = 0;
        if(n == 0) return res;
        while(low < high){
            mid = low + (high - low) / 2;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else high = mid;
        }
        if(nums[low] == target) res[0] = low;
        else return res;
        high = n - 1;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else low = mid + 1;
        }
        res[1] = low - 1;
        return res;
    }
};

(二)二分法之变形排序数组相关题目:

33. Search in Rotated Sorted Array

  • Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). You are given a target value to search. If found in the array return its index, otherwise return -1. You may assume no duplicate exists in the array. Your algorithm's runtime complexity must be in the order of O(log n).
  • 题目要求:这道题让在旋转数组中搜索一个给定值,若存在返回坐标,若不存在返回-1。
  • 题目解析:二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段。如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了。
  • 题目解答:
class Solution {
public:
    int search(vector& nums, int target) {
        int n= nums.size(), low = 0, high = n - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(nums[mid] == target) return mid;
            if(nums[low] <= nums[mid]){
                if(nums[low] <= target && nums[mid] >= target) high = mid - 1;
                else low = mid + 1;
            }else{
                if(nums[high] >= target && nums[mid] <= target) low = mid + 1;
                else high = mid - 1;
            }
        }
        return -1;
    }
};

81. Search in Rotated Sorted Array II

  • Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., [0,0,1,2,2,5,6] might become [2,5,6,0,0,1,2]). You are given a target value to search. If found in the array return true, otherwise return false.
  • 题目要求:这道题让在旋转数组中搜索一个给定值,若存在返回坐标,若不存在返回-1(可能有重复的数据)。
  • 题目解析:如果可以有重复值,就会出现来面两种情况,[3 1 1] 和 [1 1 3 1],对于这两种情况中间值等于最右值时,目标值3既可以在左边又可以在右边,那怎么办么,对于这种情况其实处理非常简单,只要把最左值向右一位即可继续循环,如果还相同则继续移,直到移到不同值为止。
  • 题目解答:
class Solution {
public:
    bool search(vector& nums, int target) {
        int n= nums.size(), low = 0, high = n - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(nums[mid] == target) return true;
            if(nums[low] < nums[mid]){
                if(nums[low] <= target && nums[mid] >= target) high = mid - 1;
                else low = mid + 1;
            }else if(nums[low] > nums[mid]){
                if(nums[high] >= target && nums[mid] <= target) low = mid + 1;
                else high = mid - 1;
            }else{
                low++;
            }
        }
        return false;
    }
};

153. Find Minimum in Rotated Sorted Array

  • Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e.,  [0,1,2,4,5,6,7] might become  [4,5,6,7,0,1,2]). Find the minimum element. You may assume no duplicate exists in the array.
  • 题目要求:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{4,5,6,7,0,1,2}为{0,1,2,4,5,6,7}的一个旋转,该数组的最小值为0。
  • 题目解析:Step1.和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。Step2.接着我们可以找到数组中间的元素:如果该中间元素位于前面的递增子数组,那么它应该大于最后一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一个指针指向该中间元素下一个位置,这样可以缩小寻找的范围。如果中间元素位于后面的递增子数组,那么它应该小于或等于最后一个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把最后一个指针指向该中间元素,这样可以缩小寻找的范围Step3.接下来我们再用更新之后的两个指针,重复做新一轮的查找。
  • 题目解答:
class Solution {
public:
    int findMin(vector& nums) {
        int n = nums.size(), low = 0, high = n - 1, mid = 0;
        while(low < high){
            if(nums[low] < nums[high]) return nums[low];
            mid = (low + high) / 2;
            if(nums[mid] > nums[high]) low = mid + 1;
            else high = mid;
        }
        return nums[low];
    }
};

154. Find Minimum in Rotated Sorted Array II

  • Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e.,  [0,1,2,4,5,6,7] might become  [4,5,6,7,0,1,2]). Find the minimum element. The array may contain duplicates.
  • 题目要求:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{4,5,6,7,0,1,2}为{0,1,2,4,5,6,7}的一个旋转,该数组的最小值为0。
  • 题目解析:如果可以有重复值,就会出现来面两种情况,[3 1 1] 和 [1 1 3 1],对于这两种情况中间值等于最右值时,目标值3既可以在左边又可以在右边,那怎么办么,对于这种情况其实处理非常简单,只要把最右值向左一位即可继续循环,如果还相同则继续移,直到移到不同值为止。
  • 题目解答:
class Solution {
public:
    int findMin(vector& nums) {
        int n = nums.size(), low = 0, high = n - 1, mid = 0;
        while(low < high){
            if(nums[low] < nums[high]) return nums[low];
            mid = (low + high) / 2;
            if(nums[mid] > nums[high]) low = mid + 1;
            else if(nums[mid] < nums[high]) high = mid;
            else high--;
        }
        return nums[low];
    }
};

(三)二分法之排序矩阵相关题目:

74. Search a 2D Matrix

  • Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted from left to right. The first integer of each row is greater than the last integer of the previous row.
  • 题目要求:编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:每行中的整数从左到右按升序排列。每行的第一个整数大于前一行的最后一个整数。
  • 题目解析:我们按S型遍历该二维数组,可以得到一个有序的一维数组,那么我们只需要用一次二分查找法,而关键就在于坐标的转换,如何把二维坐标和一维坐标转换是关键点,把一个长度为n的一维数组转化为m*n的二维数组(m*n = n)后,那么原一维数组中下标为i的元素将出现在二维数组中的[i/n][i%n]的位置。
  • 题目解答:
class Solution {
public:
    bool searchMatrix(vector>& matrix, int target) {
        if(matrix.empty() || matrix[0].empty()) return false;
        if(target < matrix[0][0] || target > matrix.back().back()) return false;
        int m = matrix.size(), n = matrix[0].size();
        int low = 0, high = m * n - 1, mid = 0, temp = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            temp = matrix[mid / n][mid % n];
            if(temp > target) high = mid - 1;
            else if(temp < target) low = mid + 1;
            else return true;
        }
        return false;
    }
};

240. Search a 2D Matrix II

  • Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted in ascending from left to right. Integers in each column are sorted in ascending from top to bottom.
  • 题目要求:编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。
  • 题目解析:对于matrix[x][y]的元素,如果target大于该元素,说明这一整行的元素都不符合因为,行是排好序的,所以要移到下一行,同理,如果target小于该元素,说明这一整列元素都不符合,因为列也是排序的,所以要前移一列。这样就可以逐渐逼近近似值了。
  • 题目解答:
class Solution {
public:
    bool searchMatrix(vector>& matrix, int target) {
        if(matrix.empty() || matrix[0].empty()) return false;
        if(target < matrix[0][0] || target > matrix.back().back()) return false;
        int x = matrix.size() - 1, y = 0;
        while(true){
            int temp = matrix[x][y];
            if(temp < target) y++;
            else if(temp > target) x--;
            else return true;
            if(x < 0 || y >= matrix[0].size()) break;
        }
        return false;
    }
};

378. Kth Smallest Element in a Sorted Matrix

  • Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix. Note that it is the kth smallest element in the sorted order, not the kth distinct element.
  • 题目要求:给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。请注意,它是排序后的第k小元素,而不是第k个元素。
  • 题目解析:我们由于是有序矩阵,那么左上角的数字一定是最小的,而右下角的数字一定是最大的,所以这个是我们搜索的范围,然后我们算出中间数字mid,由于矩阵中不同行之间的元素并不是严格有序的,所以我们要在每一行都查找一下mid,我们使用upper_bound,这个函数是查找第一个大于目标数的元素,如果目标数在比该行的尾元素大,则upper_bound返回该行元素的个数,如果目标数比该行首元素小,则upper_bound返回0, 我们遍历完所有的行可以找出中间数是第几小的数,然后k比较,进行二分查找,left和right最终会相等,并且会变成数组中第k小的数字。(方便理解,我们没有直接使用upper_bound函数,但道理一样)。
  • 题目解答:
class Solution {
public:
    int kthSmallest(vector>& matrix, int k) {
        int row = matrix.size(), col = matrix[0].size();
        int low = matrix[0][0], high = matrix[row - 1][col - 1];
        while(low < high){
            int mid = (low + high) / 2, j = col - 1, cnt = 0;
            for(int i = 0; i < row; i++){
                while(j >=0 && matrix[i][j] > mid)j--;
                cnt +=(j + 1);
            }
            if(cnt < k) low = mid + 1;
            else high = mid;
        }
        return low;
    }
};

(四)二分法之实例应用相关题目:

374. Guess Number Higher or Lower

  • We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to guess which number I picked. Every time you guess wrong, I'll tell you whether the number is higher or lower. You call a pre-defined API guess(int num) which returns 3 possible results (-11, or 0):
  • 题目要求:猜数游戏,要求返回指定的数字,可调用guess(n)查看所猜的数字n与指定数字的大小关系。
  • 题目解析:你猜一个数字,内置的guess(int num)函数告诉你要猜的数字比你猜的数字的值低了还是高了。例如:目标数字是6,guess(10)会返回-1,因为6<10。如果正好猜对了就返回0。确定一个比目标值大的右边界,再确定一个比目标值小的左边界,不断取中间值进行比较,然后确定新边界,最终一定会找到和目标值相等的n。
  • 题目解答:
// Forward declaration of guess API.
// @param num, your guess
// @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
int guess(int num);

class Solution {
public:
    int guessNumber(int n) {
        int low = 1, high = n, mid = 0, temp = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            temp = guess(mid);
            if(temp == -1) high = mid - 1;
            else if(temp == 1) low = mid + 1;
            else return mid;
        }
        return -1;
    }
};

278. First Bad Version

  • Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad. You are given an API bool isBadVersion(version) which will return whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.
  • 题目要求:这道题说是有一系列版本,其中有一个版本是坏的,而且后面跟着的全是坏的,给了一个API函数可以用来判定当前版本是否是坏的,让我们尽可能少的调用这个API,找出第一个坏版本。
  • 题目解析:由于这题很有规律,好版本和坏版本一定有个边界,那么我们用二分法来找这个边界,对mid值调用API函数,如果是坏版本,说明边界在左边,则把mid赋值给right,如果是好版本,则说明边界在右边,则把mid+1赋给left,最后返回left即可。需要注意的是,OJ里有个坑,那就是如果left和right都特别大的话,那么left+right可能会溢出,我们的处理方法就是变成left + (right - left) / 2,很好的避免的溢出问题。
  • 题目解答:
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int low = 1, high = n, mid = 0;
        while(low < high){
            mid = low + (high - low) / 2;
            bool temp = isBadVersion(mid);
            if(temp) high = mid;
            else low = mid + 1;
        }
        return low;
    }
};

275. H-Index II

  • Given an array of citations sorted in ascending order (each citation is a non-negative integer) of a researcher, write a function to compute the researcher's h-index. According to the definition of h-index on Wikipedia: "A scientist has index h if h of his/her N papers have at least h citations each, and the other N − h papers have no more than citations each."
  • 题目要求:这道题让我们求H指数,这个质数是用来衡量研究人员的学术水平的质数,定义为一个人的学术文章有n篇分别被引用了n次,那么H指数就是n。而且wiki上直接给出了算法,可以按照如下方法确定某人的H指数:1、将其发表的所有SCI论文按被引次数从高到低排序;2、从前往后查找排序后的列表,直到某篇论文的序号大于该论文被引次数。所得序号减一即为H指数。
  • 题目解析:素组有序,让我们在O(log n)的时间内完成计算。看到这个时间复杂度,应该有很敏锐的意识应该用二分查找法,我们最先初始化left和right为0和数组长度len-1,然后取中间值mid,比较citations[mid]和len-mid做比较,如果前者大,则right移到mid之前,反之right移到mid之后,终止条件是left>right,最后返回len-left即可。
  • 题目解答:
class Solution {
public:
    int hIndex(vector& nums) {
        int n = nums.size(), low = 0, high = n - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            int target = n - mid;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else return n - mid;
        }
        return n - low;
    }
};

274. H-Index

  • Given an array of citations (each citation is a non-negative integer) of a researcher, write a function to compute the researcher's h-index.
  • 题目要求:这道题让我们求H指数,数组无序。
  • 题目解析:数组无序,排序后在使用上面的二分法,很简单。
  • 题目解答:
class Solution {
public:
    int hIndex(vector& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size(), low = 0, high = n - 1, mid = 0;
        while(low <= high){
            mid = low + (high - low) / 2;
            int target = n - mid;
            if(nums[mid] < target) low = mid + 1;
            else if(nums[mid] > target) high = mid - 1;
            else return n - mid;
        }
        return n - low;
    }
};

162. Find Peak Element

  • A peak element is an element that is greater than its neighbors. Given an input array nums, where nums[i] ≠ nums[i+1], find a peak element and return its index. The array may contain multiple peaks, in that case return the index to any one of the peaks is fine. You may imagine that nums[-1] = nums[n] = -∞.
  • 题目要求:道题是求数组的一个峰值,题目中说了这个峰值可以是局部的最大值,所以我们只需要找到第一个局部峰值就可以了。
  • 题目解析:所谓峰值就是比周围两个数字都大的数字,那么只需要跟周围两个数字比较就可以了。题目中要求在对数时间内完成查找,我们考虑使用二分查找。如果中间元素大于其相邻的后续元素,则中间元素左侧(包括中间元素)必然包含一个局部最大值,如果中间元素小于其相邻的后续元素,则中间元素右侧必然包含一个局部最大值。直到最后左边沿和右边沿相遇,我们找到所求峰值。
  • 题目解答:
class Solution {
public:
    int findPeakElement(vector& nums) {
        int n = nums.size(), low = 0, high = n - 1;
        while(low < high){
            int mid1 = (low + high) / 2, mid2 = mid1 + 1;
            if(nums[mid1] < nums[mid2]) low = mid2;
            else high = mid1;
        }
        return low;
    }
};

852. Peak Index in a Mountain Array

  • Let's call an array A a mountain if the following properties hold: A.length >= 3 There exists some 0 < i < A.length - 1 such that A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] Given an array that is definitely a mountain, return any i such that A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1].
  • 题目要求:找出一个数组中的山峰的索引。山峰的意思是指在一个长度大于3的数组中,某个数值比相邻的两个数值都大。
  • 题目解析:与上一题相似,只需修改返回条件即可。
  • 题目解答:
class Solution {
public:
    int peakIndexInMountainArray(vector& nums) {
        int n = nums.size(), low = 0, high = n - 1;
        while(low < high){
            int mid = (low + high) / 2;
            if(nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) return mid;
            if(nums[mid] < nums[mid + 1]) low = mid + 1;
            else high = mid;
        }
        return low;
    }
};

如果各位看官们,大神们发现了任何错误,或是代码无法通过OJ,或是有更好的解法,或是有任何疑问,意见和建议的话,请一定要在帖子下面评论区留言告知博主啊,多谢多谢,祝大家刷得愉快,刷得精彩,刷出美好未来~

你可能感兴趣的:(LeetCode刷题)