LeetCode 面试经典 150 题-二分查找
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public int searchInsert(int[] nums, int target) { int n = nums.length; int left = 0, right = n - 1; while (left <= right) { int mid = (left + right) >> 1; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return left; } }
|
z字形查找
从右上角开始搜索,如果当前位置元素小于target,那么i+1,否则j-1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length; int n = matrix[0].length; int i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } else if (matrix[i][j] < target) { i++; } else { j--; } } return false; } }
|
- 时间复杂度:O(m + n)
- 空间复杂度:O(1)
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length; int n = matrix[0].length; int left = 0, right = m * n - 1; while (left <= right) { int mid = (left + right) >> 1; int x = matrix[mid / n][mid % n]; if (x == target) { return true; } else if (x < target) { left = mid + 1; } else { right = mid - 1; } } return false; } }
|
- 时间复杂度:O(logmn)
- 空间复杂度:O(1)
二分查找
通过每次将搜索区间减半,优化查找过程。我们不需要检查每个元素,只需检查中间元素与其相邻元素的关系,从而缩小范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public int findPeakElement(int[] nums) { int left = 0, right = nums.length - 1; while (left < right) { int mid = (left + right) >> 1; if (nums[mid] < nums[mid + 1]) { left = mid + 1; } else { right = mid; } }
return left; } }
|
二分查找
判断哪一部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界:
- 如果 [left, mid - 1] 是有序数组,即nums[left] <= nums[mid]:如果target >= nums[left] && target < nums[mid],则在左半部分,否则在右半部分;
- 否则如果nums[left] > nums[mid],则右半部分有序:如果target > nums[mid] && target <= nums[right],则在右半部分,否则在左半部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public int search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = (left + right) >> 1; if (nums[mid] == target) { return mid; } if (nums[mid] >= nums[left]) { if (target >= nums[left] && target < nums[mid]) { right = mid - 1; } else { left = mid + 1; } } else { if (target > nums[mid] && target <= nums[right]) { left = mid + 1; } else { right = mid - 1; } } }
return -1; } }
|
二分查找
- 判断 nums[mid] 是否等于 target,如果是,直接返回 true。
- 如果左边有序(即 nums[left] <= nums[mid]),那么我们可以通过比较 target 和 nums[left]、nums[mid] 来判断 target 是否在这个有序区间内,从而决定向左还是向右查找。
- 如果右边有序(即 nums[mid] <= nums[right]),则可以类似地判断 target 是否在这个有序区间内,并决定查找方向。
- 如果左右两边有重复元素(即 `nums[left] == nums[mid] || nums[mid] == nums[right]),我们只能通过将左右边界向内缩小来减少搜索范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class Solution { public boolean search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return true; } if (nums[left] < nums[mid]) { if (nums[left] <= target && target < nums[mid]) { right = mid - 1; } else { left = mid + 1; } } else if (nums[mid] < nums[right]) { if (nums[mid] < target && target <= nums[right]) { left = mid + 1; } else { right = mid - 1; } } else { if (nums[left] == nums[mid]) { left++; } if (nums[right] == nums[mid]) { right--; } } } return false; } }
|
- 时间复杂度:O(n),最坏情况下,由于重复元素的存在。
- 空间复杂度:O(1)
二分查找
要找到数组中第一个等于目标元素的位置和第一个大于目标元素的位置-1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public int[] searchRange(int[] nums, int target) { int leftIdx = binarySearch(nums, target, true); int rightIdx = binarySearch(nums, target, false) - 1; if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) { return new int[]{leftIdx, rightIdx}; } return new int[]{-1, -1}; }
public int binarySearch(int[] nums, int target, boolean lower) { int n = nums.length; int left = 0, right = n - 1, ans = n; while (left <= right) { int mid = (left + right) >> 1; if (nums[mid] > target || (lower && nums[mid] >= target)) { right = mid - 1; ans = mid; } else { left = mid + 1; } } return ans; } }
|
二分查找
如果nums[mid] > nums[right],则最小值一定在mid右侧;否则一定是mid或mid的左侧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public int findMin(int[] nums) { int left = 0, right = nums.length - 1; while (left < right) { int mid = (left + right) >> 1; if (nums[mid] < nums[right]) { right = mid; } else { left = mid + 1; } } return nums[left]; } }
|
二分查找
- 确保 nums1 是较小的数组:我们总是对较短的数组进行二分查找,减少搜索空间。
- 二分查找:在较短数组中查找一个合适的分割点,使得两数组的左半部分和右半部分满足合并后的中位数条件。
- 计算中位数:
- 如果数组的总长度为奇数,中位数是左半部分的最大值。
- 如果数组的总长度为偶数,中位数是左半部分最大值与右半部分最小值的平均值。


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int m = nums1.length; int n = nums2.length; if (m > n) { return findMedianSortedArrays(nums2, nums1); } int k = (m + n + 1) / 2; int left = 0, right = m; while (left <= right) { int i = (left + right) >> 1; int j = k - i; if (j != 0 && i != m && nums2[j-1] > nums1[i]) { left = i + 1; } else if (i != 0 && j != n && nums1[i-1] > nums2[j]) { right = i - 1; } else { int maxLeft = 0; if (i == 0) { maxLeft = nums2[j - 1]; } else if (j == 0) { maxLeft = nums1[i - 1]; } else { maxLeft = Math.max(nums1[i-1], nums2[j-1]); }
if ((m + n) % 2 == 1) { return maxLeft; }
int minRight = 0; if (i == m) { minRight = nums2[j]; } else if (j == n) { minRight = nums1[i]; } else { minRight = Math.min(nums1[i], nums2[j]); } return (maxLeft + minRight) / 2.0;
} } return 0.0; } }
|
- 时间复杂度:O(log(min(m, n)))
- 空间复杂度:O(1)
总结
二分查找的题目总体来说不难,但是对于边界的判断容易混淆,以后要多加复习。