🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页
❄️欢迎查看我的专栏我的专栏
《Java后端学习》、《JAVASE基础》、《JUC并发》、《redis》、《JVM虚拟机》、《MYSQL》、《黑马点评》、《rabbitmq》、《JavaWeb+AI的talis学习系统》、《苍穹外卖》

目录
[二、238. 除自身以外数组的乘积(中等)](#二、238. 除自身以外数组的乘积(中等))
[2.1 题目描述](#2.1 题目描述)
[2.2 误区分析(新手常踩坑)](#2.2 误区分析(新手常踩坑))
[2.3 核心解题思路](#2.3 核心解题思路)
[2.4 Java 完整 AC 代码(逐行注释)](#2.4 Java 完整 AC 代码(逐行注释))
[2.5 复杂度分析](#2.5 复杂度分析)
[2.6 解题复盘](#2.6 解题复盘)
[三、73. 矩阵置零(中等)](#三、73. 矩阵置零(中等))
[3.1 题目描述](#3.1 题目描述)
[3.2 新手误区](#3.2 新手误区)
[3.3 核心解题思路](#3.3 核心解题思路)
[3.4 Java 完整 AC 代码](#3.4 Java 完整 AC 代码)
[3.5 复杂度分析](#3.5 复杂度分析)
[3.6 进阶优化思路(面试加分项)](#3.6 进阶优化思路(面试加分项))
[四、54. 螺旋矩阵(中等)](#四、54. 螺旋矩阵(中等))
[4.1 题目描述](#4.1 题目描述)
[4.2 解题难点](#4.2 解题难点)
[4.3 核心解题思路](#4.3 核心解题思路)
[4.4 Java 完整 AC 代码](#4.4 Java 完整 AC 代码)
[4.5 复杂度分析](#4.5 复杂度分析)
[4.6 易错点总结](#4.6 易错点总结)
一、前言:为什么数组算法是算法入门核心?
在算法体系中,数组是最基础、最朴素的线性结构,几乎所有高级算法(动态规划、双指针、贪心、单调栈)都建立在数组遍历的基础之上。很多同学刷题卡顿的核心原因,不是看不懂复杂算法,而是基础数组思维不扎实、边界处理不严谨、不会空间优化。
LeetCode 中等难度数组题,普遍具备以下特点:
-
逻辑不难,但是细节极多、易错点密集
-
暴力解法能通过部分案例,但无法满足时间、空间要求
-
需要思维转换:拆分遍历、状态标记、边界收缩、空间复用
今天讲解的三道题,覆盖了数组算法三大核心经典思想:
-
前后缀拆分思想(238)
-
原地标记、状态存储思想(73)
-
动态边界模拟、循环遍历思想(54)
三道题吃透,可解决 80% 中等数组、矩阵类面试题型。
二、238. 除自身以外数组的乘积(中等)
2.1 题目描述
给你一个整数数组 nums,返回数组 answer,其中 answeri 等于 nums 中除 numsi 之外其余各元素的乘积。
限制条件:
-
请勿使用除法
-
时间复杂度必须 O(n)
-
32 位整数范围内,无需考虑溢出
2.2 误区分析(新手常踩坑)
很多新手第一反应:求出数组总乘积,每个位置除以当前数字即可。
但题目明确禁止除法,且存在数组含 0 的场景,除法方案完全不可行。暴力双重循环 O(n²) 会超时,无法通过大数据案例。因此必须使用前后缀乘积拆分的优化思路。
2.3 核心解题思路
任意位置 i 的结果 = i 左侧所有数的乘积 × i 右侧所有数的乘积。
我们可以将计算拆分为两次单层遍历:
-
左遍历(前缀乘积):从前往后遍历,让 ansi 存储当前下标左侧所有元素的累积乘积。
-
右遍历(后缀乘积):从后往前遍历,用临时变量记录右侧累积乘积,与当前 ansi 相乘,得到最终结果。
该方案全程仅两次线性遍历,严格满足 O(n) 时间复杂度,且复用结果数组,无额外数组开销,空间最优。
2.4 Java 完整 AC 代码(逐行注释)
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
// 空数组直接返回
if(len == 0) return new int[0];
int[] ans = new int[len];
// 第一位左侧无元素,前缀乘积默认为1
ans[0] = 1;
// 第一次遍历:计算所有位置的前缀乘积
for(int i = 1; i < len ; i++){
ans[i] = nums[i-1] * ans[i-1];
}
// 临时变量存储后缀乘积
int temp = 1;
// 第二次遍历:从后往前,累乘右侧后缀乘积
for(int i = len -2 ; i >= 0 ; i--){
temp *= nums[i + 1];
ans[i] *= temp;
}
return ans;
}
}
2.5 复杂度分析
-
时间复杂度:O(n),两次线性遍历,无嵌套循环
-
空间复杂度:O(1),仅使用常数临时变量,结果数组不计入额外空间
2.6 解题复盘
本题核心思维就是拆分问题、空间换时间、复用数组。很多算法题不能直接求解结果,需要把结果拆分为「前缀+后缀」「左状态+右状态」,分两次遍历合并结果,这是数组优化最经典的套路,可通用大量算法题型。
三、73. 矩阵置零(中等)
3.1 题目描述
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。要求使用原地算法,尽可能节省空间。
3.2 新手误区
新手最容易犯的错误:遍历过程中直接置零。
如果一边遍历一边修改矩阵,后续遍历会把「原本正常的 0」和「我们手动置的 0」混淆,导致整表全部置 0,出现错误结果。
因此正确思路必须分为两步:先标记、后修改。
3.3 核心解题思路
-
创建两个布尔数组,分别记录「当前行是否存在 0」「当前列是否存在 0」
-
第一次遍历全矩阵,完成所有行列的 0 状态标记
-
第二次遍历全矩阵,只要当前行/列被标记为含 0,就将当前位置置零
该思路逻辑清晰、可读性极强、无边界 bug,面试中优先推荐该写法,不易翻车。
3.4 Java 完整 AC 代码
java
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
// 定义行、列标记数组
boolean[] rowHasZero = new boolean[m];
boolean[] colHasZero = new boolean[n];
// 第一次遍历:标记所有需要置零的行和列
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if( matrix[i][j] == 0){
rowHasZero[i] = colHasZero[j] = true;
}
}
}
// 第二次遍历:根据标记原地置零
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(rowHasZero[i] || colHasZero[j]){
matrix[i][j] = 0;
}
}
}
}
}
3.5 复杂度分析
-
时间复杂度:O(m*n),两次矩阵遍历
-
空间复杂度:O(m+n),使用两个标记数组,是性价比极高的解法
3.6 进阶优化思路(面试加分项)
如果题目要求极致空间优化,可以复用矩阵第一行、第一列作为标记位,可以将空间复杂度压缩至 O(1),适合高阶面试手撕代码,原理和本文思路一致,只是将外部数组改为原地标记。
四、54. 螺旋矩阵(中等)
4.1 题目描述
给你一个 m 行 n 列的矩阵 matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。
螺旋遍历顺序:从左到右 → 从上到下 → 从右到左 → 从下到上,循环往复,逐层向内收缩。
4.2 解题难点
本题无复杂算法,难点在于边界控制与循环终止条件。很多同学代码逻辑正确,但边界判断出错,导致漏遍历、重复遍历、数组越界。
4.3 核心解题思路
定义四个动态边界:
-
左边界 l、右边界 r
-
上边界 t、下边界 b
四轮遍历 + 边界收缩:
-
左 → 右:遍历完成后,上边界下移
-
上 → 下:遍历完成后,右边界左移
-
右 → 左:遍历完成后,下边界上移
-
下 → 上:遍历完成后,左边界右移
每一轮遍历结束后判断边界是否交叉,交叉则说明遍历完成,直接终止循环。
4.4 Java 完整 AC 代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
if(matrix.length == 0)
return new ArrayList<Integer>();
// 定义上下左右边界
int l = 0,r = matrix[0].length-1,t = 0,b = matrix.length-1,x = 0;
Integer[] ans = new Integer[(r+1) * (b+1)];
while(true){
// 1. 从左往右遍历顶层
for(int i = l ; i <= r; i++) ans[x++] = matrix[t][i];
if(++t > b) break;
// 2. 从上往下遍历右侧
for(int i = t ; i <= b ; i++) ans[x++] = matrix[i][r];
if(--r < l) break;
// 3. 从右往左遍历底层
for(int i = r ; i >= l ; i-- ) ans[x++] = matrix[b][i];
if(--b < t) break;
// 4. 从下往上遍历左侧
for(int i = b; i >= t; i--) ans[x++] = matrix[i][l];
if(++l > r) break;
}
return Arrays.asList(ans);
}
}
4.5 复杂度分析
-
时间复杂度:O(m*n),每个元素仅遍历一次
-
空间复杂度:O(1),仅边界变量,结果集合不计入额外空间
4.6 易错点总结
-
边界收缩顺序不能乱,必须按遍历顺序对应收缩
-
每一轮遍历结束必须判断边界交叉,否则会重复遍历内层数据
-
单行、单列矩阵的特殊场景,依靠边界判断自动兼容,无需单独特判
五、三道算法题核心思维总结
这三道中等难度数组题,覆盖了面试最核心的三种基础算法思维,也是所有进阶算法的前提:
-
拆分思维(238):复杂结果拆分为左右两段,分治遍历、合并结果,规避暴力解法的缺陷。
-
标记思维(73):只读遍历做标记、二次遍历做修改,隔离数据读取与修改,避免数据污染。
-
边界模拟思维(54):用动态边界代替固定下标,适配矩阵逐层遍历场景,通用性极强。
刷题不要只背代码,要吃透通用思维模型,遇到同类变式题可以直接套思路,这才是算法刷题的真正意义。
六、刷题优化与复盘建议
-
数组类题目优先思考:能否一次遍历、能否空间复用、能否拆分前后缀,优先压缩时间与空间复杂度;
-
矩阵模拟类题目,核心永远是边界控制,代码逻辑简单,细节决定是否 AC;
-
刷题后务必复盘:记录易错点、优化思路、面试口述思路,做到手撕无 bug、口述逻辑清晰。
结语
中等数组题是算法分水岭,刷透基础数组与矩阵题,能够极大提升代码思维、边界处理能力与面试临场手撕能力。本文三道高频题,是校招、实习、面试必刷题库,建议大家独立手写两遍,吃透思路、摆脱题解依赖,真正做到举一反三。



