二分法
手撕二分
二分就是有序区间内找值,先一分为2,比较中间值和目标值,判断下一个二分的区间,再二分,直到中间值等于目标值
704. 二分查找:在长度为n升序的整型数组nums找target,找到返回下标,没找到返回-1
数组有序,且无重复元素,(因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件)
二分两种区间:
[l , r]左闭右闭[l , r)左闭右开
首先我们先弄清,这个区间的含义是:目标值target可能在的区间
- 左闭右闭(确定好后,全程就得保持区间不变)
先设左右初始值l = 0, r = n - 1满足[l, r],进入while循环,l = r满足区间=>循环条件while(l <= r)会有以下三种情况:
nums[mid] > target时,mid显然不在含目标值的[l, r]内=>r = mid - 1nums[mid] < target时,同理l = mid + 1nums[mid] == target即找到目标值,直接返回
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {//[]左闭右闭
let l = 0, mid = 0, r = nums.length - 1; //定义初始值满足[]区间
while (l <= r) { //l = r在区间内,循环二分
mid = l + ((r - l) >> 1) //位运算防止大数溢出=(l + r)/2
if (nums[mid] > target) { //target在左边
r = mid - 1
} else if (nums[mid] < target) { //target在右边
l = mid + 1
} else { //nums[mid] == target 直接返回
return mid
}
}
return -1 //循环结束说明没有找到,返回-1
};
- 左闭右开
先设左右初始值l = 0, r = n满足[l, r),进入while循环,为满足区间=>循环条件while(l < r)
同理三种情况:
nums[mid] > target时,区间本来就不包括r也不包括mid,所以r = midnums[mid] < target时,同理l = mid + 1nums[mid] == target即找到目标值,直接返回
最重要是:区间的定义不变-->循环的不变量,写不写=,下一个值是什么,都要满足区间定义, 根据区间来
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) { //[)
let l = 0, mid = 0, r = nums.length
while (l < r) { //[l, r)区间
mid = l + ((r - l) >> 1) //取中点比较中间值和目标值,更新区间
if (nums[mid] > target) { //target在左边
r = mid //区间不包括mid,也不包括r,所以r = mid
} else if (nums[mid] < target) { //在右边
l = mid + 1 //mid不在[)
} else {
return mid
}
}
return -1
};
1. 确定区间:target可能在的范围[l,r] / [l, r)
2. 满足区间条件while循环
3. 比较中间值与目标值,更新区间直到找到
27.移除数组
27. 移除元素
不用辅助数组,原地移除(删除)数组nums中的等于val的元素,被移出的部分不用管
三种方法
(重点)快慢双指针:把一个数组当两个用(顺序不变)
- 快指针:遍历旧数组,找要装进新数组的元素
- 慢指针:新数组下标
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) { //快(老)慢(新)双指针
let slow = 0 //慢指针相当于新的数组下标
for (let fast = 0; fast < nums.length; fast++) { //快指针遍历旧数组
if (nums[fast] !== val) { //不是要移除元素时加入新数组
nums[slow++] = nums[fast]
}
}
return slow //返回数组个数
};
前后双指针:把不要的数移到最后(顺序变)
- 前指针:从前面开始找找不要的数
- 后指针:从后面找要的数,交换
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) { //前(找要删)后(找不删)双指针
let l = 0,r = nums.length - 1; //区间为左闭右闭[]
while(l <= r){ //继续循环,进行下一次查找的条件
// 下面加 l <= r 是为了防止找的时候 指针穿过
while(l <= r && nums[l] !== val) l++; //找左边=val要删的值
while(l <= r && nums[r] === val) r--; //找右边!=val不删的值
// 一定要l < r时才覆盖,最后一次退出循环时,l > r不能交换
if(l < r) nums[l++] = nums[r--];//用不删的值覆盖要删的
//保持指针指向下一个要判断的值
}
return l; //l找被移除的,最后正好是指向第一个不满足的,也就是size大小
};
let l = 0, r = nums.length - 1
while (l <= r) {
while (l <= r && nums[l] !== val) l++
while (l <= r && nums[r] === val) r--
if(l < r) nums[l++] = nums[r--]
}
return l
注意暴力解法的代码细节(顺序不变)
i先找到要删的数- 把后面的都往前移一位
- 注意:因为
i后所有数都前移了一位,i也得往前移一个,才能下一次for循环i++后指向下一个要判断的值(不然直接++会跳过刚移到i位置上的数) - 在移动的过程中数组长度变化,循环条件应该为变化的
size
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) { //暴力
let size = nums.length //nums.length为数组实际大小不会改变,size等会要返回的删除之后的大小
for (let i = 0; i < size; i++) {
if (nums[i] === val) { //找到要删除的值
for (let j = i + 1; j < size; j++) {
nums[j-1] = nums[j]
}
size--; //数组大小减一
i--; //i后面的值都向前移动了一位,如果i不前移,接下来执行i++会跳过一个数没判断,所以一定要i--
}
}
return size
};