977题上一天已经写过,今天从滑动窗口开始
三道滑动窗口
对于什么时候使用滑动窗口解决问题:必然是要求几个数是挨在一起满足一些条件
给定一个含有 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.返回结果
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 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
。如果键不存在,那么这个键值对将被添加到映射中。
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
三道螺旋矩阵
给你一个正整数 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而不是0?
根据题目的要求,你需要生成一个从1到�×�n×n的螺旋矩阵,而不是从0开始。因此,你的起始数应该是1,不是0。
-
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
,你可以参考以下规则:
-
基本数据类型:对于基本数据类型(如
int
,char
,double
,boolean
等),在声明并初始化变量时,你不需要使用new
。int n = 10; char c = 'a'; double d = 3.14; boolean b = true;
-
对象:对于类或对象(包括标准库中的对象,如
String
,ArrayList
等),你需要使用new
来创建一个新的实例。String str = new String("Hello"); ArrayList<Integer> list = new ArrayList<Integer>();
-
数组:对于数组,无论它们是基本数据类型还是对象,都需要使用
new
来分配内存空间。int[] arr = new int[10]; String[] strArray = new String[5];
-
封装类:对于基本数据类型的封装类(如
Integer
,Character
,Double
,Boolean
等),你可以不使用new
直接赋值,因为Java会自动装箱。但如果你要显式地创建一个新的对象实例,那么你需要使用new
。// 自动装箱,不使用new Integer num1 = 10; // 使用new创建新的Integer对象 Integer num2 = new Integer(10);
综上,如果你正在为基本数据类型的变量分配值,你不需要使用new
。但是,如果你正在创建一个对象或数组的新实例,那么你需要使用new
。
给你一个 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;
这两个条件是不会重复的,因为它们控制的是不同方向的遍历。
-
if (++t > b) break;
: 当我们完成从左到右的遍历后,我们移动上边界t
,这是因为我们已经访问了这一行。然后我们检查是否有更多的行可以从上到下遍历。如果没有(也就是说t
超过了b
),那么我们结束整个螺旋遍历。 -
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
来提前结束循环。
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
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)
是一个更为全面的检查,它涵盖了以下三种可能的无效输入情况:
-
matrix == null
: 这确保代码不会对null
值进行操作,从而避免NullPointerException
。 -
matrix.length == 0
: 这确保传入的数组不是一个空数组。这样,后续当我们试图访问matrix[0]
时不会出现ArrayIndexOutOfBoundsException
。 -
matrix[0].length == 0
: 这确保即使第一行存在,它也不是一个空数组。这对于形如matrix = [[], [], []]
的输入非常有用,这样的输入表示一个二维数组,但其中的每一行都是空的。
这三种检查确保代码能够安全地处理各种可能的无效输入情况,提高了代码的健壮性。简单地只检查 matrix == null
可能会遗漏其他两种无效输入的情况,从而导致后续代码中的异常。
null和空数组有区别吗
ChatGPT
是的,null
和空数组在Java中是有明显区别的。让我们分别看看它们的特点:
-
null:
null
是一个特殊的值,表示没有引用任何对象的引用变量。- 对于对象和数组变量,其默认值是
null
。 - 试图访问
null
引用的成员将抛出NullPointerException
。 null
可用于任何引用类型的变量,不仅仅是数组。
-
空数组:
- 空数组是一个真正的数组对象,但它没有任何元素。
- 它的长度为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。