二分查找及其应用
二分查找
附一个关于二分查找边界选择的解释:
二分查找
基础的二分查找,
- 时间复杂度:O(logN)。
- 空间复杂度:O(1)。
class Solution { public: int search(vector<int>& nums, int target) { int mid, left = 0, right = nums.size() - 1; while (left <= right) { mid = left + (right - left) / 2; if (nums[mid] == target) return mid; if (target < nums[mid]) right = mid - 1; else left = mid + 1; } return -1; } };
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。假设数组中无重复元素。
- 时间复杂度:O(logN)。
- 空间复杂度:O(1)。
class Solution { public: int searchInsert(vector<int>& nums, int target) { int n = nums.size(); int left = 0, right = n - 1, ans = n; while (left <= right) { int mid = ((right - left) >> 1) + left; if (target <= nums[mid]) { ans = mid; right = mid - 1; } else { left = mid + 1; } } return ans; } };
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,返回 [-1, -1]。
- 时间复杂度:O(logN)。
- 空间复杂度:O(1)。
class Solution { public: int n; int leftwer_bound(vector<int>& nums, int target) { //[left,right)前闭后开区间 int left = 0, right = n; while (left<right) { int mid = (left + right) >> 1; if (target <= nums[mid]) right = mid; else left = mid + 1; } return left; } int upper_bound(vector<int>& nums, int target) { //[left,right)前闭后开区间 int left = 0, right = n; while (left<right) { int mid = (left + right) >> 1; if (target<nums[mid]) right = mid; else left = mid + 1; } return left; } vector<int> searchRange(vector<int>& nums, int target) { n = nums.size(); int left = leftwer_bound(nums, target); int right = upper_bound(nums, target); if (left == right) return vector<int>{-1, -1}; return vector<int>{left, right - 1}; } };
搜索旋转排序数组
在常规二分搜索的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分搜索的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分
- 时间复杂度: O(logn),其中nums数组的大小。整个算法时间复杂度即为二分搜索的时间复杂度O(logn)。
- 空间复杂度: O(1) 。我们只需要常数级别的空间存放变量。
class Solution { public: int search(vector<int>& nums, int target) { int n = (int)nums.size(); if (!n) return -1; if (n == 1) return nums[0] == target ? 0 : -1; int l = 0, r = n - 1; while (l <= r) { int mid = (l + r) / 2; if (nums[mid] == target) return mid; if (nums[0] <= nums[mid]) { if (nums[0] <= target && target < nums[mid]) { r = mid - 1; } else { l = mid + 1; } } else { if (nums[mid] < target && target <= nums[n - 1]) { l = mid + 1; } else { r = mid - 1; } } } return -1; } };
搜索旋转排序数组 II
和上一题一样,不过此时nums可能包含重复元素。
- 时间复杂度:最坏情况o(n),最好情况o(logn)
- 时间复杂度:O(1)
class Solution { public: bool search(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left <= right) { while (left != right && nums[left] == nums[right]) right--; //无重复值的解法中添加这行 int mid = (left + right) / 2; if (nums[mid] == target) return true; else if (nums[mid]>target) { if (nums[mid]>nums[right] && target<nums[left]) left = mid + 1; else right = mid - 1; } else { if (nums[mid]<nums[left] && target>nums[right]) right = mid - 1; else left = mid + 1; } } return false; } };
寻找旋转排序数组中的最小值
假设数组中不存在重复元素。
- 时间复杂度:O(logN)。
- 空间复杂度:O(1)。
class Solution { public: int findMin(vector<int>& nums) { int left = 0; int right = nums.size() - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[right]) { left = mid + 1; } else { right = mid; } } return nums[left]; } };
寻找旋转排序数组中的最小值 II
nums可能有重复值
- 时间复杂度:平均时间复杂度为 O(logn),其中 n 是数组 nums 的长度。如果数组是随机生成的,那么数组中包含相同元素的概率很低,在二分查找的过程中,大部分情况都会忽略一半的区间。而在最坏情况下,如果数组中的元素完全相同,那么 while 循环就需要执行 n 次,每次忽略区间的右端点,时间复杂度为O(n)。
- 空间复杂度:O(1)。
class Solution { public: int findMin(vector<int>& nums) { int low = 0; int high = nums.size() - 1; while (low < high) { int pivot = low + (high - low) / 2; if (nums[pivot] < nums[high]) { high = pivot; } else if (nums[pivot] > nums[high]) { low = pivot + 1; } else { high -= 1; } } return nums[low]; } };
寻找重复数
时间复杂度:O(nlogn),其中 n 为 nums 数组的长度。二分查找最多需要二分 O(logn) 次,每次判断的时候需O(n) 遍历 nums 数组求解小于等于 mid 的数的个数,因此总时间复杂度为 O(nlogn)。
空间复杂度:O(1)。我们只需要常数空间存放若干变量。
class Solution { public: int findDuplicate(vector<int>& nums) { int n = nums.size(); int l = 1, r = n - 1, ans = -1; while (l <= r) { int mid = (l + r) >> 1; int cnt = 0; for (int i = 0; i < n; ++i) { cnt += nums[i] <= mid; } if (cnt <= mid) { l = mid + 1; } else { r = mid - 1; ans = mid; } } return ans; } };