算法刷题--数组专项

刷题笔记–数组专项

2022/4/25

一、二分查找

leetcode链接: https://leetcode-cn.com/problems/binary-search/

使用二分法的前提条件

  1. 数组为有序数组
  2. 数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的

二分查找涉及很多的边界条件,首先要确定边界条件。

区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

写二分法,区间的定义一般为两种,**左闭右闭即[left, right],**或者左闭右开即[left, right)。

习惯第一种写法,左闭右闭 我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
  • right = nums.length - 1; right的值要赋值为数组长度减一
class Solution {
    public int search(int[] nums, int target) {
        // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)
                return mid;
            else if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid - 1;
        }
        return -1;
    }
}

二、移除元素

leetcode链接: https://leetcode-cn.com/problems/remove-element/submissions/

1、暴力解法:双层for循环

一个for循环遍历数组元素 ,第二个for循环更新数组。

但是需要注意的是在循环移动的时候需要动态改变 i 和 size 的值,因为数组被更新了,防止出现连续等于val值的情况。

 //暴力解法
        int size=nums.length;
        for(int i=0;i<size;i++){
            if(nums[i]==val){
                for(int j=i;j<nums.length-1;j++)
                {
                    nums[j]=nums[j+1];
                }
                i--;
                size--;
               
            }
        }
       return size;

2、双指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

以快指针作为 for 循环的终止条件;,每次循环快指针的值都加一;

初始化的时候 快慢指针都指向 0 ,在for循环遍历数组的时候,如果数组值等于指定的val值,则什么也不做;

如果值不等于 val 值,则让慢指针加一,并且把快指针的值覆盖慢指针的值,如果没有相等的元素,那么快慢指针指向相同的值,如果出现了相等的元素,会导致慢指针速度比快指针低,直到遍历完成整个数组。

当出现连续的值的时候 比如3 2 2 3 删除 2 ;因为判断的是fast所在的值,因此遇见连续的会fast会继续往下走,slow依旧不动。

并且最后 slow 指针所指向的值就是数组去掉相等值之后的个数。

class Solution {
    public int removeElement(int[] nums, int val) {
        int fast=0;
        int slow=0;
        for(fast=0;fast<nums.length;fast++){
            if(nums[fast]==val){
                
            }else{
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;

    }
}

三、有序数组的平方

leetcode链接: https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html

1、暴力解法:

class Solution {
    public int[] sortedSquares(int[] nums) {
        //暴力解法:平方后排序
        for(int i=0;i<nums.length;i++){
            nums[i]=nums[i]*nums[i];
        }
        for(int i=0;i<nums.length;i++){
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]>nums[j]){
                    int temp=nums[i];
                    nums[i]=nums[j];
                    nums[j]=temp;
                }

            }
        }
        return nums;

    }
}

2、双指针法

重点:数组其实是有序的, 只不过负数平方之后可能成为最大数了。

那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

思路:

因此可以考虑使用新数组来保存平方过后的数组值,从最左边和最右边开始比较,当左边值平方大于右边值平方时,说明左边值比所有的值都大,因此放到新数组的最右边,当左边小于右边的时候,右边的值此时为最大,加入到新数组的当前size值指向的位置即可。每次加入后,size值–。

class Solution {
    public int[] sortedSquares(int[] nums) {
        //使用双指针和新的数组
        int size=nums.length;
        int left=0;
        int right=size-1;
        int[] arr=new int[size];
        //数组有序 
        while(left<=right){
            int l=nums[left]*nums[left];
            int r=nums[right]*nums[right];
            if(l>=r){
                arr[size-1]=l;
                left++;
        
            }else{
                arr[size-1]=r;
                right--;
            }
            size--;
        }
        return arr;
        
    }
}

2022/5/3

四、长度最小的子数组

leetcode链接: https://leetcode-cn.com/problems/minimum-size-subarray-sum/submissions/

1、暴力解法:双重for循环

  • 双层for循环 相当于把每一个元素的可能都列出来;
  • 每次循环需要重置sum的值;
  • 如果当前长度比之前的长度小就赋值给结果;
  • 等于10000说明没有连续子数组;
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        //双层for循环  相当于把每一个元素的可能都列出来
        int result=10000;
        int sum=0;
        int count=0;
        for(int i=0;i<nums.length;i++){
            sum=0;    //每次循环需要重置sum的值
            for(int j=i;j<nums.length;j++){
                sum+=nums[j];
                if(sum>=target){
                    count=j-i+1;
                    //如果当前长度比之前的长度小就赋值给结果
                    result=result<count?result:count;
                    break;   //只需要记录当前循环的长度最小值
            
                }
            }
        }
        //等于10000说明没有连续子数组
        return result==10000?0:result;

    }
}

在这里插入图片描述

2、滑动窗口(双指针)

​ 所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

  • 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
  • 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
  • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。

时间复杂度是O(n)

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

理解:

​ 当和大于目标值的时候,此时就需要移动窗口的起始位置了,因为如果不移动,继续向右移动的话,其和必定会大于目标值而且其**长度肯定会大于第一次和大于目标值时的长度,**因此需要不断调整窗口的起始位置。

​ 当窗口内数据的和小于目标值的时候,此时就不需要再继续移动初始值了,因为继续移动只会使得值越来越小。

总的来说,当窗口内数据的值大于目标值的时候,窗口的左边至少需要移动一次。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        //滑动窗口
        int left=0;
        int right=0;
        int result=Integer.MAX_VALUE;
        int tempCount=0;    //临时计数
        int sum=0;
        //以窗口的右边指针为循环条件,遍历数组
        for(;right<nums.length;right++){
            sum+=nums[right];
            while(sum>=target){
                tempCount=right-left+1;
                result=result<tempCount?result:tempCount;
                sum-=nums[left++];  //先减去左指针的数组值 再让左指针减一
            }
             
        }
        return result==Integer.MAX_VALUE?0:result;
    
    }
}

五、螺旋矩阵 II

leetcode链接: https://leetcode-cn.com/problems/spiral-matrix-ii/

按「圈」进行构建。

宫水三叶大佬的题解:

https://leetcode-cn.com/problems/spiral-matrix-ii/solution/yi-ti-shuang-jie-xiang-jie-xing-zhuang-j-24x8/

使用「左上角」(x1,y1)(x1,y1) &「右下角」(x2,y2)(x2,y2) 来确定某个「圈」,进行构建。

完成后,令「左上角」&「右下角」往里收,使得 (x1+1,y1+1) 和 ((x2−1,y2−1),执行相同的构建规则。

看来几个题解,感觉还是三叶大佬的这个方法比较容易实现,也容易理解。

  • 与之不同的是我并没有使用递归,而是判断当前值是否小于等于n平方作为while循环的条件每次循环把圈缩小即x1 + 1, y1 + 1,x2 - 1, y2 - 1。
  • 每次循环的时候需要判断x1>x2||y1>y2,当x1>x2时可以说明圈已经缩小到最小了,即圈中只有一个元素;再小的时候就返回空,跳出循环
  • 同时每条边判断的时候采用坚持 左闭右开的原则,即每一个边的最后一个元素的值不包含在本次循环写入中,切忌不能一会左闭右开,一会左闭右闭;坚持一个原则;
  • for(int i=y1;i<y2;i++) 其次发现一个问题,当n为奇数的时候,即最后一个圈一个元素时x1==x2,此时因为左闭右开的原则无法写入最后一个值,因此加入了一个判断 if(x1==x2){arr[x1][y1]=n*n;}

在这里插入图片描述

class Solution {
    public int[][] generateMatrix(int n) {
        //定义坐标画圈求解
        int[][] arr=new int[n][n];
        circle(0,0,n-1,n-1,n,arr);
        return arr;
    }

    public void circle(int x1,int y1,int x2,int y2,int n,int[][] arr){
         int val=1;   //保存当前的值
      
        int size=n*n;
        while(val<=size){
              if(x1>x2||y1>y2){   //说明此时模拟已经结束
            return ;
        }
        if(x1==x2){
            arr[x1][y1]=n*n;
            return;
        }
        //实行左闭右开
        //从左到右
        for(int i=y1;i<y2;i++)   arr[x1][i]=val++;
         //从上到下
        for(int i=x1;i<x2;i++)   arr[i][y2]=val++;
        //从右到左
        for(int i=y2;i>y1;i--)   arr[x2][i]=val++;
        //从下到上
        for(int i=x2;i>x1;i--)   arr[i][y1]=val++;
        x1++;y1++;
        x2--;y2--;
        }
       
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值