代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素
1.了解数组基础
数组是存放在连续的内存空间,相同类型数据的集合
数据类型:相同的数据类型
存储方式:存放在连续的内存空间中
因为是存放在连续的内存空间中,数据之间是紧挨着的,所以删除元素和插入元素需要移动指定位置之后的所有数据。(存储方式决定了元素的操作)
数组元素的删除是通过元素覆盖
获取元素:通过数组下标索引
数组下标从0开始
2.LeetCode 704.二分查找
题目链接:https://leetcode.cn/problems/binary-search/
2.1自己看到题目的第一想法
思路1:暴力解决,用for循环遍历与目标值相同的元素,返回下标
思路2:二分查找
二分查找适用:主要用于一个有序的数组(元素之间),查找目标值。
二分查找思想:对数据进行折半查找,用左右指针(两个变量)对中间的下标进行控制(也成为一个变量)。中间值等于目标值,就返回中间值下标,反之继续更新中间值,直到左指针大于右指针证明数组元素已全部遍历过,还没有等于目标值,证明数组中没有这个元素,返回-1。
代码:
精简版:
class Solution {
public int search(int[] nums, int target) {
int mid;
int left=0;
int right=nums.length-1;
mid=(left+right)/2;
while(left<=right){
if(nums[mid]>target)
right=mid-1;
}else if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]==target){
return mid;
}
mid=(left+right)/2;
}
return -1;
}
}
解析版:
class Solution {
public int search(int[] nums, int target) {
//目标值变量target
//问题分解
//1、对半砍,数组中最中间的值是哪一位元素(是一个会变得量,那我们就先定义出来)
int mid;
//2、找指定元素
//3.2需要左右元素,是一个变化得值,定义出来
int left=0;
int right=nums.length-1;
//for(int i=0;i<nums.length;i++){
//3.需要左右元素
//}
mid=(left+right)/2;//中间值得元素下标
//那我们希望,当nums[mid]!=target的时候(大于或者小于),要变化mid的值,也就要变化left或者right的值
//要排除right>left的风险,这时就说明所有元素已经遍历完了,依旧没找到,返回-1
while(left<=right){//当等于的时候,也是可以算mid的,必须要考虑这种情况
if(nums[mid]>target){
//中间的大,说明目标值在左边,且一定不会是中间值,那么right就是mid-1
right=mid-1;
//重新更新mid的值
//mid=(left+right)/2;
}else if(nums[mid]<target){
//中间的小,说明目标值在右边,且一定不会是中间值,那么left就是mid+1
left=mid+1;
//重新更新mid的值
//mid=(left+right)/2;
}else if(nums[mid]==target){
return mid;
}
mid=(left+right)/2;
}
//没有不相等,那就是相等
//return mid;
//right<left
return -1;
}
}
2.2自己实现过程中遇到哪些困难
遇到不确定while中条件的等于还是不等于,也就是边界处理问题
2.2看完代码随想录之后的想法
避免越界:mid=left+(right-left)>>1;
(right-left)>>1 相当于(left+right)/2
根据左右指针的区间来确定while里面的条件和mid的值
区间一般分为左闭右闭,左闭右开
2.2.1左右指针的区间为左闭右闭时
int left=0;
int right=nums.length-1;
int mid=(left+right)/2;
while(left<=right){
//因为左闭右闭,比如[1,1],用反证法
//左边的1能去到,右边的1也能取到,1=1,那么left=right就是合理的
//反之,左闭右开,比如[1,1)
//左边的1能取到,右边的1不能取到(也就是不为1),那么left=right就是不合理的
if(nums[mid]>target){
right=mid-1;//不是right=mid;//因为左闭右闭,明知中间的值是绝对取不到的了
}else if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]==target){
return mid;
}
mid=(left+right)/2;
}
return -1;
2.2.2左右指针区间为左闭右开时
int left=0;
int right=nums.length;//注意此时不为nums.length-1,因为写这个是能够取到这个值的,我们要开区间,不能取到值
int mid=(left+right)/2;
while(left<right){
//不能相等
//左闭右开,比如[1,1)
//左边的1能取到,右边的1不能取到(也就是不为1),那么left=right就是不合理的
if(nums[mid>target){
right=mid;
}else if(nums[mid]<target){
left=mid+1;//注意左边能取到,一定不会等于mid值,那就是mid+1
}else if(nums[mid]==target){
return mid;
}
mid=(left+right)/2;
}
return -1;
3.LeetCode 27.移除元素
题目链接:https://leetcode.cn/problems/remove-element/
3.1自己看到题目的第一想法
思路:暴力解决,第一层for循环查找和目标值相同的元素值,也就是我们用来遍历需要被删除的元素。
如果数组中有元素值等于目标值,进入第二层遍历我们进行对需要删除的元素进行删除操作(数组的删除使用元素覆盖)。
覆盖成功后,我们依旧在此时被删的元素下标处,继续检查一遍,看是否继续等于目标值。
同时,将数组长度-1.
最终返回数组长度
代码:
class Solution {
public int removeElement(int[] nums, int val) {
//我们考虑用暴力解决
//2、每次一遍历,删除掉一个元素(也就是往前覆盖而已),需要重新更新一下数组的长度,是一个变量,要存起来
//1、遍历一个跟跟目标值相同的元素,我们就移除掉(也就是后面元素向前挪一位,然后继续遍历下去,在删除的这个位置继续),一个移除掉再进行下一个,直到遍历完新的
int len=nums.length;
int i=0;
while(i<len){
if(nums[i]==val){
//将i位置后面的所有元素都向前挪一个位置
for(int j=i;j<len-1;j++){//到倒数第二位
nums[j]=nums[j+1];//删除操作
}
//为什么这个行,这个就不行,因为得是j+1而不是j++
//重新去学一下j++和j+1
// for(int j=i;j<len-1;j++){//到倒数第二位
// nums[j]=nums[j++];//删除操作
// }
len--;
}else{//不相等时,就继续遍历下一个
i++;
}
}
return len;
}
}
3.2自己实现过程中遇到哪些困难
为什么用下面这个会报错
for(int j=i;j<len-1;j++){//到倒数第二位
nums[j]=nums[j++];//删除操作
}
解决1:
for(int j=i;j<len-1;j++){//到倒数第二位
nums[j]=nums[j+1];//删除操作
}
解决2:
for(int j=i+1;j<len;j++){//到最后一位
nums[j-1]=nums[j];
}
3.3看完代码随想录之后的想法
思路:双指针思想
一个快指针相当于暴力法的最外层循环,做第一层循环比较每一个元素
一个满指针作为接收nums[fast]≠val的元素,相当于暴力解决的第二层的删除
代码:
class Solution {
public int removeElement(int[] nums, int val) {
//双指针,一个作为快指针,用来遍历需要被保留的元素(不被删除)
//慢指针,用来将不被删除的元素进行存储的指针(相当于一个虚拟的新数组,给每一个下标从0开始进行赋值)
int fast=0;
int slow=0;
for(fast=0;fast<nums.length;fast++){
if(nums[fast]!=val){
//将不需要被删除的元素,存储进去数组
nums[slow]=nums[fast];//存进新数组下标为0的位置开始
//fast指针在最外层会动了,就不管他
//此时新数组第一个下标处已有数据,将此时的指针往下进行,也就是
slow++;
}
}
//当数组中所有的元素都被遍历完了,就出现一个新的数组了,并且新数组的长度为slow
//这里返回的为什么不是slow++,因为再进行新增加元素后,已经对slow指针往下调了
return slow;//只需要返回数组长度,不需要返回数组
}
}
4.今日收获,记录一下自己的成长
今日收获了
1.巩固了数组基础知识,明白了对于数据的访问以及其他操作(增删)是需要根据此数据在内存中的存储方式,按照存储方式对数据进行操作。
2.解决二分查找边界问题,用不同区间来分别解决while条件和mid的值。
3.接触越界处理,避免越界:mid=left+(right-left)>>1;
(right-left)>>1 相当于(left+right)/2
4.对j++,++j,j+1有进一步地了解
5.接触学习了双指针思想和运用。