数组
1. 数组理论
- 数组是连续的,相同数据类型的集合
- 数组下标从0开始
- 内存地址连续——>删除/添加元素,要移动其他元素地址
- 二维数组当然也是连续的
2. 二分查找理论
2.1 使用条件
思路:数组且有序,且无重复元素,使用二分查找
2.2 2种写法
- 左闭右闭 [left,right]
- 左闭右开 [left,right)
一般,不用左开右闭
注意:在循环中根据区间定义来做边界处理
2.2.1 左闭右闭
-
while (left <= right)
要使用 <= -
if (nums[middle] > target)
中right 要赋值为 middle - 1;if (nums[middle]<target)
中left要赋值为 middle + 1 -
初试值
left=0
,right=nums.length-1
;注意要区别于左闭右开区间的初始值:left=0;right=nums .length
2.2.2 左闭右开
-
while (left < right)
,这里使用 <;因left==right在区间[left,right)内无意义 -
if (nums[middle] > target)
中 right 更新为 middle;if (nums[middle]<target)
中 left 依旧是为 middle + 1因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,
即:下一个查询区间不会去比较nums[middle]
-
初试值
left=0
;right=nums.length
举个例子😃
一个长度为5的数字,最后一个数下标为4,length=5,若要数组下标在左闭右开区间[left,right)中,则right必须为5,才能使得区间在04中,此刻区间为[0,5)包含数组下标0~4
3. 704 二分查找 E
3.1 思路
数组且有序,且无重复元素,因此使用二分法
3.2 左闭右闭写法
class Solution {
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;
while (left <= right) {
int middle=left+((right-left)>>1);//右位移运算,提高效率 >>1 相当于除以2
if (nums[middle]==target) {
return middle;
} else if (nums[middle]>target) {
right=right-1;
}else if (nums[middle]<target){
left=left+1;
}
}
return -1;
}
}
注意: 位运算>> 是除以2操作不会带小数 ,向下取整
3.3 左闭右开写法
class Solution {
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;//左闭右开 right为数组长度
while (left < right) {
int middle = left + ((right - left) >> 1);//右位移运算,提高效率
if (nums[middle] == target) {
return middle;
} else if (nums[middle] > target) {
right = middle;//左闭右开区间,right值为middle;跟左闭右闭有区别
} else if (nums[middle] < target) {
left = left + 1;
}
}
return -1;
}
}
4. 27 移出元素 E
4.1 思路
数组的元素在内存中是连续存放的,不能单独删除数组中的某个元素,只能覆盖
4.2 暴力解法
思路:两层for循环:一层循环遍历数组,一层循环更新数组
class Solution {
public int removeElement(int[] nums, int val) {
int length=nums.length;
//循环遍历数组
for (int i = 0; i < length; i++) {
if (nums[i]==val) {//发现要移出元素,将后面的数组集体向前移动一位进行覆盖
for (int j = i+1; j < length; j++) {//j=i+1 要移出元素的后面所有数组开始
nums[j-1]=nums[j];//覆盖
}
i--;//一定要i--!!!,因为下标i后的数值都向前移了一位了,所以i必须也向前移动一位
length--;//覆盖后数组长度少1
}
}
return length;
}
}
注意:
一定要 进行 i-- 操作
举例:数组{0,1,2,2,3,0,4,2},要移出2。
当i为2时,找到了符合要移出的数值,将后面的数往前移动之后是{0,1,2,3,0,4,2},如不对i进行–操作,则此处i还是为2,当进入下一个for循环当中要先对i++使得i为3,就少了下标为2的数组元素2没有移出
4.3 双指针法
思路:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
- 快指针:寻找 新数组(不含目标元素) 中的元素
- 慢指针:指向更新 新数组 下标的位置
class Solution {
public int removeElement(int[] nums, int val) {
int slow=0;
//fast快指针遍历数组
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast]!=val) {
nums[slow]=nums[fast];//指向更新新数组 小标
slow++;//此后加一
}
}
return slow;
}
}
- 时间复杂度:O(N)
- 空间复杂度:O(1)
4.4 相向双指针法
这个方法是从代码随想录看到的,第一次看到有点新颖。
相向,顾名思义就是从两头开始向中间靠拢,一头一尾定义2个左右指针,因为此题只是要求移出元素后返回新数组的长度,并没有对其中数组内部元素的序列有要求,所以正是因为这一点,本题采用此方法,让右指针先移动到从右数第一个值不为val的位置,之后开始while循环,移动左指针,当左指针指向val时,让右指针指向的数组元素覆盖其val,同时右指针如果等于val继续向左移动。
总结下来 左右指针移出操作各不同:
- left:指向val时,用right指向的元素覆盖val
- right:指向val时,做 right-- 操作,忽略值为val的元素
这样讲的话应该就通透了吧 !
class Solution {
public int removeElement(int[] nums, int val) {
int left=0;
int right=nums.length-1;
//先对右指针进行操作,移动到右边第一个不为val的位置
//right移出操作
while (right >= 0 && nums[right] == val) {
right--;
}
while (left <= right) {//left移出操作
//左指针指向元素若为val
if (nums[left]==val) {
//right位置元素覆盖left
nums[left]=nums[right];
//right位置移出
right--;
}
//同时左指针继续++
left++;
//右指针指针指向元素若为val,则--
while (right >= 0 && nums[right] == val) right--;
}
return left;
}
}
注意:
- left位置移出操作:将right位置元素覆盖给left后,要记得 right–,将right位置移出,向左移动
📓以上为 5.24 所学,加油!踏踏实实走好每一步,你走过的路每一步都算数~!