关于二分法,大致思路虽然不难。但下列问题一直没有真正搞懂,做题也经常是通过不断试错解出来的
① while(left < right) OR while(left <= right) ??
② left = mid + 1 OR left = mid ??
③ right = mid - 1 OR right = mid ??
④ return mid OR return left OR return right ??
框架
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
//int mid = (right + left) / 2; //left+right可能会造成溢出
int mid = left + (right - left) >> 1;//避免上述情况
//讨论三种情况
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
查找某数
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
//查找范围[left,right],存在左右边界重合的情况,left=right的情况不能落下
while(left <= right) {
int mid = left + (right - left) >> 1;
//找到直接返回
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
//由于查找范围是左右闭区间,因此改变区间时right,left均加1
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
局限性:当存在数组 nums = [0,2,2,2,3],target = 2,此算法返回索引 2,没错。但是如果想得到 target 的左侧边界索引 1,或者右侧边界索引 3,算法是无法处理的。虽然可以找到一个 target,然后向左或向右线性搜索,但是不好,因为这样就浪费掉了二分查找O(log(n))的时间复杂度。
改进:有多个相同值时寻找左侧边界
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length;
//查找范围[left,right),不存在左右边界重合的情况,left和right无法重合
while (left < right) {
int mid = left + (right - left) >> 1;
if (nums[mid] == target) {
//找到符合条件的mid后,由于要找左侧边界,因此将范围缩小至左侧继续搜索
//[left,right)被分为[left,mid)和[mid+1,right),由于是左闭右开区间,因此若让right = mid - 1,则mid - 1 会被遗漏
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
//不会返回-1,返回值属于闭区间[0,nums.length]
}
存在找不到的情况,可在末尾添加
return nums[left] == target ? left : -1;
改进:有多个相同值时寻找右侧边界
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length;
//查找范围[left,right),不存在左右边界重合的情况,left和right无法重合
while (left < right) {
int mid = left + (right - left) >> 1;
if (nums[mid] == target) {
//找到符合条件的mid后,由于要找右侧边界,因此将范围缩小至右侧继续搜索
//[left,right)被分为[left,mid+1)和[mid+1,right),由于是左闭右开区间,因此若让left = mid,则mid + 1 会被遗漏
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1;
//跳出while循环后left = right 因此等价于right - 1
//不会返回-1,返回值属于闭区间[0,nums.length]
//由于执行过left = mid + 1 因此最终nums[left]一定不等于target了
}
存在找不到的情况,可在末尾添加
return nums[left-1] == target ? (left-1) : -1;
找左侧
public int findleft(int[] nums,int target)
{
if(nums.length == 0)
return -1;
int l = 0,r = nums.length - 1;
while(l < r)
{
int mid = l + (r - l)/2;
if(nums[mid] >= target)
{
r = mid;
}
else
l = mid + 1;
}
return nums[r] == target?r:-1;
}
找右侧
public int findright(int[] nums,int target)
{
if(nums.length == 0)
return -1;
int l = 0,r = nums.length - 1;
while(l < r)
{
//重点 防止死循环
int mid = l + (r - l + 1)/2;
if(nums[mid] <= target)
{
l = mid;
}
else
r = mid - 1 ;
}
return nums[r]==target?r:-1;
}