Day2-第一章数组part2-滑动窗口与螺旋矩阵

977题上一天已经写过,今天从滑动窗口开始

三道滑动窗口

对于什么时候使用滑动窗口解决问题:必然是要求几个数是挨在一起满足一些条件

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

lass Solution {
    public int minSubArrayLen(int target, int[] nums) {
         int left = 0;
        int right = 0;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        int n = nums.length;
        for ( right = 0; right < n; right++) {
            sum = sum+ nums[right];
            while (sum>=target){
                result =Math.min(result,right-left+1);
                sum = sum-nums[left];
                left++;
            }
        }
        return result==Integer.MAX_VALUE? 0:result;
    }
}

思路:

1.定义滑动窗口左右边界

2.定义一个决定滑动窗口的量sum

3.为返回结果赋初值

4.外层循环为窗口右边界不断扩张

5.内层循环为保持窗口条件左边界不断跟上

6.返回结果

904. 水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

public class Blog1 {
    public int totalFruit(int[] fruits) {
        Map<Integer,Integer> cnt = new HashMap<>();//这里使用map是为了方便计数
        int left = 0;
        int ans = 0;
        int n =fruits.length;
        for (int right = 0; right < n; right++) {
            cnt.put(fruits[right],cnt.getOrDefault(fruits[right],0)+1);//这一行负责篮子里水果数量一直加下去,可以背下来这一句常用的map里元素计数的代码。
//再写一遍之后更加理解了题目的意思fruits[right]即为水果种类。可能是苹果桃子苹果桃子苹果桃子苹果桃子这样就会全部收进来
            while (cnt.size()>2){//划定窗口范围,打断计数,说明有了三个篮子,通过这个循环减掉最左边的篮子来保持窗口
//再写一遍的总结:如果已经有两种水果,现在right前进遇到了第三种水果,就会通过第二个while循环把第一种水果数量全部消掉
                cnt.put(fruits[left],cnt.get(fruits[left])-1);
                if (cnt.get(fruits[left])==0){
                    cnt.remove(fruits[left]);
                }
                left++;
            }
            ans = Math.max(ans,right-left+1);//两个篮子两个篮子进行比较
        }
        return ans;
    }
}
class Solution {
    public int totalFruit(int[] fruits) {
        Map<Integer,Integer> cnt = new HashMap<>();
        int left = 0;
        int right = 0;
        int result = 0;
        int n = fruits.length;
        for (right= 0; right < n; right++) {
            cnt.put(fruits[right], cnt.getOrDefault(fruits[right],0)+1);
            while (cnt.size()>2){
                cnt.put(fruits[left],cnt.get(fruits[left])-1);
                if (cnt.get(fruits[left])==0){
                    cnt.remove(fruits[left]);
                }
                left++;
            }
            result = Math.max(result,right-left+1);
        }
        return result;
    }
}

思路:为什么这道题用了hashmap而上一道题没有

因为209题我们不需要关心哪些具体的元素在滑动窗口内,只需要关心滑动窗口内的元素之和。

这道题的关键是找出只包含两种水果的最长连续子数组。为了做到这一点,我们需要知道某个区间内的每种水果数量,这样当区间内的水果种类超过2时,就可以更新左边界来缩小区间。因此,使用哈希表来记录当前滑动窗口内的每种水果的数量是非常有意义的。

这就是为什么在第一题中不需要定义一个map,而在第二题中需要定义一个hashmap的原因:第二题需要跟踪滑动窗口内的具体元素及其出现的频率,而第一题只关心滑动窗口内的元素之和。

2.像这句话,如果有重复的键,那么键就不会放进去,只会改掉之前的值对吗

cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);

是的,你的理解是正确的。

在Java的HashMap中,put方法用于将指定的键值对插入到映射中。如果映射之前已经包含了该键的映射关系,则旧值会被替换。

简单地说,如果键 fruits[right] 已经存在于 cnt 中,则它的值会被更新为 cnt.getOrDefault(fruits[right], 0) + 1。如果键不存在,那么这个键值对将被添加到映射中。

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

三道螺旋矩阵

59. 螺旋矩阵 II

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

class Solution {
    public int[][] generateMatrix(int n) {
        int l = 0;//左边界,表示列
        int r = n-1;
        int t = 0;//上边界,表示行
        int b = n-1;
        int [][] mat=new int[n][n];
        int num = 1;
        int target = n*n;
        while (num<=target){
            for (int i = l; i <= r; i++) {
                mat[t][i]=num++;//遍历完第一行
            }
            t++;//left-right
            for (int i = t; i <= b; i++) {//此时t已经等于1,避免了第一行末位数的重复
                mat[i][r]=num++;
            }
            r--;
            for (int i = r; i >=l; i--) {
                mat[b][i]=num++;
            }
            b--;
            for (int i = b; i  >=t; i--) {
                mat[i][l]=num++;
            }
            l++;
        }
        return mat;
    }
}

为什么是1而不是0,mat[t][i]=num++这句话又应该怎么理解

ChatGPT

好的,我明白你的疑问了。让我们来逐步解答:

  1. 为什么是1而不是0?

    根据题目的要求,你需要生成一个从1到�×�n×n的螺旋矩阵,而不是从0开始。因此,你的起始数应该是1,不是0。

  2. mat[t][i] = num++这句话如何理解?

    这行代码包含了一个后缀递增操作。其含义可以拆分为以下两个步骤:

    a. mat[t][i] = num;:将num的当前值赋给mat[t][i]

    b. num = num + 1;:增加num的值。

    使用num++是一种简洁的方式,同时完成了赋值和递增两个操作。

int[][] mat = new int[n][n];像这种什么时候需要加new什么时候不需要,是不是只有int n = ..的时候才不需要

ChatGPT

在Java中,关键字new用于创建对象和数组的新实例。关于何时使用new,你可以参考以下规则:

  1. 基本数据类型:对于基本数据类型(如int, char, double, boolean等),在声明并初始化变量时,你不需要使用new

    int n = 10; char c = 'a'; double d = 3.14; boolean b = true; 
  2. 对象:对于类或对象(包括标准库中的对象,如String, ArrayList等),你需要使用new来创建一个新的实例。

    String str = new String("Hello"); ArrayList<Integer> list = new ArrayList<Integer>(); 
  3. 数组:对于数组,无论它们是基本数据类型还是对象,都需要使用new来分配内存空间。

    int[] arr = new int[10]; String[] strArray = new String[5];

  4. 封装类:对于基本数据类型的封装类(如Integer, Character, Double, Boolean等),你可以不使用new直接赋值,因为Java会自动装箱。但如果你要显式地创建一个新的对象实例,那么你需要使用new

    // 自动装箱,不使用new Integer num1 = 10; // 使用new创建新的Integer对象 Integer num2 = new Integer(10); 

综上,如果你正在为基本数据类型的变量分配值,你不需要使用new。但是,如果你正在创建一个对象或数组的新实例,那么你需要使用new

54. 螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
         List<Integer> ans = new ArrayList<>();
        int m = matrix.length;
        int n = matrix[0].length;
        int l = 0;
        int r = n - 1;
        int t = 0;
        int b = m - 1;
        int count = 1;
        int target = m * n;
        while (count <= target) {
            for (int i = l; i <= r ; i++) { 
                if(count <= target){// 添加检查
                    ans.add(matrix[t][i]);
                    count++;
                }
            }
            t++;
            for (int i = t; i <= b ; i++) { // 添加检查
                if(count <= target){
                    ans.add(matrix[i][r]);
                    count++;}
            }
            r--;
            for (int i = r; i >= l ; i--) { // 添加检查
                if(count <= target){
                    ans.add(matrix[b][i]);
                    count++;
                }
            }
            b--;
            for (int i = b; i >= t ; i--) { // 添加检查
               if(count <= target){
                 ans.add(matrix[i][l]);
                 count++;
               }
            }
            l++;
        }
        return ans;
    }
}

if (++t > b) break;和if (t > --b) break;会不会重复了呢,还是两个都是有必要的

ChatGPT

if (++t > b) break;if (t > --b) break; 这两个条件是不会重复的,因为它们控制的是不同方向的遍历。

  1. if (++t > b) break;: 当我们完成从左到右的遍历后,我们移动上边界 t,这是因为我们已经访问了这一行。然后我们检查是否有更多的行可以从上到下遍历。如果没有(也就是说 t 超过了 b),那么我们结束整个螺旋遍历。

  2. if (t > --b) break;: 当我们完成从右到左的遍历后,我们移动下边界 b,因为我们已经访问了这一行。然后我们检查是否还有更多的行可以从下到上遍历。如果没有,我们也结束整个螺旋遍历。

这两个条件分别处理两个不同的遍历方向(从上到下和从下到上),因此它们都是必要的。

总之,这两个条件确保我们在每个方向上只访问矩阵中未被访问的部分。当没有更多的部分可以访问时,我们结束遍历。

++t > b为什么是>而不是>=

ChatGPT

在螺旋矩阵遍历的情境中,我们使用 ++t > b 来确保在从左到右遍历当前行之后,仍然有行可以从上到下遍历。

这里的 t 代表上边界,而 b 代表下边界。每次我们完成一行的遍历,我们会将上边界 t 向下移动一行(通过 ++t)来表示该行已被访问过,并检查上边界是否超过了下边界。

考虑以下情况:

如果 t 等于 b,这意味着上边界和下边界都指向同一行,因此我们仍然有一行可以从上到下遍历。在这种情况下,我们不想打断遍历过程。

只有当 t 超过了 b(即 t > b)时,我们知道所有的行都已被访问过,并且没有更多的行可以从上到下遍历,所以我们此时要终止遍历。

使用 >= 会导致在上边界和下边界指向同一行时提前终止遍历,这并不是我们想要的结果。所以,我们使用 > 来确保上边界真的超过了下边界才终止遍历。

LeetCode 54 题描述为“螺旋矩阵”,要求读者从一个m x n大小的矩阵中,按照螺旋的方式输出它的所有元素。

LeetCode 59 题描述为“螺旋矩阵 II”,要求读者生成一个n x n大小的螺旋矩阵,其中的元素是1到n^2的整数。

这两个问题都与螺旋矩阵有关,但是它们的任务不同:一个是读取,另一个是生成。

对于LeetCode 54,当我们按照螺旋的顺序遍历矩阵时,我们需要注意矩阵的边界以及已经遍历过的部分。因此,在循环中,我们可能会使用break来结束循环,防止超出边界或者重复遍历。

而对于LeetCode 59,我们的任务是生成矩阵,所以我们可以提前知道循环的次数(比如对于一个3 x 3的矩阵,我们知道需要循环9次)。因此,在这种情况下,我们不需要使用break来结束循环,因为我们已经知道何时停止。

总之,break的使用取决于我们的任务和循环的目的。在LeetCode 54中,我们需要确保不会超出边界或重复遍历,所以可能使用break。而在LeetCode 59中,我们提前知道循环的次数,所以不需要使用break来提前结束循环。

剑指 Offer 29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return new int[0];
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int[] ans = new int[m * n];
        int index = 0;
        int l = 0;
        int r = n - 1;
        int t = 0;
        int b = m - 1;
        while (true) {
            for (int i = l; i <= r; i++) {
                ans[index] = matrix[t][i];
                index++;
            }
            t++;
            if (t > b) {//注意是b不是m,因为b也是在变化的
                break;
            }
            for (int i = t; i <= b; i++) {
                ans[index] = matrix[i][r];
                index++;

            }
            r--;
            if (r < l) {
                break;
            }
            for (int i = r; i >= l; i--) {
                ans[index] = matrix[b][i];
                index++;

            }
            b--;
            if (b < t) {
                break;
            }
            for (int i = b; i >= t; i--) {
                ans[index] = matrix[i][l];
                index++;
            }
            l++;
            if (l > r) {
                break;
            }
        }
        return ans;
    }
}

if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return new int[0]; } 为什么不止写matrix == null

ChatGPT

这里的条件 if (matrix == null || matrix.length == 0 || matrix[0].length == 0) 是一个更为全面的检查,它涵盖了以下三种可能的无效输入情况:

  1. matrix == null: 这确保代码不会对 null 值进行操作,从而避免 NullPointerException

  2. matrix.length == 0: 这确保传入的数组不是一个空数组。这样,后续当我们试图访问 matrix[0] 时不会出现 ArrayIndexOutOfBoundsException

  3. matrix[0].length == 0: 这确保即使第一行存在,它也不是一个空数组。这对于形如 matrix = [[], [], []] 的输入非常有用,这样的输入表示一个二维数组,但其中的每一行都是空的。

这三种检查确保代码能够安全地处理各种可能的无效输入情况,提高了代码的健壮性。简单地只检查 matrix == null 可能会遗漏其他两种无效输入的情况,从而导致后续代码中的异常。

null和空数组有区别吗

ChatGPT

是的,null 和空数组在Java中是有明显区别的。让我们分别看看它们的特点:

  1. null:

    • null 是一个特殊的值,表示没有引用任何对象的引用变量。
    • 对于对象和数组变量,其默认值是 null
    • 试图访问 null 引用的成员将抛出 NullPointerException
    • null 可用于任何引用类型的变量,不仅仅是数组。
  2. 空数组:

    • 空数组是一个真正的数组对象,但它没有任何元素。
    • 它的长度为0(例如,myArray.length 返回0)。
    • 你可以安全地访问其属性,如其长度,而不会抛出异常。但试图访问其任何索引的元素会抛出 ArrayIndexOutOfBoundsException
    • 它是一个特定于数组的概念。

举例来说: 

int[] nullArray = null; int[] emptyArray = new int[0]; System.out.println(nullArray); // 输出:null System.out.println(emptyArray); // 输出:[I@someHashCode System.out.println(nullArray.length); // 抛出 NullPointerException System.out.println(emptyArray.length);// 输出:0 

在上面的例子中,nullArray 是一个 null 引用,而 emptyArray 是一个空数组。尝试访问 nullArray 的长度会抛出异常,但可以安全地访问 emptyArray 的长度,结果为0。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值