LeetCode 热题 100 : 普通数组

1. 最大子数组和

题目描述

给你一个整数数组 nums,请你找出数组中连续子数组(最少包含一个元素)的最大和

解题思路

💡 核心技巧:动态规划 + 状态压缩

与滑动窗口不同,本题不能用双指针,因为数组包含负数,子数组和不具有单调性。

关键洞察

  • 定义 dp[i] 为以 nums[i] 结尾的最大子数组和
  • 状态转移方程:dp[i] = max(nums[i], dp[i-1] + nums[i])
  • 无需存储整个 dp 数组,用 sum 变量实时更新

为什么有效 :当 sum 变为负数时,说明以当前元素结尾的子数组和不如从下一个元素开始,因此重置 sum = num

代码实现

javascript 复制代码
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let ans = -Infinity;
    let sum = 0;
    
    for (let num of nums) {
        // 选择:从当前元素开始,还是延续之前子数组
        sum = Math.max(sum + num, num);
        ans = Math.max(ans, sum);
    }
    
    return ans;
};

复杂度分析

  • 时间复杂度:O(n),单次遍历
  • 空间复杂度:O(1),仅用常数额外空间

2. 合并区间

题目描述

给定一组区间,合并所有重叠的区间。

解题思路

💡 核心技巧:排序 + 一次遍历

区间合并的关键在于排序,让重叠区间在遍历中自然出现。

关键洞察

  1. 按区间起始位置排序(intervals.sort((a, b) => a[0] - b[0])
  2. 初始化结果数组,放入第一个区间
  3. 遍历后续区间:
    • 若当前区间与结果数组最后一个区间重叠(current[0] <= last[1]),则合并
    • 否则,将当前区间加入结果

为什么排序是关键:排序后,重叠区间必然相邻,避免了 O(n²) 的暴力检查。

代码实现

javascript 复制代码
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    if (intervals.length === 0) return [];
    
    // 按起始位置升序排序
    intervals.sort((a, b) => a[0] - b[0]);
    
    const merged = [intervals[0]];
    
    for (let i = 1; i < intervals.length; i++) {
        const current = intervals[i];
        const lastMerged = merged[merged.length - 1];
        
        // 检查重叠:当前区间起始 <= 上一个合并区间的结束
        if (current[0] <= lastMerged[1]) {
            // 合并:更新结束位置为两者的最大值
            lastMerged[1] = Math.max(lastMerged[1], current[1]);
        } else {
            // 无重叠,直接加入
            merged.push(current);
        }
    }
    
    return merged;
};

复杂度分析

  • 时间复杂度:O(n log n),排序占主导
  • 空间复杂度:O(n),结果数组最多存储 n 个区间

3. 旋转数组

题目描述

给定一个整数数组 nums 和一个整数 k将数组向右旋转 k 次

解题思路

💡 核心技巧:三次反转

无需额外空间,仅通过数组反转实现旋转。

关键洞察

  1. 整体反转:[1,2,3,4,5][5,4,3,2,1]
  2. 反转前 k 个元素:[5,4][4,5][4,5,3,2,1]
  3. 反转剩余元素:[3,2,1][1,2,3][4,5,1,2,3]

代码实现

javascript 复制代码
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {
    const n = nums.length;
    k %= n; // 避免无效旋转
    if (k === 0) return;

    const reverse = (start, end) => {
        while (start < end) {
            [nums[start], nums[end]] = [nums[end], nums[start]];
            start++;
            end--;
        }
    };

    reverse(0, n - 1);       // 整体反转
    reverse(0, k - 1);       // 前 k 个反转
    reverse(k, n - 1);       // 后面部分反转
};

复杂度分析

  • 时间复杂度:O(n),三次反转各 O(n)
  • 空间复杂度:O(1),原地操作

4. 除自身以外的数组乘积

题目描述

给你一个整数数组 nums,返回一个数组 answer,其中 answer[i]numsnums[i] 以外所有元素的乘积

解题思路

💡 核心技巧:前缀乘积 × 后缀乘积

关键洞察

  • ans[i] = (nums[0] * ... * nums[i-1]) * (nums[i+1] * ... * nums[n-1])
  • pre 数组存前缀乘积,suf 数组存后缀乘积

代码实现

javascript 复制代码
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
    const n = nums.length;
    const ans = new Array(n);
    
    // 计算前缀乘积(ans[0] = 1)
    let pre = 1;
    for (let i = 0; i < n; i++) {
        ans[i] = pre;
        pre *= nums[i];
    }
    
    // 计算后缀乘积并累乘到 ans
    let suf = 1;
    for (let i = n - 1; i >= 0; i--) {
        ans[i] *= suf;
        suf *= nums[i];
    }
    
    return ans;
};

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

空间优化 :直接在 ans 数组中同时存前缀和后缀,可以使空间复杂度降到O(1)


5. 第一个缺失的正数

题目描述

给你一个未排序的整数数组 nums,找出其中最小的缺失的正整数

解题思路

💡 核心技巧:原地哈希

利用数组下标作为哈希表,将正整数放到对应位置。

关键洞察

  1. 有效范围:1 ~ n(n 为数组长度)
  2. 交换操作:将 nums[i] 放到 nums[nums[i]-1] 位置
  3. 最后遍历数组,第一个 nums[i] != i+1 的位置即为答案

为什么高效:O(n) 时间,O(1) 空间,无需额外哈希表。

代码实现

javascript 复制代码
/**
 * @param {number[]} nums
 * @return {number}
 */
var firstMissingPositive = function(nums) {
    const n = nums.length;
    
    // 第一步:将正整数放到正确的位置(1~n)
    for (let i = 0; i < n; i++) {
        // 当前元素在1~n范围内,且不在正确位置
        while (nums[i] > 0 && nums[i] <= n && nums[i] !== nums[nums[i] - 1]) {
            // 交换到目标位置
            const j = nums[i] - 1;
            [nums[i], nums[j]] = [nums[j], nums[i]];
        }
    }
    
    // 第二步:遍历数组,找到第一个不符合条件的位置
    for (let i = 0; i < n; i++) {
        if (nums[i] !== i + 1) {
            return i + 1;
        }
    }
    
    // 如果全部符合,返回n+1
    return n + 1;
};

复杂度分析

  • 时间复杂度:O(n),两次遍历,交换操作总次数 ≤ n
  • 空间复杂度:O(1),原地操作

总结对比

题目 核心技巧 关键优化
最大子数组和 动态规划 + 状态压缩 无需额外数组
合并区间 排序 + 一次遍历 排序后重叠区间自然相邻
旋转数组 三次反转 无额外空间
除自身以外的数组乘积 前缀/后缀乘积 用结果数组存储中间结果
第一个缺失的正数 原地哈希 利用数组下标作为哈希表

希望这篇解析对你有帮助!如果你喜欢这类结构清晰、对比鲜明的算法总结,欢迎关注我的 LeetCode 热题 100 系列 👋

相关推荐
B325帅猫-量子前沿技术研究所10 分钟前
PSD和FFT的关系
人工智能·算法
闻缺陷则喜何志丹12 分钟前
【排序】P6149 [USACO20FEB] Triangles S|普及+
c++·算法·排序·洛谷
avocado_green18 分钟前
【LeetCode】90. 子集 II
算法·leetcode
tankeven24 分钟前
HJ178 【模板】双指针
c++·算法
君义_noip33 分钟前
信息学奥赛一本通 4131:【GESP2506六级】学习小组 | 洛谷 P13015 [GESP202506 六级] 学习小组
算法·动态规划·gesp·信息学奥赛
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 72. 编辑距离 | C++ 经典 DP 增删改状态转移
c++·算法·leetcode
穿条秋裤到处跑1 小时前
每日一道leetcode(2026.04.16):距离最小相等元素查询
算法·leetcode·职场和发展
XY_墨莲伊2 小时前
【实战项目】基于B/S结构Flask+Folium技术的出租车轨迹可视化分析系统(文末含完整源代码)
开发语言·后端·python·算法·机器学习·flask
小雅痞2 小时前
[Java][Leetcode simple] 1. 两数之和
java·算法·leetcode
somi72 小时前
ARM-驱动-09-LCD FrameBuffer
arm开发·驱动开发·算法·自用