代码随想录算法训练营27期DAY1:升序无重复数组二分查找+快慢指针删除数组元素

本文介绍了在Java中实现二分查找算法以及如何原地移除数组中特定元素的问题,包括两种方法:暴力搜索与二分查找,以及针对移除元素问题的快慢指针解决方案。

代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素(java)

题目一、二分查找

题目链接
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4

解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1

解释: 2 不存在 nums 中因此返回 -1

提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

小傻瓜做法:for循环暴力搜

class Solution {
    public int search(int[] nums, int target) {
        
       for(int i=0;i<nums.length;i++){
           if(nums[i]==target){
               return i;//找到就返回下标
           }
       }
        return -1;//能到这就肯定没找到,找不到就返回-1
    }
}

到这里就能超过5%的人咯!加油鹿小葵!!!

进阶做法:二分查找。
题目着重强调了有序无重复数组,这时候要考虑可以用二分的方式查找target(如果数组元素重复就不能保证返回的下标是唯一的)
《代码随想录》算法视频公开课
手把手带你撕出正确的二分法

二分法的两种写法(左闭右闭、左闭右开)

二分法在这里插入图片描述
二分法相当于利用while循环搭配left和right两个指针来划定寻找target的区间然后将要查的值target和一个取left和right中值的mid来比大小,所以区间就成了很重要的边界条件,否则会丢数或者超出范围。

1.左闭右闭也就是定义 target 是在一个在左闭右闭的区间里,也就是[left, right]。那么while循环的判断条件就是left<=right,因为当left=right的时候,还剩最后一个数没比过,如果写left<right就会丢一个数(个人理解)。
那么left和right两个指针怎么移动呢?判断条件又是什么?
1.1当mid指向的数组元素比target小时,说明mid左侧的所有数都比target小了(升序无重复数组),这时候把left指针移到mid的右侧也就是说left=mid+1,在右侧的区间接着找数
1.2当mid指向的数组元素比target大时,说明mid右侧的所有数都比target大了,这时候把right移到mid的左侧也就是right=mid-1,在左侧接着找数
1.3如果mid指向的数组元素和target相同,说明该数找到了,返回mid(目标值的下标)
如果出了while循环还没有返回说明数根本没被找到,返回-1
(注意要把求mid的代码写while循环里)

class Solution {
    public int search(int[] nums, int target) {
     int left=0;//左指针
     int right=nums.length-1;//右指针,数组从0开始所以这-1
      while(left<=right){//循环判断条件
          int mid = left+(right-left)/2;//其实就是(right+left)/2,这里是避免溢出
          if(nums[mid]<target){//找到的数比目标值小
             left = mid+1;//去右边的区间接着找
          }
          else if(nums[mid]>target){//大
              right = mid-1;//左边找
          }
          else{//相等
              return mid;//找到了,返回下标
          }
      }
        return -1;//没找到
    }
}

两个指针,时间复杂度:O(log n),空间复杂度:O(1)
2.左闭右开,同理,只不过right每次都多往右指了一个格格,开始的时候right指向nums数组最右边元素的右边,right=nums.length
while循环的判断条件是left<right
当mid指向的数组值大于target时,right指向mid,因为循环条件是left小于right
而mid指向的数组值小于target时,left指向mid+1。

class Solution {
    public int search(int[] nums, int target) {
     int left=0;
     int right=nums.length;
      while(left<right){
          int mid = left+(right-left)/2;
          if(nums[mid]<target){
             left = mid+1;
          }
          else if(nums[mid]>target){
              right = mid;
          }
          else{
              return mid;
          }
      }
        return -1;
    }
}

27.移除元素

啪的一下上难度了,很快奥,偷袭我六十五岁的老同志。

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}
示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]

解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]

解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

一个大胆的想法

我要用一个for循环解决这个事。
后来发现不行,得两个。得暴力,得用闪电五连鞭。

就是说,一个for循环用来遍历数组,另一个for循环用于找到要删的数之后把后面所有的数都依次向前挪一个。
简单的想法,但是一写就会写错。偷袭可恶。

class Solution {
    public int removeElement(int[] nums, int val) {
        int len = nums.length;//1
        for(int i=0;i<len;i++){//2
            if(nums[i]==val){
                for(int j=i+1;j<len;j++){
                    nums[j-1] = nums[j];//3
                }
                len--;//4
                i--;//5
            }    
        }
        return len;
    }
}

解释4点。
第一,为什么要整一个len呢。数组元素不能被删除只能被覆盖,len是记录长度的,只要发现有一个target就把len的数值减一,方便最后返回(第4点)。
第二,为什么i和j的右边界都是len呢。因为后面的数逐个向前移动的时候,就像蛇蜕皮一样把没用的数留在后面了,后面的数被写到前面的时候,后面的没用了,所以边界值是不断变化的len而不是固定的数组长度,如果这两个地方都写数组长度length的话程序运行会超时。
第五,为什么i要往前退一个呢。因为i当下就指向值和target相同的数组元素的位置,j利用循环把后面的数都搬来了,搬到i的脚下。如果说不对i做任何处理,也不看挪过来新的数的时候是不是需要删除的数,下一个循环时i向右移了,那就少判断了很多很多数。所以i必须先左移。变成负值也没事下一个循环加一之后又变0了很符合循环条件。

时间复杂度On^2,很慢,非常慢,但是能过

第二种思路:反者道之动

如果说我不是删除不要的,而是把要的拿出来呢?

快慢指针

一个快指针fast去寻找不等于target的数,一个slow指针记录找到的数应该塞到什么地方。fast找到之后,数给slow脑袋上的小格子,然后slow去下一个坑位蹲。

class Solution {
    public int removeElement(int[] nums, int val) {
        int slow=0;//慢指针等着梁志超他奶奶上菜
        for(int fast =0;fast<nums.length;fast++){//快指针找数
            if(nums[fast]!=val){
                nums[slow++] = nums[fast];//这里加加就是先存再往右挪的意思
            }
        }
        return slow;

    }
}

好了现在已经超过40%的人了!睡觉!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值