二分查找思路不再赘述, 注意二分查找适用于有序, 无重复元素的数组, 要是有重复元素, 算法查找到的未知不唯一. 主要难点在于边界处理:
left<=right
还是 left?
right=middle
还是right=middle + 1
?要用数学中区间的观点去处理边界, 常用的是左闭右闭 [left, right]
, 左闭右开 [left, right)
.
整个算法要保证这个不变量不变, 也就是到底用左闭右闭还是左闭右开
二分查找就是通过和中间位置的元素比较大小, 重新划定区间, 再和中间位置元素比较大小, 循环往复直到找到这个位置, 如果区间不合法了, 也就是不存在了.
关于区间的问题:
所以对于查找来说, 这个区间一定要是合法的, 也就是进入while循环的条件一定要是合法的区间 ( while内代码的功能就是查找)
while (left<=right)
while (left
关于middle取值的问题
用 if (num[middle] > target)举例子, 需要重新赋值right, 已经判断了middle, 下一次循环不需要带上middle, 要从left到middle-1就可以. 比如数组 [0 1 3 4 5 7 9 12], middle=3 对应元素4,
right=middle
, 下一次循环while (left<=right)
中就会[left, middle]
,right=middle-1
就可以实现[left, middle-1]
若 right=middle-1
, 下一次循环while (left
[left, middle-1),
那middle-1这个位置的元素就是始终没有判断, 所以不可以.
若right=middle
就可以实现[left, middle)
在看题解的过程中我发现还需要对target先进性一个判断,要是比最小的小或者比最大的大 直接就返回-1了。if (target < nums[0] || target > nums[nums.length - 1]) { return -1; }
左闭右闭的代码如下:
public int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0;
int right = nums.length - 1;
int middle = 0;
while (left <= right) {
middle = (left + right)/2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
左闭右开的代码如下:
public int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0;
int right = nums.length;
int middle = 0;
while (left < right) {
middle = (left + right)/2;
if (nums[middle] > target) {
right = middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
数组中删除元素不是直接删除, 要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖.
暴力解法代码:
public int removeElement(int[] nums, int val) {
int size = nums.length;
for (int i=0; i<size; i++) {
if (nums[i]==val) {
size--;
for (int j=i; j<size; j++){
nums[j] = nums[j+1]; //元素前移实现覆盖
}
i--; //这一行很关键, 重新检查当前位置覆盖的新元素
}
}
return size;
}
其中 i--
; 我在第一次写的时候落下了, 就出现了错误. 比如[0, 1, 2, 2, 3, 2]
数组, 删除元素2, 得到的应该是[0, 1, 3]
. 但我得到的却是[0, 1, 2, 3]
原因就是没有 i--
, 当检测到第一个2时 i=2, 后面的元素前移, 第二个2就到了第一个2的位置上变成了 [0, 1, 2, 3, 2],
此时再下一次循环中i=3, 会去看元素3, 错过了前移的第二个2.
所以再覆盖元素之后需要i--
, 再次检测当前位置覆盖之后的元素
双指针解法
public int removeElement(int[] nums, int val) {
int fast=0;
int slow=0;
for (fast=0; fast<nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
快慢指针是一个将二维循环降到一维很常用的方法
比如[0, 1, 2, 2, 3, 2]
数组, 对于0 和1元素, 它们重新赋值了自己
对于第一个2, 慢指针递增的if语句没有进去, 慢指针停下了, 但是快指针继续+1 . slow=3, fast=4
对于第二个2,同理, slow还是3, fast来到了5
遇到了3, slow的位置, 也就是第三个元素赋值为第五个元素5. slow=4, fast=6
遇到最后一个2, 同理slow不动, fast=7 for循环结束, 最后返回数组长度, 也就是slow