hot 100 普通数组、矩阵专题

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;
    }
}
相关推荐
顺风尿一寸2 小时前
深入剖析 Linux 内核 TCP Poll 机制:等待、唤醒与同步
java·linux
新时代Java农民工2 小时前
刚安装好的IDEA在插件库里面搜索不到要安装的插件
java·ide
智者知已应修善业2 小时前
【输入矩阵将其按副对角线交换后输出】2024-11-27
c语言·c++·经验分享·笔记·线性代数·算法·矩阵
好家伙VCC2 小时前
# Deno框架实战:从零搭建一个安全、高效的Node.js替代项目 在现代
java·python·安全·node.js
17(无规则自律)2 小时前
C++ 链表修炼指南
数据结构·c++·算法·leetcode·链表
zjjsctcdl2 小时前
Spring之FactoryBean详解
java·后端·spring
二十雨辰2 小时前
[Java]RuoYi帝可得-2文件储存
java·开发语言
·中年程序渣·2 小时前
Spring AI Alibaba入门学习(三)
java·学习·spring
毅炼2 小时前
JVM常见问题总结(2)
java·jvm·mvc