代码随想录——数组

一、二分查找(704 简单)

前提: 1. 所要查找的数组必须有序。
   2. 无重复元素。

二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)还是while(left <= right),到底是right = middle呢,还是要right = middle-1呢?
  大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则

class Solution {
    public int search(int[] nums, int target) {
        
        if(target<nums[0]||target>nums[nums.length-1]){
            return -1;
        }
        int head=0;
        int end=nums.length-1;
        
        while(head<=end){

            int middle=(head+end)/2;
            if(nums[middle]==target){
                return middle;
                }else if(nums[middle]<target){
                head=middle+1;
                }else{
                end=middle-1;
                }
        }
        return -1;
    }

}

二、移除元素(27 简单)

2.1 两个for循环

暴力解法:首先对数组进行遍历(一个for循环),若遍历到某一个数组元素为目标值,则数组中后面的元素全部向前进一维将该等于目标值的元素覆盖(一个for循环)。

2.2 双指针法

  1. 快慢指针:有一个for循环,整个for循环是快指针的循环。
    快指针:用来查找不是目标值的数组中的元素。【用来获取新数组中的元素】
    慢指针:用来创建新的数组。也就是说用快指针查找到的不是目标值的元素,赋给慢指针的位置。【用来获取新数组中元素的位置】
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];
                slow++;//变成++slow也可以运行
            }
        }
        return slow;
    }
}

注:注意slow++

  1. 相向双指针:基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素。
    基本思想: 左边的指针找左边等于val的元素,右边的指针找不等于val的元素。
    左边的指针每一步循环都前进,当等于val时,用右边的指针的元素替换该元素,此时右边的指针才向左走找到下一个不等于val的元素。所以能够看出,右边的指针把为val的元素都跳过,把不是val的元素都替换给左边等于val的元素的位置。所以当left>right时循环结束,此时left指向的位置为最终数组末尾的下一个元素。

三、有序数组的平方(977 简单)

题目描述:已知一个排好序的数组,把每个数平方然后把平方后的数再排序。

方法一: 先平方,再排序

双指针法

方法二: 双指针法,因为已知数组按升序排序的,则平方后最大的数要么是最小的负数的平方,要么是最大的正数的平方。用双指针法,相向指针,比较大小。

注:

  1. 在这里使用的指针法因为比较最左和最右的两个数的平方,所以我们需要得到的是最大值,放到新的数组的最后一个元素处。
  2. 如果左边指针的元素的平方大,则把左边指针加1;如果右边指针的元素的平方大,则把右边指针减1.
  3. 编程时注意i++和++i的区别。
    • a=i++ 先把i的值赋予a,然后再执行i=i+1
    • a=++i 先执行i=i+1,然后把i的值赋予a
class Solution {
    public int[] sortedSquares(int[] nums) {
        int left=0;
        int right=nums.length-1;
        int[] result=new int[nums.length];
        int j=nums.length-1;
        while(left<=right){
            if(nums[left]*nums[left]>nums[right]*nums[right]){
                result[j--]=nums[left]*nums[left];
                ++left;//left++也能运行出正确结果
            }else{
                result[j--]=nums[right]*nums[right];
                --right;//right--也能运行出正确结果
            }

        }
        return result;
    }
}

四、长度最小的子数组(209 中等)

题目描述:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

注:和移除元素的两个方法的思路类似。

4.1 暴力解法

两个for循环。基本思想:从数组的第一个元素开始,依次做加法,和目标数s比较。因为要找的是长度最小的连续子数组,所以把所有情况都循环,设置一个result保存子数组的长度,一旦满足大于等于s,就更新result,再依次比较。

4.2 滑动窗口

滑动窗口也可以理解为双指针法的一种。只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

窗口就是满足其和 ≥ s 的长度最小的连续子数组。

  • 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

  • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

从左往右看,左边是窗口的起始位置,右边是窗口的结束位置。和暴力解法相比,滑动窗口只需要一个for循环,那么该循环的索引应该是窗口的起始位置还是结束位置呢?应该是结束位置(也可以看作快指针)。
算法:
当首次满足≥s时,对初始位置的指针右移,再判断是否≥s;

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int j=0;//窗口的开始位置
        int sum=0;
        int result=Integer.MAX_VALUE;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            if(sum>=target){
                
                result=Math.min(i-j+1,result);
                sum-=(nums[j]+nums[i]);
                j++;
                i--;    
            }
        }
        //如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result== Integer.MAX_VALUE ? 0 : result;
    }
}

注:

  1. 这里注意if语句的逻辑,但是按照滑动窗口的逻辑,使用while语句更简单。因为if走完了就跳出这个if语句了,执行i++;但是while直到不满足条件语句才会进行下一次的for循环,执行i++。
  2. Integer.MAX_VALUE ? 0 : result
      三元表示符:
      (关系表达式) ? 表达式1 : 表达式2;
      int x = 10;
      int y = 5;
      int z;
      如果x大于y 则是true,将x赋值给z;
      如果x不大于y 则是false,将y赋值给z;
      z = (x > y) ? x : y;

```java
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int j=0;//窗口的开始位置
        int sum=0;
        int result=Integer.MAX_VALUE;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            while(sum>=target){
                result=Math.min(i-j+1,result);
                sum-=nums[j];
                j++;  
            }
        }
        return result== Integer.MAX_VALUE ? 0 : result;
    }
}

五、螺旋矩阵Ⅱ(59 中等)

题目描述:给定一个正整数 n,生成一个包含1到 n^2所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
要求:当n=3时,输出 [ [1,2,3],[8,9,4],[7,6,5] ]

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

所以思路就是:按从左到右,从上到下,从右到左,从下到上循环,把这个看作是一次循环。所以外面的大循环是一圈一个循环。n=3时,一圈循环,一个中心点;n=4时,两圈循环;n=5时,两圈循环,一个中心点。所以当n为偶数时,外面大循环次数为n/2;当n为奇数时,外面大循环(n-1)/2,但是有个中心点

所以为了在写循环的过程中不乱套,也遵循一个固定原则。如果我们遵循左闭右开的原则,对于一个3×3的矩阵,则每一行(列)的最后一个元素和下一个循环一起。

用左闭右开的原则写的代码如下:

class Solution {
    public int[][] generateMatrix(int n) {
        int [][] res=new int[n][n];//定义一个二维数组
        int loop=0;//控制循环次数
        int start = 0;  // 每次循环的开始点(start, start)
        int count = 1;  // 定义填充数字
        int i, j;//把i看成是矩阵的行,j看成是矩阵的列,所以二维数组其实就可以看成是矩阵

        while (loop++ < n / 2) { // 判断边界后,loop从1开始
            // 模拟上侧从左到右,左闭右开
            for (j = start; j < n - loop; j++) {
                res[start][j] = count++;
            }

            // 模拟右侧从上到下,j不动
            for (i = start; i < n - loop; i++) {
                res[i][j] = count++;
            }

            // 模拟下侧从右到左,i不动
            for (; j >= loop; j--) {
                res[i][j] = count++;
            }

            // 模拟左侧从下到上,j不动
            for (; i >= loop; i--) {
                res[i][j] = count++;
            }
            start++;//一圈大循环结束之后给start加1,下一次循环从(start,start)开始
        }

        if (n % 2 == 1) {
            res[start][start] = count;
        }

        return res;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值