二分查找
leetcode 704
题目描述
理解及注意事项
二分法原理上相对来说简单一些,就是通过中间值与目标值之间不断的比较进而缩小查找范围(left , right)。
但是对于left 和 right 又有两种方案,即全闭[left , right] , 左闭右开 [left , right]。
代码上体现主要在于while循环条件的变化以及缩小范围时right值的初始化和更新时的变化。
方案一:全闭范围
class Solution {
public int search(int[] nums, int target) {
//左闭右闭 [a,b]
int left = 0 , right = nums.length - 1 ,mid , number; //同下 right 同样需要考量,因为right代表的是索引
while (left <= right){// 以区间范围为考量,[a,b] 对应 [1,2] , [2,2] 是否满足范围标准
//进而确定while循环条件是否是<=
mid = left + ((right - left) >>1);
number = nums[mid];
if(number == target){
return mid;
}else if (number > target){
right = mid - 1; //因为本身包含右侧,所以需要进一步-1
}else{
left = mid + 1; //因为本身包含左侧,所以需要进一步+1
}
}
return -1;
}
}
方案二:左闭右开
class Solution {
public int search(int[] nums, int target) {
//左闭右开 [a,b)
int right = nums.length , left = 0 , mid , number ; //因为是左闭右开,[a,b) ,
// 所以核对区间范围 考虑是索引 所以是[0,length]
while (left < right) { //同样核对区间 [a,b) [1,2) [1,1)是否满足 ,确定条件中是否是<=
mid = left + ((right - left) >>1);
number = nums[mid];
if (number == target){
return mid ;
}else if (number > target){
right = mid; // 因为本身就不包含右侧,所以不需要进行-1
}else {
left = mid + 1; //因为本身包含左侧,所以需要进一步+1
}
}
return -1;
}
}
leetcode 35
题目描述
理解及注意事项
这个题本质也是二分法,只是最后返回的是存在target的索引或插入位置。
难点主要在插入位置的确定上,按照解题思路里的话来说,如果nums中不存在target,那么退出循环,left和right肯定是交叉的[right , left],那么插入位置应该在left上。
这里就不写左闭右开的情况了,只列出左闭右闭的情况
解题代码
class Solution {
public int searchInsert(int[] nums, int target) {
//左闭右闭 [a,b]
int left = 0 , right = nums.length -1 , mid = 0 ,number ;
while( left <= right ){
mid = left + ((right - left) >> 1);
number = nums[mid];
if (number == target){
return mid;
} else if(number > target){
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}
leetcode 34
题目描述
理解及注意事项
U1S1,这个非递减数组,很离谱,其实就是升序数组的意思,真的很无语。
我一开始的思路是二分法,在找到target之后,无限判断是否有与target相同的数,进而扩大start和end的范围。但是后来总是报OutOfRange的错误,所以就改了一下判断依据,非常巧妙地(加粗夸赞自己的傻瓜式思想!),因为&&如果前面的是false那就不判断后面的了,所以我在前面判断是否数组索引越界,然后后面放索引对应值与target的判定,这样就避免了OOR。
稍微看了一下例子的思路,其实也相对来说简单,因为是升序顺序数组,上一个思路只用到了顺序数组的条件,并没有用到升序,佬们的思路也很简单,找到target的第一个索引和比target大的下一个数的第一个索引-1,构成了[start,end]
我的傻瓜式思想
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0 , right = nums.length -1 , mid , number ;
int[] result = new int[2];
while (left <= right){
mid = left + ((right - left)>>1);
number = nums[mid];
if (number == target){
int end = mid , start = mid;
while(true){
if(start>=1 && nums[start-1] == target){
start--;
}else {
break;
}
}
while(true){
if(end<nums.length - 1 && nums[end+1] == target){
end++;
}else {
break;
}
}
result[0] = start;
result[1] = end;
return result;
}else if( number > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
result[0] = -1;
result[1] = -1;
return result;
}
}
佬们的牛逼思想
这个地方直接复制粘贴的,我跟着他的思路写了一个但是报错,到时候回来看一下。
这算是正常二分法的变种,就算nums[mid] == target ,也要继续收缩,进而获得border。需要注意的是 Right找LeftBorder , left找RightBorder!
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
// 情况二
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;
}
}