文章目录
二分也是一种双指针,记录左右区间,每次将目标值与左右区间的中位数进行判断,用于在有序数组中搜索某个值。
69. x 的平方根 – 简单
计算给定值的算术平方根。结果只保留整数部分
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
/**
二分:
注意:这里 l=1,解决x=0的问题
返回值是r,因为最终循环结束是 l = r+1, 但是r的右边是 平分和大于x的情况,l的左边是平方和小于x的情况
**/
class Solution {
public int mySqrt(int x) {
int l = 1, r = x;
while (l <= r) {
int mid = l + (r-l)/2;
if (mid == x/mid) return mid;
else if (mid > x / mid) r = mid - 1;
else l = mid + 1;
}
return r;
}
}
// 牛顿迭代法 a = ( a + x / a ) / 2 --- 使用于 f(x) = 0 这种函数
class Solution {
public int mySqrt(int x) {
long a = x;
while (a*a > x) {
a = (a + x/a)/2;
}
return (int)a;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置 – 中等
找到目标值的起始位置和结束位置。不存在返回 [-1,-1]。
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
/**
思路:双指针+二分查找
找到一个target 同时更新 左右边界,并且 查找 [l,target-1] [target + 1] 两个边界
**/
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = {-1,-1};
findLocation(nums, target, 0, nums.length - 1, res);
return res;
}
private void findLocation(int[] nums, int target, int l, int r, int[] res) {
while (l <= r) {
int mid = l + (r-l)/2;
if (nums[mid] > target) r = mid - 1;
else if (nums[mid] < target) l = mid + 1;
else {
res[0] = res[0] == -1?mid:Math.min(res[0], mid);
res[1] = Math.max(res[1], mid);
findLocation(nums, target, l, mid - 1, res);
findLocation(nums, target, mid + 1, r, res);
break;
}
}
}
}
81. 搜索旋转排序数组 II – 中等
一个非递减的数组,经过旋转了一次,判断一个数是否在这旋转数组中。
输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
// [0 0 1 2 2 5 6] 旋转一次-> [2,5,6,0,0,1,2]
/**
思路:二分查找
判断 [l,mid] 和 [mid,r] 区间的递增递减情况,然后分类讨论。
nums[mid] > nums[l] 那么 [l,mid] 必然是递增区间 [mid,r] 不确定
nums[mid] < nums[r] 那么 [mid.r] 必然是递减区间 [l,mid] 不确定
nums[mid] == nums[l] 或者 nums[mid] == nums[r] 不能确定任何区间情况 所以可以 ++l / --r
**/
class Solution {
public boolean search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while(l <= r) {
int mid = (r+l)>>>1;
if (nums[mid] == target || nums[l] == target || nums[r] == target) return true;
// l - mid 是单调递增区间
if (nums[mid] > nums[l]) {
if(nums[mid] > target && nums[l] < target) r = mid - 1;
else l = mid + 1;
// mid - r 是单调递增区间
} else if (nums[mid] < nums[r]) {
if(nums[mid] < target && target < nums[r]) l = mid + 1;
else r = mid - 1;
} else ++l;
}
return false;
}
}
154. 寻找旋转排序数组中的最小值 II – 困难
求旋转n次排序数组的最小值。
旋转规则: 每次旋转把最后一个元素旋转到第一个位置 [0,1,4,4,5,6,7] -> [7,0,1,4,4,5,6]
输入:nums = [2,2,2,0,1]
输出:0
/*
二分: nums[mid] < nums[r] 说明最小值在 [l,mid] 之间
nums[mid] > nums[r] 说明最小值在 [mid+1, r] 之间
nums[mid] == nums[r] 说明最小值在 [l, r-1]之间
结束条件 l < r 即:当 l==r 结束循环,此时就是结果
*/
class Solution {
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r)>>>1;
if (nums[mid] > nums[r]) {
l = mid + 1;
} else if (nums[mid] < nums[r]) {
r = mid;
} else --r;
}
return nums[l];
}
}
540. 有序数组中的单一元素 – 中等
在不递减的数组中查找只出现一次的元素 (其他元素均出现两次),要求,时间复杂度O(logn)、空间复杂度O(1)
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
/**
思路一:二进制元素 时间复杂度O(n)
**/
class Solution {
public int singleNonDuplicate(int[] nums) {
int res = 0;
for (int i = 0; i < nums.length; ++i) res ^= nums[i];
return res;
}
}
/**
思路二:二分
如果 nums[mid] == nums[mid-1],
[l,mid]之前元素是偶数个,说明结果在 [mid+1, r]之间
[l,mid]之前元素是奇数个,说明结果在 [l, mid - 2]之间
如果 nums[mid] == nums[mid+1]
对右区间做相同考虑
如果上面都不成立,说明nums[mid]只出现一次
**/
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (r+l)>>>1;
if (nums[mid] == nums[mid - 1]) {
if ((mid - l + 1) % 2 == 0) l = mid + 1;
else r = mid - 2;
} else if (nums[mid] == nums[mid + 1]) {
if ((r - mid + 1) % 2 == 0) r = mid - 1;
else l = mid + 2;
} else {
return nums[mid];
}
}
return nums[l];
}
}
4. 寻找两个正序数组的中位数 – 困难
在两个长为m+n不递减的数组中,找到中位数。要求时间复杂度为 0(log(m+n))。
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
/**
如果不考虑时间复杂度:
思路:双指针,指向两个数组起点位置,每次移动较小的数 (类似归并两个数组)
二分:对较小的数组进行二分,根据较小的数组的长度 计算另一个数组元素的位置
**/
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m > n) return findMedianSortedArrays(nums2, nums1);
int rMid = (m+n+1)>>>1;
int low = 0, high = m;
while (low < high) {
// 这里取中位数+1,防止low=mid在两个元素时进入死循环
int mid = low + (high - low + 1)/2; // nums1数组右侧第一个元素
int rLoc = rMid - mid; // nums2数组右侧第一个元素
if (nums1[mid-1] > nums2[rLoc]) high = mid - 1;
else low = mid;
}
// 定义四个子数组相邻的边界元素
int rNeed = rMid - low;
int s1LMax = low==0?Integer.MIN_VALUE:nums1[low-1];
int s1RMin = low==m?Integer.MAX_VALUE:nums1[low];
int s2LMax = rNeed==0?Integer.MIN_VALUE:nums2[rNeed-1];
int s2RMin = rNeed==n?Integer.MAX_VALUE:nums2[rNeed];
if ((m+n)%2 == 0) return (Math.max(s1LMax, s2LMax) + Math.min(s1RMin, s2RMin))/2.0;
else return Math.max(s1LMax, s2LMax);
}
}