704.二分查找
何为二分法?
二分法,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。
复杂度如何?
二分法的时间复杂度是O (logn)。
所以在算法中,比O (n)更优的时间复杂度几乎只能是O (logn)的二分法。 根据时间复杂度来倒推算法也是面试中的常用策略:题目中若要求算法的时间复杂度是O (logn),那么这个算法基本上就是二分法。
算法时间复杂的的一个排行如下所示
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n^2)平方阶 < O(n^3)立方阶 < O(2^n)指数阶
算法思路?
二分法查找的思路如下:
- 首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。
- 如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤 1 的操作。
- 如果某一步数组为空,则表示找不到目标元素。
通用格式
注意
- 是从“有序”数组里面寻找
- 左右区间一般都是“左闭右闭”或者“左闭右开”。
- 但是二分查找真正的坑根本就不是那个细节问题,而是在于到底要给
mid
加一还是减一,while 里到底用<=
还是<
。(参考:二分查找算法细节详解,顺便写了首诗)
采取“左闭右闭”的写法
采取“左闭右闭”的写法
var search = function(nums, target) {
// 左闭右闭
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = (left + right) >> 1; // 关注点
if (nums[mid] == target) {
return mid
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1
};
采取“左闭右开”的写法
var search = function(nums, target) {
// 左闭右开
var left = 0;
var right = nums.length;
while(left < right) {
var mid = (left + right) >> 1;
if(nums[mid] == target) {
return mid
} else if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
};
总结 & 注意点
JavaScript 中 的变量 是松散类型 的,可以保存任何类型数据,变量只不过是一个名称。
所以当出现left=0,right=5时,直接使用(left + right) / 2
得到的就是2.5 。
那么当值为undefined时,下列的if或者else if中条件都会不符合,这样left和right的值都不会发生变化,由此,就会一直在while中死循环。
27. 移除元素
想要移除数组中的元素,看似简单,实则很多细节需要我们去注意!
数组地址
像 C/C++ 这种传统的编译型的语言,它们的数组在内存中用一串连续的区域来存放一些值,而且它们的数组中存放的数据类型都需要预先设定成同一类型。
而对于JS来说的话,数组中存储的数据类型是可以完全不一致的,这就意味着,JS 数组中内存地址不是连续的。不过,现在的 JS 引擎为了优化 JS 的性能,它会分配一个连续的内存空间给存储了相同数据类型的数组,以达到更好的遍历效果。所以,只要你数组里存的是相同类型的值,在内存中的地址还是连续的。
js数组相关知识点
JavaScript删除数组中某一元素的方法:首先获取指定元素在数组中的位置(即索引index);然后使用splice()函数根据索引值来删除数组中的元素,语法格式splice(index, 1)
。
推荐阅读:
暴力解法
var removeElement = function (nums, val) {
let len = nums.length;
for(let i = 0; i < len; i++) {
if(nums[i] == val) {
nums.splice(i,1)
// i--; // 注意这里
}
}
return nums
};
let nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2;
console.log(removeElement(nums, val));
错误想法
一开始想的的是使用for循环遍历数组中的每一个元素,用splice方法,把数组中和val的值给删除掉。但是遇到的问题是,let nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2;
两个连续的2,当使用splice方法删除第一个2(元素下标为2)时,原数组中元素下标都会前移一位。第二个2的下标就会由3变成了2,但是此时继续进行下一轮遍历了,i=3,就会忽略元素中的第二个2 。
后续的解决方法就是,nums.splice(i,1);i--;
不过这样的写法内存消耗大。
当然,还有一种双重for循环的解法,大家可以看看Carl老师关于此点的讲解
双指针解法
双指针算法的时间复杂度是O(n)
/**
* @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
};
fast指针用来遍历每一个元素,去确定这个元素能否留下来。如果这个元素不是需要删除的元素,那么就用慢指针去更新一下数组的内容。
想要了解的更透彻,可以看看Carl老师的视频讲解
day01 over!明天继续加油,先把后续需要学的内容放在这里~