Day1:二分查找+移除元素+有序数组的平方+长度最小的子数组(双指针)

二分查找:(注意是应用在有序数组里面!)

  1. 思路:设置三个下标(left mid right),在查找的过程中通过将目标元素比较mid下标对应的数字进行比较,大了就缩小right(right=mid-1),小了就放大left(left=mid+1),等于就是答案。
  2. 亿些细节:while(中到底是left<=right 还是left<right?)

         这里就不得不涉及到数组的基础知识了,由于数组下标都是从0开始,一直到总长度-1,那我们在设置left和right的初始下标就要小心了!

            ①L=0 R=总长度-1:那么我们每一次比较都是有意义的,都是属于数组内部的,那么我们采用left<=right(推荐,比较容易理解)

            ②L=0 R=总长度:那么我们最后一次的比较没比较去比较=这种情况,因为一开始R指向的并不是数组内部的元素。left<right

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

移除元素:

  1. 思路:可以用两层暴力,但是推荐通过双指针优化成为一层for循环
  2. 理解数组是连续的内存空间,不能单独删除数组中的某个元素,只能覆盖
  3. 一点细节:明确每个指针的意义和用途很重要!!如何确保覆盖这个动作!

    快指针:用于指向后面去覆盖的元素

    慢指针:用于指向待覆盖的元素

那么,如何让两个指针运作起来去 确保遇到了待覆盖的元素,然后快慢指针刚好相差1个单位呢?

这里借助卡尔哥的代码吧,真的很巧妙:

class Solution {
    public int removeElement(int[] nums, int val) {
        int slowIndex = 0;//慢指针
//快指针作为for循环里面的单位,会自动++
        for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
//只有当快指针没有遇到指定元素,快慢指针会一起同步运动
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }//除了快指针遇到就会快一个单位,然后自然就可以做到覆盖啦
        }
        return slowIndex;//注意返回的是慢指针下标!
    }
}

有序数组的平方:

  • 思路1:很简单,平方然后用Array.sort(nums)就好了,O(n^2)

  • 思路2:

通过双指针从两边向中间移动,两个指针进行比较!

class Solution {
    public int[] sortedSquares(int[] nums) {
        int slow=0,fast=nums.length-1;
        int []result=new int[nums.length];
        //由于答案数组从小到大排序 倒叙!
        for(int i=nums.length-1;i>=0;i--){
            if(slow>fast) break;
            int num1=nums[slow]*nums[slow];
            int num2=nums[fast]*nums[fast];
            if(num1>=num2){
                result[i]=num1;
                slow++;
            }else{
                result[i]=num2;
                fast--;
            }
        }
        return result;
     }
}

总结:在数组里用双指针的话,必须是我们得去挖掘数组的信息(有序?还是趋势是什么?),要么就是通过速度差去完成某一个前后的动作等等!

长度最小的子数组

思路1:暴力

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
          //两层暴力:
          int result=nums.length+1; //用于记录是否有找到过答案的
          for(int i=0;i<nums.length;i++){
            int sum=0;
            for(int j=i;j<nums.length;j++){
                 sum+=nums[j];
                 if (sum >= target) {
                    result = Math.min(result, j - i + 1);
                    break;
                }
          }
          
         
    } return result>nums.length? 0:result;  //是否有更改,没有意味着找不到就是0 
  }
}

明显第一个for用来规定起始位置,第二个for用来规定终止位置,O(n^2)的大小,超时

那么我们优化的时候绝对会思考滑动窗口,因为太自然了,但是第一次接触滑动窗口的小伙伴就会疑惑,只用一个for循环如何记录起始位置和终止位置的?

那我们就来分析:

①如果for定的是起始位置,必然少不了往后遍历的过程

②如果for定的是终止位置,那么如何记录起始位置?那么我们就来详细学习滑动窗口!

窗口内是什么?-------满足其和 ≥ s 的长度最小的 连续 子数组

什么时候移动窗口的起始位置?----窗口已经满足>=s了,为了寻找最短,移动起始位置

什么时候移动窗口的结束位置?----窗口内子数组 和<s,为了扩大

思路2:滑动窗口

模板:

// 开始滑动窗口(外面定结束位置)
    while (right < s.length()) {
        // c 是将移入窗口的字符
        char c = s.charAt(right);
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        // ...

        // 判断左侧窗口是否要收缩(里面定起始位置)
        while (window needs shrink) { // 这个条件根据具体问题来设定
            // d 是将移出窗口的字符
            char d = s.charAt(left);
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            // ...

            // 更新最小窗口大小
            // ...
        }

        // 更新结果
        // ...
    }

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
         int left=0;
         int result=nums.length+1;
         int sum=0;
         for (int right = 0; right < nums.length; right++) {
            sum+=nums[right];
            //窗口内已经找到了答案(注意不是if而是while,因为是连续的过程)
            while(sum>=target){
               result=Math.min(result,right-left+1);
               //记录好了之后,移动起始位置,试探最短的长度
               sum-=nums[left++];
               
            }
         }
         return result>nums.length? 0:result;
  }
}

疑惑:为什么又有for和while,时间复杂度是O(n)呢?

分析:

  • 外层的for循环用来移动right指针,它遍历整个数组一次。因此,这个循环的时间复杂度是O(n)。
  • 内层的while循环用来移动left指针,收缩窗口的大小,直到窗口内的数组和小于target。重要的是要注意,尽管看起来while循环可能会被执行多次,但实际上,每个元素最多只会被left指针和right指针各访问一次
  • 由于每个元素只被访问两次(一次由right指针,一次由left指针),整个过程的时间复杂度是线性的,即O(n)。尽管代码中有嵌套循环,但它们并不是传统意义上的嵌套循环,因为每个元素不会被多次重复处理。

双指针总结:

  1. 在有序数组中找到目标元素
  2. 用于有速度差达到覆盖目的的
  3. 数组有大小大趋势的,为了获得一个递增的数组
  4. 滑动窗口
  5. (约瑟夫环的问题!)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值