这里将对刷题笔记一文末提及的几道推荐二分法进阶题目进行说明介绍。一道简单题加了一定的文字修饰,一道中等题巧用二分查找,以下为刷题笔记一链接,题目链接在文末提供。
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0
由题目提示中目标数组为升序有序无重复元素的数组且题目要求为寻找目标值以及对于时间复杂度的要求,可以清楚地认识到本题使用二分法解决问题。
解题步骤:
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0 ;
int right = nums.length-1;
int middle = left + (right-left)/2;
if(nums[left]>target){
return 0;
}
if(nums[right]<target){
return right + 1;
}
while(left<=right){
if(nums[middle]<target){
left=middle+1;
middle = left + (right-left)/2;
}
else if(nums[middle]>target){
right=middle-1;
middle = left + (right-left)/2;
}
else{
return middle;
}
}
return right+1;
}
}
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0 ;
int right = nums.length;
int middle = left + (right-left)/2;
if(nums[left]>target){
return 0;
}
if(nums[right]<target){
return right;
}
while(left < right){
if(nums[middle]<target){
left=middle+1;
middle = left + (right-left)/2;
}
else if(nums[middle]>target){
right=middle-1;
middle = left + (right-left)/2;
}
else{
return middle;
}
}
return right;
}
}
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
题目要求的是取得目标值 target 出现的闭区间(包含且等于 target ),即左边界为第一次小于 target 值的下标加一,右边界为第一次大于目标值 target 的下标减一,如若为 {5,7,7,8,8,10}, target = 8 ,返回 {3,4}。
左右边界的获取方法可以说是一致,但需要判断方法有所不同:
根据题目要求寻找target在数组里的左右边界后,可分为如下几种情况:
target 在数组范围之外,即在数组范围的右边或者左边。
例如数组 {3, 4, 5},target 为 2 或者数组 {3, 4, 5},target 为 6,此时应该返回 {-1, -1}
target 在数组范围中,分两种情况:tareget存在数组之中,不存在数组之中。如下:
数组中不存在 target。
例如数组 {3,6,7} ,target 为 5,此时应该返回 {-1, -1}
数组中存在 target。左边界加一,右边界减一,得到区间。
例如数组 {3,6,7} ,target 为 6,此时应该返回 {1, 1}
了解完基本思路,我们就要去实现代码来寻找左右边界,且如果对于二分法足够清晰,可以采用同时查找左右边界的方式,如果,还只是初学者,建议先从一个边界值下手,再来处理另一个边界值。
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 数组中不存在 target。
if (leftBorder == -2 || rightBorder == -2) {
return new int[]{-1, -1};
}
// 数组中存在 target
if (rightBorder - leftBorder > 1) {
return new int[]{leftBorder + 1, rightBorder - 1};
}
// target 在数组范围之外
return new int[]{-1, -1};
}
int getRightBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
}
else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
}
else {
left = middle + 1;
}
}
return leftBorder;
}
}
注意:
左右边界值移动时,切忌数组下标越界,即下标小于零或大于数组长度-1,必须先判断下标不越界,才能对比值,顺序不可调换。对于条件的选择顺序应为:
left - 1 >= 0 && nums[left - 1] == nums[index]
right + 1 < nums.length && nums[right + 1] == nums[index]
class Solution {
public int[] searchRange(int[] nums, int target) {
int index = binarySearch(nums, target); // 二分查找
if (index == -1) { // nums 中不存在 target,直接返回 {-1, -1}
return new int[] {-1, -1}; // 匿名数组
}
// nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
int left = index;
int right = index;
// 向左滑动,找左边界
while (left - 1 >= 0 && nums[left - 1] == nums[index]) { // 防止数组越界。逻辑短路,两个条件顺序不能换
left--;
}
// 向左滑动,找右边界
while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // 防止数组越界。
right++;
}
return new int[] {left, right};
}
public int binarySearch(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 (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1; // 不变量:左闭右闭区间
}
}
return -1; // 不存在
}
}