学习任务
- 二分查找
- 移除元素
二分查找
题目是 leetCode 704 题 leetcode.cn/problems/bi…
这道题使用二分查找的方式解题,有一个前提是为什么会想到用二分查找来解题?或者说题目满足什么条件,可以想一想是不是可以用二分法来解决。
- 有序数组
- 数组中无重复的元素
二分法一般有两种写法:左闭右闭 [left right] 和左闭右开 [left right)
这里有两点需要注意:
- 在
while循环中left到底是<还是<=right - 在
if判断中left或者right更新时,如何依据middle取值
所以在左闭右闭 [left right] 和左闭右开 [left right) 这两种二分写法中,对于以上两点思路完全不同,注意区分。
左闭右闭 [left right]
对于左闭右闭这种二分法,先思考left 到底是 < 还是 <= right,由于left == right是有意义的,所以使用<=
再来思考第二个问题,就是更新left 或者 right时,如何取值,这里使用如下图一个例子作为演示:
定义数组如下,target = 2
这里我们知道
nums[middle] > target所以我们要更新搜索范围的左下标,关键需要注意 right 是应该取middle还是取middle - 1呢?
由于我们这里是左闭右闭 [left right],left可以等于 right,在nums[middle] > target我们已经比较过nums[middle]和 target的大小了,再赋值right时,就没有必要再比较一遍了,所以right = middle - 1
同理判断 nums[middle] < target 和 nums[middle] = target 的情况即可,具体代码实现如下。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
let mid, left = 0, right = nums.length - 1;
// 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
while (left <= right) {
// 位运算 + 防止大数溢出(右移一位,相当于 / 2,向下取整)
mid = left + ((right - left) >> 1);
// 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
if (nums[mid] > target) {
right = mid - 1; // 去左面闭区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右面闭区间寻找
} else {
return mid;
}
}
return -1;
};
左闭右开 [left right)
左闭右开这种写法,由于右边是开区间,取不到right的值,所以在while循环中 left < right,如果 left = right就会出现如[1 1)即取到又取不到的情况,就不合法了。
关于第二个问题,还是如下这个例子:
由于right本身是开区间,也就是取不到right,所以这里的right = middle,这也是和左闭右闭 [left right]的区别。
同理判断 nums[middle] < target 和 nums[middle] = target 的情况即可,具体代码实现如下。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
let mid, left = 0, right = nums.length;
// 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
while (left < right) {
// 位运算 + 防止大数溢出
mid = left + ((right - left) >> 1);
// 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
// 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
if (nums[mid] > target) {
right = mid; // 去左区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右区间寻找
} else {
return mid;
}
}
return -1;
};
移除元素
学习 leetCode 27 题: leetcode.cn/problems/re…
这个题目如果使用两层for循环一层for循环遍历数组元素 ,第二个for循环更新数组,这种暴力解法的时间复杂度是O(n^2)。
可以使用双指针法来降低复杂度,通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
关键是要理解快慢指针的定义:
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
//时间复杂度:O(n)
//空间复杂度:O(1)
var removeElement = (nums, val) => {
let k = 0;
for(let i = 0;i < nums.length;i++){
if(nums[i] != val){
nums[k++] = nums[i]
}
}
return k;
};
新数组更新之后的长度就是k