1 矩阵置零
1.1 思路
思路比较简单
先遍历矩阵,用boolean数组 row、col标记需要置零的行和列号。
再遍历一遍矩阵,如果row[i] || col[j],那么就把matrix[i][j]置零。
1.2 实现代码
java
class Solution {
public void setZeroes(int[][] matrix) {
// 获取矩阵的行数m和列数n
int m = matrix.length, n = matrix[0].length;
// 定义两个布尔数组,标记需要置零的行和列
// row[i] = true 表示第i行需要置零;col[j] = true 表示第j列需要置零
boolean[] row = new boolean[m];
boolean[] col = new boolean[n];
// 第一步:遍历矩阵,标记需要置零的行和列
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 若当前元素为0,标记其所在行和列
if (matrix[i][j] == 0) {
row[i] = true;
col[j] = true;
}
}
}
// 第二步:根据标记,将对应行/列的所有元素置零
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 只要当前位置的行 或 列被标记,就置零
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
}
}
2 螺旋矩阵
2.1 思路
定义top bottom left right四个变量
while(top <= bottom&& left <= right)来控制循环
从左到右、从上到下、从右到左、从下到上
每次for循环,对应的控制变量就要对应++或--。而且++或--后要判断是否非法,(top > bottom或者right < left等等,要提前退出循环)
2.2 实现代码
java
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
// 定义结果列表,存储螺旋遍历的元素
List<Integer> res = new ArrayList<>();
// 获取矩阵的行数m和列数n
int m = matrix.length, n = matrix[0].length;
// 定义四个边界变量,控制遍历范围:
// top:上边界(初始为第0行)
// bottom:下边界(初始为最后一行m-1)
// left:左边界(初始为第0列)
// right:右边界(初始为最后一列n-1)
int top = 0, bottom = m - 1, left = 0, right = n - 1;
// 循环条件:上边界不超过下边界,且左边界不超过右边界(还有未遍历的区域)
while (top <= bottom && left <= right) {
// 第一步:遍历"上边界"的行 → 从左到右
for (int i = left; i <= right; i++) {
res.add(matrix[top][i]);
}
top++; // 上边界向下收缩一行(该层的顶行已遍历完)
if (top > bottom) break; // 边界交叉,提前退出(避免单行重复遍历)
// 第二步:遍历"右边界"的列 → 从上到下
for (int i = top; i <= bottom; i++) {
res.add(matrix[i][right]);
}
right--; // 右边界向左收缩一列(该层的右列已遍历完)
if (right < left) break; // 边界交叉,提前退出(避免单列重复遍历)
// 第三步:遍历"下边界"的行 → 从右到左
for (int i = right; i >= left; i--) {
res.add(matrix[bottom][i]);
}
bottom--; // 下边界向上收缩一行(该层的底行已遍历完)
if (bottom < top) break; // 边界交叉,提前退出
// 第四步:遍历"左边界"的列 → 从下到上
for (int i = bottom; i >= top; i--) {
res.add(matrix[i][left]);
}
left++; // 左边界向右收缩一列(该层的左列已遍历完)
}
// 返回最终的螺旋遍历结果
return res;
}
}
3 旋转图像
3.1 思路

先转置,再把每一行翻转。
转置:只遍历i > j的部分;交换matrix[i][j]和matrix[j][i]
翻转每一行,双指针写个函数即可。
3.2 实现代码
java
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length; // 正方形矩阵的边长
// 步骤1:矩阵转置(行和列互换)
for (int i = 0; i < n; i++) {
// j < i:遍历当前行的"下三角区域",避免重复交换
for (int j = 0; j < i; j++) {
// 交换matrix[i][j]和matrix[j][i](转置核心)
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 步骤2:翻转每一行(左右翻转)
for (int i = 0; i < n; i++) {
rotateRow(matrix[i]); // 调用独立方法翻转当前行
}
}
/**
* 辅助方法:双指针翻转单行(左右翻转)
* @param row 要翻转的行数组
*/
public void rotateRow(int[] row) {
int n = row.length;
int left = 0, right = n - 1; // 双指针:左指针从0开始,右指针从末尾开始
while (left <= right) { // 指针相遇时停止(奇数长度的行,中间元素无需交换)
// 交换左右指针位置的元素
int temp = row[left];
row[left] = row[right];
row[right] = temp;
left++; // 左指针右移
right--; // 右指针左移
}
}
}
4 搜索二维矩阵Ⅱ
4.1 思路
把它想想象成一颗以右上角为根节点的二叉搜索树即可。很简单
4.2 实现代码
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 获取矩阵的行数m和列数n
int m = matrix.length, n = matrix[0].length;
// 选择矩阵右上角作为搜索起始点:
// startRow=0(第一行),startCol=n-1(最后一列)
int startRow = 0, startCol = n - 1;
// 循环条件:行索引未越界(<m)且列索引未越界(>=0)
while (startRow < m && startCol >= 0) {
// 取出当前位置的元素值
int current = matrix[startRow][startCol];
if (current > target) {
// 情况1:当前值 > 目标值 → 目标值不可能在当前列(列是升序,下方元素更大)
// 列索引左移,排除当前列
startCol--;
} else if (current < target) {
// 情况2:当前值 < 目标值 → 目标值不可能在当前行(行是升序,左侧元素更小)
// 行索引下移,排除当前行
startRow++;
} else {
// 情况3:当前值 == 目标值 → 找到目标,返回true
return true;
}
}
// 循环结束仍未找到,返回false
return false;
}
}
5 最大子数组和
5.1 dp思路
dp[i] = Math.max(nums[i], dp[i-1] + nums[i])
对于第 i 个元素,有两种选择:
选择 1:只取自己(放弃前面的子数组,重新开始);
选择 2:加入前面的子数组(继承前面的最大和);
取两者中较大的值,就是以 i 结尾的最大子数组和
遍历所有元素,取dp[i]的最大值即可。
5.2 实现代码
java
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
// 优化:无需额外dp数组,用变量保存前一个状态(空间O(1))
int pre = nums[0]; // pre 等价于 dp[i-1],初始为第一个元素
int maxSum = pre; // 记录全局最大和,初始为第一个元素
for (int i = 1; i < n; i++) {
// 状态转移:取「当前元素」或「当前元素+前一个最大和」的较大值
pre = Math.max(nums[i], pre + nums[i]);
// 更新全局最大和
maxSum = Math.max(maxSum, pre);
}
return maxSum;
}
}
5.3 贪心思路
维护一个curSum,遍历数组,如果curSum >= 0,则把nums[i]加入curSum。如果curSum < 0,则直接把curSum = nums[i]。
同时维护最大结果res。
5.4 实现代码
java
class Solution {
public int maxSubArray(int[] nums) {
int currentSum = nums[0]; // 当前子数组和,初始为第一个元素
int maxSum = nums[0]; // 全局最大和,初始为第一个元素
for (int i = 1; i < nums.length; i++) {
// 贪心决策:如果当前和为负,重置为当前元素;否则累加当前元素
if (currentSum < 0) {
currentSum = nums[i];
} else {
currentSum += nums[i];
}
// 更新全局最大和
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}
}
6 合并区间
6.1 思路
注意点1:先按左侧排序
Arrays.sort(Intervals, (a, b) -> {return Integer.compare(a[0], b[0]});要会写
注意点2:
start = intervals[0][0] end = intervals[0][1];
遍历数组,如果intervals[i][0] > end,那么就没有重合,直接新建一个数组存start和end,存进res。
否则,更新end = Math.max(end, intervals[i][1])即可。
注意点3:最后一个start、end要单独放到res里面。
注意点4:return res.toArray(new int[res.size()][2]);要会写
6.2 实现代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public int[][] merge(int[][] intervals) {
int len = intervals.length;
// 优化1:处理空输入,直接返回空数组
if (len == 0) {
return new int[0][2];
}
List<int[]> res = new ArrayList<>();
// 按区间起始值升序排序(Lambda表达式简化写法)
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
// 初始化当前合并区间的起止值
int start = intervals[0][0], end = intervals[0][1];
// 遍历剩余区间
for (int i = 1; i < len; i++) {
int curStart = intervals[i][0];
int curEnd = intervals[i][1];
if (curStart > end) {
// 无重叠:将当前合并区间加入结果,重置起止值
res.add(new int[]{start, end});
start = curStart;
end = curEnd;
} else {
// 有重叠:更新合并区间的结束值(取最大值)
end = Math.max(end, curEnd);
}
}
// 加入最后一个合并区间
res.add(new int[]{start, end});
// 优化2:简化List转数组的写法
return res.toArray(new int[0][2]);
}
}
7 轮转数组
7.1 思路
写一个从下标start到end反转数组的工具函数
先全部翻转一遍
再0 ~ k - 1,翻转一遍, k ~ len - 1翻转一遍
7.2 实现代码
java
class Solution {
/**
* 主方法:将数组向右轮转 k 个位置
* @param nums 待轮转的数组
* @param k 轮转的步数
*/
public void rotate(int[] nums, int k) {
int len = nums.length;
// 关键优化:k 可能大于数组长度,取模后得到有效轮转步数(比如数组长度5,k=7 → 等价于k=2)
k %= len;
// 步骤1:翻转整个数组
reverse(nums, 0, len - 1);
// 步骤2:翻转前 k 个元素
reverse(nums, 0, k - 1);
// 步骤3:翻转剩余的 len-k 个元素
reverse(nums, k, len - 1);
}
/**
* 辅助方法:双指针翻转数组的指定区间 [start, end]
* @param nums 待翻转的数组
* @param start 翻转起始索引(包含)
* @param end 翻转结束索引(包含)
*/
public void reverse(int[] nums, int start, int end) {
// 双指针向中间靠拢,交换对称位置的元素
while (start <= end) {
// 交换 nums[start] 和 nums[end]
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
// 左指针右移,右指针左移
start++;
end--;
}
}
}
8 除了自身以外数组的乘积
8.1 思路
核心逻辑:每个位置的结果 = 左侧所有数的乘积 × 右侧所有数的乘积,代码把这两步分开算,且复用answer数组减少空间。
空间优化:用结果数组存前缀,变量动态记录后缀,实现 O (1) 额外空间;
关键技巧:反向遍历 + 动态更新后缀乘积,是降低空间复杂度的核心;
8.2 实现代码
java
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length; // 步骤1:获取数组长度
int[] answer = new int[len]; // 步骤2:初始化结果数组
answer[0] = 1; // 步骤3:第一个元素的左侧无数字,乘积为1
// 步骤4:计算「左侧乘积」,存入answer数组
for (int i = 1; i < len; i++) {
answer[i] = answer[i - 1] * nums[i - 1];
}
int suffix = 1; // 步骤5:初始化右侧乘积为1(最后一个元素的右侧无数字)
// 步骤6:反向计算「右侧乘积」,并和左侧乘积相乘得到最终结果
for (int i = len - 1; i >= 0; i--) {
answer[i] *= suffix; // 左侧乘积 × 右侧乘积 = 最终结果
suffix *= nums[i]; // 更新右侧乘积(把当前数加入,供前一个位置使用)
}
return answer;
}
}
9 缺失的第一个正数(难)
9.1 思路
核心思路:原地哈希(数组下标当 "哈希键")
思路本质
我们要找的是「最小的缺失正整数」,范围一定在 [1, n+1](n 是数组长度):
如果数组包含 1~n 所有数,缺失的是 n+1;
否则缺失的是 1~n 中第一个没出现的数。
核心技巧:把数组本身当成哈希表,让数值为 x 的元素(1≤x≤n)放到下标为 x-1 的位置(比如数值 1 放到下标 0,数值 2 放到下标 1)。完成 "归位" 后,遍历数组:
若 nums[i] ≠ i+1,则 i+1 就是缺失的最小正整数;
若所有位置都满足 nums[i] = i+1,则缺失的是 n+1。
9.2 实现代码
java
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// 步骤1:将数值x(1≤x≤n)归位到下标x-1的位置
for (int i = 0; i < n; i++) {
// 循环归位:直到当前位置的数不在[1,n]范围,或已经归位正确
// 注意用while而非if,因为交换后新进来的数可能也需要归位
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
// 交换nums[i]和nums[nums[i]-1],让nums[i]归位
swap(nums, i, nums[i] - 1);
}
}
// 步骤2:遍历数组,找第一个未归位的位置
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1; // 缺失的最小正整数
}
}
// 步骤3:所有1~n都存在,缺失的是n+1
return n + 1;
}
// 辅助方法:交换数组中两个位置的元素
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}