【第一章 数组part01】
704. 二分查找 27. 移除元素 977.有序数组的平方
💻[数组理论基础]
前置知识:数组理论基础
TIPS
- 正是因为数组在内存空间的地址连续,因此增\删一个元素,就要移动后面所有元素的内存地址.
- 对于数组,增删不方便,而改\查元素是方便的.
- 在C++中二维数组是连续分布的.像Java是没有指针的 ,寻址操作完全交给虚拟机,因此java数组暴露出来的地址是经过处理的.
💻704. 二分查找
目标:熟练根据 左闭右开,左闭右闭 两种区间规则 写出二分法。
题目链接
文章讲解
视频讲解
一、题目和思考:
一个升序的数组nums中给定一个整数目标target,在nums中搜索目标,存在返回下标,不存在返回-1.
Tips:
使用二分的前提:有序数据
and无重复值
.
如果计算的结果是小数,默认向下取整,(5 - 0) / 2 = 2.5,取2.
二、思路与题解:
思路
方法一:如果我定义区间是[left,right]
如果我定义搜索的边界是 [left, right],也就是说,包括 left 和 right 这两个边界,那么我应该选择 while(left <= right)。
定义左\右
当左<=右
进入循环
定义中间
如果[mid]<target值
left=mid+1
如果[mid]>target值
right=mid-1
否就是[mid]=target值
返回mid
否则左>右
也就是遍历后未找到目标,-1
方法二:如果我定义区间是[left,right)
如果我定义搜索的边界是 [left, right),也就是说,不包括 right 这个边界,那么我应该选择 while(left < right)。
定义左\右
当左<右
进入循环
定义中间
如果[mid]<target值
left=mid+1
否则target<mid值
right=mid-1
否则
如果[mid]=target值//[]mid]也就是当前的nums[left]
return mid
否则
遍历后未找到目标,-1
题解
方法一:如果我定义区间是[left,right]
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;//注意这里是-1
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else{
return mid;
}
}
return -1;
}
}
方法二:如果我定义区间是[left,right)
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;//注意这里是-1
while(left<right){
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else{
return mid;
}
}
if(nums[left]==target){
return left;
}
return -1;
}
}
三、难点的解决:
while(left < right)
和 while(left <= right)
的区别?
如果我定义搜索的边界是 [left, right]
while(left <= right)
有等于号【推荐这样写】
left=right也被算进去
while(left <= right)
允许在left == right
时进行最后一次检查,这对于大多数情况是直接返回结果的。while(left<right){
如果我定义搜索的边界是 [left, right)
while(left < right)
没有等于号 【不推荐!】
left=right会被忽略,要在while同级再进行判断
while(left < right)
则不允许在left == right
时进行搜索,但可以通过在循环外做一次额外的检查来确保不会错过目标。while(left<right){ } if (nums[left] == target) { return left; }
mid
的计算不正确?
解决:
1.避免直接计算 left + right,以免被赋值的mid超过整数最大值,导致溢出,从而mid计算错误
2.注意比较的是 nums[mid] 和 target
3.mid的初始化应当在while循环内进行,确保每次循环mid都会刷新,以此确保进行了有效缩小范围,解决超时报错
只使用 left = mid
和 right = mid
,即不使用 +1
或 -1
来更新边界是否可行?
标准二分法是
left = mid+1
和right = mid-1
,这样写的目的是 :
使得每一步都保持在 left 和right 边界的位置上,确保每一次while都缩小一半范围,直到搜索到目标值.
如果不使用
+1
或-1
,只使用left = mid
和right = mid
更新边界,
目标还是使得每一步都保持在left 和 right 边界的位置上.while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; // 找到目标,返回下标 } else if (nums[mid] < target) { left = mid; // 目标在右侧 } else { right = mid; // 目标在左侧 } }
关键在于:每次更新边界时,不是简单地将 left 或 right 向中间靠拢,而是让 left 和 right 移动到 mid位置,这样可以确保每次搜索仍然有效。
还是使用"靠近"的方式更新边界吧,这种"代替" 的方式思考比较复杂.
为什么int mid=left+(right-left)/2
就对了,用int mid=right-(right-left)/2
就错了?
计算 mid 的两种方式:
int mid = left + (right - left) / 2; //方法一计算mid,运行通过
int mid=right-(right-left)/2;//方法二计算mid,运行报错
解决:
问:是因为Java除法向下取整的原因吗? 答:不是.
- Java 的整数除法向下取整并不会直接导致越界。
- 出现越界错误的根本原因是边界计算不准确,尤其是在循环的最后阶段。 看看什么是Java向下取整:
Java 整数除法向下取整:当两个整数相除时,如果结果有小数部分,Java 会向下取整(即向负无穷方向取整),比如:5 / 2 = 2 // 向下取整 -5 / 2 = -3 // 向下取整
但是,回到我的代码中的
mid = right - (right - left) / 2;
,这行代码本身并不会导致越界,因为: 我遇到的越界问题 通常是因为在循环过程中,left
和right
的值已经非常接近,或者正好处于数组边界,而在进行下一次计算时没有正确判断边界情况。错误的原因: 它和
mid = left + (right - left) / 2
结果是一样的。出错的根本原因是,在循环判断left < right
时,可能会出现计算的mid
值恰好位于数组的边界上,尤其是数组元素的数量非常小或者left
和right
差距非常小的时候。此时,mid
可能会访问到数组的非法索引,从而抛出ArrayIndexOutOfBoundsException
。为避免这种问题,还是得使用
left + (right - left) / 2
,因为它更加简洁且不会引发其他潜在的异常。
四、拓展练习
35.搜索插入位置
34.在排序数组中查找元素的第一个和最后一个位置
69.x 的平方根
367.有效的完全平方数
💻27. 移除元素
目标:暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握,至于拓展题目可以先不看。
题目链接
文章讲解
视频讲解
一、题目和思考:
这里是题目的简述
TIPS
- 思考
二、思路与题解:
思路
方法一:如果…
这里是方法一的思路
方法二:如果…
这里是方法二的思路
题解
三、难点的解决:
难点一XXX:
描述难点一
解决:
难点二XXX:
描述难点二
解决:
难点三XXX:
描述难点三
解决:
四、拓展练习
💻977.有序数组的平方
目标:本题关键在于理解双指针思想
题目链接
文章讲解
视频讲解
一、题目和思考:
这里是题目的简述
TIPS
- 思考
二、思路与题解:
思路
方法一:如果…
这里是方法一的思路
方法二:如果…
这里是方法二的思路
题解
三、难点的解决:
难点一XXX:
描述难点一
解决:
难点二XXX:
描述难点二
解决:
难点三XXX:
描述难点三
解决: