704-二分法
二分法是一种针对有序序列效率很高的查找方法,但是在写的时候要注意边界条件,要保持在每次循环时区间的定义都一致,即要么一直是“左闭右闭”型区间,要么一直是“左闭右开”型区间。
我自己比较熟悉的是左闭右闭型区间:
class Solution {
public int search(int[] nums, int target) {
// 左右指针均是指向实实在在的元素,即“左闭右闭”型区间
int left = 0, right = nums.length-1;
while(left <= right){
int middle = (right -left) / 2 + left;
if(nums[middle] > target){
right = middle-1;
}else if(nums[middle] < target){
left = middle + 1;
}else{
return middle;
}
}
return -1;
}
}
但是卡哥要求要同时熟练掌握“左闭右闭”和“左闭右开”型区间的写法,没办法,硬着头皮写吧,让自己的大脑习惯学习新东西!
class Solution {
public int search(int[] nums, int target) {
// 这种情况下,右指针指向的是一个不包括的元素,即“左闭右开”型区间
int left = 0, right = nums.length;
while(left < right){
int middle = (right - left) / 2 + left;
int temp = nums[middle];
if(target > temp){
left = middle + 1;
}else if(target < temp){
right = middle;
}else{
return middle;
}
}
return -1;
}
}
27-移除元素
本题是一道双指针法的一个入门题目,但是卡哥又觉得说先暴力解决,有助于提升代码能力,我怎么能放弃这个提升自己的机会呢?哈哈,开干吧!暴力实现之后才发现没有那么简单。
class Solution {
public int removeElement(int[] nums, int val) {
int n = 0;
for(int i=0; i< nums.length-n;){
if(nums[i] == val){
for(int j=i;j<nums.length-n-1;j++){
nums[j] = nums[j+1];
}
n++;
}else{
i++;
}
}
return nums.length-n;
}
}
此题中n尤为关键,它用于记录数组中和val值相等的元素个数,这个还用于设置边界条件,一旦发现一个和val值的元素,本质上数组的长度会减少1,因此第一层循环和第二层循环的边界不再是无脑的nums.length了,(别问为什么,问就是一开始我无脑设置边界为nums.length,最后会成为一个死循环
还有一个就是i不是一直都默认增长的,当发现和val值相等的元素时,会从i开始移动数组元素,i位置的元素会发生变化,因此也要进行判断,只有不和val值相等时才会默认增加i
暴力法解决之后,进入正题,使用“双指针法”进行移除数组元素
先上代码:
class Solution {
public int removeElement(int[] nums, int val) {
int i=0,j=0;
for(;j<nums.length;){
if(nums[j] != val){
nums[i] = nums[j];
i++;
j++;
}else{
j++;
}
}
return i;
}
}
我的理解是:设置两个指针back, front,一开始指向数组第一个元素,然后一起遍历,如果遇到和val值相同的元素,这是back停下来指向它,front向前走; 否则用front指向的元素覆盖back所指向的元素,此时back与front同时向前移动。 直到front到达数组末尾,结束循环。此时数组的个数就是back的值。
扩展题目:
35-搜索插入位置
同样是二分法的应用,只不过这次不保证数组中一定能找到指定元素,如果找不到,则返回其应该插入的位置。同样使用二分法查找,只是再找不到的时候返回left的值即可。
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length-1;
while(left <= right){
int middle = (right - left) / 2 + left;
int temp = nums[middle];
if(target < temp){
right = middle-1;
}else if(target > temp){
left = middle + 1;
}
else {
return middle;
}
}
return left;
}
}
为什么返回left呢?换种思维想一下:比如再 1 3 4 5 中找2,显然找不到,但是我们知道2应该插在1和3之间,必定最后有一个情况是left=1, right=3, 这时取中midlle显然是left,而2比nums[middle]大,因此left会被加一,然后结束,因此left的值刚好是应该2插入的位置
其实这个时候在我看来和计算机的特性相关了,因为取整是直接去掉浮点数部分,再加上二分法的一个特性,所以left就是插入位置。有时候不是所有东西都能想通的,接受它,接纳它,使用它即可!
34-在排序数组中查找元素的第一个位置和最后一个位置
这也是二分法的一个变种题,难度又有了一点提升。
现在的情形是:排序数组中有可能有重复出现的数,需要找出出现的第一个位置和最后一个位置,第一次看到这个题目我其实是很懵逼的,但是后面仔细一想,使用二分法找到第一个元素后,继续更新边界值,重新寻找,直到找到第一个位置和最后一个位置。
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0, right = nums.length-1;
int[] res = new int[2];
// 默认不存在
res[0] = -1;
res[1] = -1;
// 找最左边出现的位置
while(left <= right){
int middle = (right -left) / 2 + left;
int temp = nums[middle];
if(target < temp){
right = middle-1;
}else if(target > temp){
left = middle + 1;
}else{
// 这个条件判断很重要,如果不满足,则证明左边已经没有符合条件的元素了
if(middle-1 >= left && nums[middle-1] == temp){
right = middle-1;
continue;
}else{
res[0] = middle;
break;
}
}
}
// 找最后边出现的位置
left = 0;
right = nums.length-1;
while(left <= right){
int middle = (right -left) / 2 + left;
int temp = nums[middle];
if(target < temp){
right = middle-1;
}else if(target > temp){
left = middle + 1;
}else{
left= middle+1;
// 同理,如果不满足该条件,则证明后边已经没有符合条件的元素了
if(right >= left && nums[left] == temp){
continue;
}else{
res[1] = middle;
break;
}
}
}
return res;
}
}
总结
二分法很基础,保持住所谓的“循环不变量”很关键, 双指针法还得多练练,这个方法的应用场景太多了。
PS: 开工第一周,没适应过来,所以开始补进度hh。