704. 二分查找
题目链接:https://leetcode.cn/problems/binary-search/
解题思路
二分查找的区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在 while 寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
1. 左闭右闭即[left, right]
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function (nums, target) {
// 定义target在左闭右闭的区间里,即:[left, right]
let left = 0; // 左指针
let right = nums.length - 1; //右指针
while (left <= right) {
// 当left==right,区间[left, right]依然有效,所以用 <=
let middle = (left + right) >> 1; // 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else {
// nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
};
2. 左闭右开即[left, right)
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function (nums, target) {
// 定义target在左闭右开的区间里,即:[left, right)
let left = 0; // 左指针
let right = nums.length; // 右指针
while (left < right) {
// 当left==right,在区间[left, right)是无效的空间,所以用 <
let middle = (left + right) >> 1; // 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle; // target 在左区间,所以[left, middle)
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right)
} else {
// nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
};
27. 移除元素
题目链接:https://leetcode-cn.com/problems/remove-element/
解题思路
要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。这里数组的删除并不是真的删除,只是将删除的元素移动到数组后面的空间内,然后返回数组实际剩余的元素个数,OJ 最终判断题目的时候会读取数组剩余个数的元素进行输出。
1. 暴力解法
两层 for 循环,一个 for 循环遍历数组元素,找到要删除的元素之后,第二个 for 循环更新数组。
代码
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
let len = nums.length;
for (let i = 0; i < len; i++) {
// 找到了要删除的元素
if (nums[i] === val) {
// // 发现需要移除的元素,就将数组集体向前移动一位
for (let j = i + 1; j < len; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
len--; // 此时数组的大小-1
}
}
return len;
};
2. 快慢指针
通过一个快指针和慢指针在一个 for 循环下完成两个 for 循环的工作。
a. 创建一个双指针,fastIndex
指向当前将要处理的元素,slowIndex
指向下一个将要赋值的位置。
b. 若nums[fastIndex]
和val
不等,它一定是输出数组的一个元素,我们就将nums[slowIndex]
改为nums[fastIndex]
,然后将slowIndex
、fastIndex
指针同时右移。
c. 最后返回slowIndex
即可;
代码
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
if (!nums.length) return 0;
let slowIndex = 0;
for (let fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] !== val) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
};
3. 相向的双指针法
利用双指针,将右边不等于 val 的元素覆盖左边等于 val 的元素。
代码
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
if (!nums.length) return 0;
let left = 0,
right = nums.length - 1;
while (left <= right) {
// 比较val和左指针指向的元素,找左边等于val的元素
while (left <= right && nums[left] !== val) {
left++;
}
// 比较val和右指针指向的元素,找右边不等于val的元素
while (left <= right && nums[right] === val) {
right--;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (left < right) {
nums[left++] = nums[right--];
}
}
// left一定指向了最终数组末尾的下一个元素
return left;
};
4. 相向的双指针法
a. 利用双指针,将 nums
中等于 val
的元素移动到数组末尾;
b. 当 left=right
时, 如果 nums[left]
等于 val
, 即删除 nums[left]
, 返回 left
的值即可;如果 nums[left]
不等于 val
, 即不删除 nums[left]
, 返回 left+1
。
代码
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
if (!nums.length) return 0;
let left = 0,
right = nums.length - 1;
while (left < right) {
// 比较val和左指针指向的元素
while (left < right && nums[left] !== val) {
left++;
}
// 比较val和右指针指向的元素
while (left < right && nums[right] === val) {
right--;
}
// 跳出内部循环时, nums[left] === val、nums[right] !== val
// 交换nums[left]和nums[right],此时数组尾部全是值为val的元素
[nums[left], nums[right]] = [nums[right], nums[left]];
}
// 判断left===right时,nums[left]和val的关系
return nums[left] === val ? left : left + 1;
};