算法-二分查找
1. 搜索插入的位置(LeetCode 35)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
输入: nums = [1,3,5,6], target = 5
输出: 2
输入: nums = [1,3,5,6], target = 7
输出: 4
注意:
- 使用
left + (right-left) / 2,而不是(left+right) / 2能够避免出现相加溢出的情况
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
// 左闭右闭
while(left <= right) {
int mid = left + (right - left)/2;
if(nums[mid] == target)
return mid;
else if(target < nums[mid])
right = mid-1;
else
left = mid+1;
}
return right+1;
}
}
2. 在排序数组中查找元素的第一个和最后一个位置(LeetCode 34,易错)
给你一个非递减的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
要求:时间复杂度为O(log n)
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length == 0)
return new int[]{-1, -1};
int start = lowerBound(nums, target);
if(start == nums.length || nums[start] != target)
return new int[]{-1, -1};
// 最大的小于 target+1 的下标,又存在 nums[i] == target
// 所以end必定是最大的等于target的下标
int end = lowerBound(nums, target+1) - 1;
return new int[]{start, end};
}
// 返回最小的满足 nums[i] >= target 的下标i
public int lowerBound(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
while(left <= right) {
int mid = left + (right - left)/2;
// 注意这里是:大于等于 target
if(nums[mid] >= target) {
right = mid-1;
} else {
left = mid+1;
}
}
// 最终结束时 left = right+1
// 即 nums[left] < target, nums[left] = nums[right+1] >= target
// 如果nums都小于target,则left = nums.length
return left;
}
}
3. 搜索旋转排序数组(LeetCode 33,有难度)
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。
例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2] 。
给你旋转后的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
class Solution {
public int search(int[] nums, int target) {
if(nums.length==1)
return (nums[0] == target) ? 0 : -1;
int left = 0;
int right = nums.length-1;
while(left <= right) {
int mid = left + (right - left)/2;
if(nums[mid] == target)
return mid;
// 只在有序的半区上进行二分查找
// 一定是大于等于,否则处理不了左边只有一个元素的情况
if(nums[mid] >= nums[left]) {
// target 落在左半区内
if(target >= nums[left] && target < nums[mid])
right = mid-1;
else
left = mid+1;
}
// nums[mid] < nums[left],左半区不是单调的。考察target和右半区的关系
else {
if(target > nums[mid] && target <= nums[right])
left = mid+1;
else
right = mid-1;
}
}
return -1;
}
}
4. 寻找旋转排序数组中的最小值(LeetCode 153,有难度)
给你一个元素值互不相同的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。
请你找出并返回数组中的 最小元素 。
输入:nums = [4,5,6,7,0,1,2]
输出:0
class Solution {
public int findMin(int[] nums) {
if(nums.length==1) return nums[0];
int left = 0;
int right = nums.length-1;
while(left < right) {
int mid = left + (right - left)/2;
// 以最右边元素作为参照
if(nums[mid] < nums[right]) {
right = mid;
} else {
left = mid+1;
}
}
return nums[left];
}
}
5. 寻找两个正序数组的中位数(LeetCode 4, 难!!!)
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
要求:时间复杂度为O(log (m+n))
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
- 数组长度k为奇数,中位数为
nums[k/2 + 1];k为偶数,中位数为(nums[k/2] + nums[k/2+1]) / 2. - 因此,找中位数可以转化为找第k个元素
- getKthElement的思路见注释,每次都能够删除
nums[index] ~ nums[newIndex],即newIndex - index + 1个元素,大概是k/2-1个,从而对k实现二分。 - 边界情况是一个数组为空,则只需考虑另一个数组即可;如果
k=1,只需要考虑两个数组中的较小值即可。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int length1 = nums1.length;
int length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}

浙公网安备 33010602011771号