LeetCode Hot 100 题解 · 普通数组篇

LeetCode Hot 100 题解 · 普通数组篇

  1. 本专题收录 LeetCode Hot 100 中所有 普通数组 相关题目。
  2. 每道题提供最直接、最容易理解的解题思路,包含详细注释的代码实现,方便笔试和面试复习。

📑 目录

题号 题目 难度 核心思路
53 53.最大子数组和 🟠中等 dp[i] = 以 i 结尾的最大子数组和 :转移只有两种------接上前面的(dp[i-1] + x)或自己另起炉灶(x),取大者。前面的和是负的就果断舍弃 (只会拖累)。答案取所有 dp[i] 的最大值,不是 dp[n-1]。空间压掉就是"贪心"写法。
56 56.合并区间 🟠中等 按左端点排序 + 一次扫描 :排序后只有右端点需要合并判断------当前区间的左端点若 ≤ 上一结果的右端点,二者重叠,右端点取 max 即可(覆盖型相交,不是首尾相接);否则区间彻底分开,上一结果安全出栈。
189 189.轮转数组 🟠中等 三次翻转 :整体翻转 → 翻前 k 个 → 翻后 n-k 个。右移 k 位本质是把尾部 k 个数和前部 n-k 个数换块 ,而"换块"恰好等价于"整体翻 + 各自翻"。原地 O(1) 空间,绕开 O(n) 额外数组。
238 238.除自身以外数组的乘积 🟠中等 前后缀分解res[i] = 左边所有数的积 × 右边所有数的积。从左扫一遍顺手把前缀积写进 res,再从右扫一遍用一个变量 suf 滚动携带后缀积,乘进去。全程不出现除法 (0 会把除法炸掉),O(1) 额外空间。
41 41.缺失的第一个正数 🔴困难 原地哈希(把数组自己当哈希表) :长度 n 的数组,答案必在 [1, n+1] 里------1..n 各回各家各找各妈(把值 v 换到下标 v-1),谁家没主人谁就是缺失者,全齐则是 n+1答案范围被数组长度框死 是突破口,排序 O(n log n)、开哈希表 O(n) 空间都因此能被原地置换替代。

53.最大子数组和

题目链接53.最大子数组和

题目描述 :给你一个整数数组 nums元素可正可负可为 0 ),找出和最大的连续子数组 (至少含一个元素),返回这个最大和。比如 [-2,1,-3,4,-1,2,1,-5,4] 答案是 6(子数组 [4,-1,2,1])。


思路一:动态规划(以 i 结尾的最大和,负前缀果断舍弃)

连续子数组的题目可以考虑dp

核心思想

  1. 暴力枚举所有子数组是 O(n²)。观察问题特征:想知道"以 i 结尾的最优解",只需要知道"以 i-1 结尾的最优解"------多走一步只会多一个决策 ,这是线性 DP 的标准信号。
  2. 定义 dp[i] = i 结尾 的最大子数组和(注意这个"以 i 结尾"的限定,它是转移能成立的关键)。转移只有两个选项:
    • 接着前面的延伸:dp[i-1] + nums[i]
    • 前面的和是负的,接了只亏不赚,自己另起炉灶nums[i]
    • 取两者较大值------本质就是 dp[i-1] < 0 时舍弃前缀。
  3. 答案是 max(dp[0..n-1])不是 dp[n-1]:最优子数组可能结束在任何位置,"以 i 结尾"只是求解手段,不是答案的形状。
  4. dp[i] 只依赖 dp[i-1],一个滚动变量就够,数组不用开。

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,单趟扫描。

空间复杂度 : O ( 1 ) O(1) O(1),滚动变量代替 dp 数组。

O(n)空间写法:

java 复制代码
class Solution {
    public int maxSubArray(int[] nums) {
    // 类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
        int ans = Integer.MIN_VALUE;
        int n = nums.length; 
        // dp[i]表示nums中以nums[i]结尾的最大子序和
        int[] dp = new int[n];
        dp[0] = nums[0];
        ans = dp[0];
        for (int i = 1; i < n; ++i) {
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); // 选或不选前面所有
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}

O(1)空间写法:

java 复制代码
class Solution {
    public int maxSubArray(int[] nums) {
        // pre: 以当前元素结尾的最大子数组和(滚动替代 dp 数组)
        int pre = 0;
        int ans = Integer.MIN_VALUE; // 元素可能全为负,不能用 0 初始化
        for (int x : nums) {
            pre = Math.max(pre + x, x); // 接上前缀 or 另起炉灶,前缀为负自然被舍弃
            ans = Math.max(ans, pre);   // 最优子数组可能结束在任何位置,每步都结算
        }
        return ans;
    }
}

同一个转移换身衣服就是贪心写法:sum += x; if (sum < 0) sum = 0; 每步用 sum 更新答案------sum < 0 就清零,等价于 dp 里"负前缀舍弃"。两版完全等价,面试讲 dp 版,因果链最清楚。

贪心C++写法:

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        // 类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
        int result = INT_MIN;
        int numsSize = int(nums.size());
        int sum = 0;
        for (int i = 0; i < numsSize; i++) {
            sum += nums[i];
            result = max(result, sum);
            // 如果sum < 0,重新开始找子序串
            if (sum < 0) {
                sum = 0;
            }
        }

        return result;
    }
};

思路二:分治(最大子段和拆成三块:左半、右半、横跨中线)

核心思想

  1. 递归地想:[left, right] 的最大子数组和只有三种可能------完全落在左半 [left, mid]、完全落在右半 [mid+1, right]、横跨中线 mid。前两种递归求解,第三种单独算,三者取最大。
  2. 横跨中线的情况最讲究:这样的子数组一定同时包含 nums[mid]nums[mid+1] ,所以它 = 左半边"以 mid 结尾"的最大子数组和 + 右半边"以 mid+1 开头"的最大子数组和------两个方向各扫一遍,起点终点被钉死,问题退化成两趟简单累加。
  3. "以 i 结尾/开头"这个限定和思路一的 dp 是同一个技巧:把自由端点钉死,最优解才可增量计算
  4. 单题用它杀鸡用牛刀(常数大、代码长),但它是线段树维护区间最大子段和的原型------面试当加分项提一句。

时间复杂度 : O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n 为数组长度,T(n) = 2T(n/2) + O(n)

空间复杂度 : O ( log ⁡ n ) O(\log n) O(logn),递归栈深度。

java 复制代码
public class Solution {

    public int maxSubArray(int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        return maxSubArraySum(nums, 0, len - 1);
    }

    private int maxCrossingSum(int[] nums, int left, int mid, int right) {
        // 一定会包含 nums[mid] 这个元素
        int sum = 0;
        int leftSum = Integer.MIN_VALUE;
        // 左半边包含 nums[mid] 元素,最多可以到什么地方
        // 走到最边界,看看最值是什么
        // 计算以 mid 结尾的最大的子数组的和
        for (int i = mid; i >= left; i--) {
            sum += nums[i];
            if (sum > leftSum) {
                leftSum = sum;
            }
        }
        sum = 0;
        int rightSum = Integer.MIN_VALUE;
        // 右半边不包含 nums[mid] 元素,最多可以到什么地方
        // 计算以 mid+1 开始的最大的子数组的和
        for (int i = mid + 1; i <= right; i++) {
            sum += nums[i];
            if (sum > rightSum) {
                rightSum = sum;
            }
        }
        return leftSum + rightSum;
    }

    private int maxSubArraySum(int[] nums, int left, int right) {
        if (left == right) {
            return nums[left];
        }
        int mid = left + (right - left) / 2;
        return max3(maxSubArraySum(nums, left, mid),
                maxSubArraySum(nums, mid + 1, right),
                maxCrossingSum(nums, left, mid, right));
    }

    private int max3(int num1, int num2, int num3) {
        return Math.max(num1, Math.max(num2, num3));
    }
}

56.合并区间

题目链接56.合并区间

题目描述 :给你一堆区间 intervals[i] = [start, end],把所有有重叠的区间合并成一个 ,返回不重叠的区间列表。比如 [[1,3],[2,6],[8,10],[15,18]][[1,6],[8,10],[15,18]]


思路一:按左端点排序 + 一次扫描(只有右端点需要判断)

核心思想

  1. 区间乱序时,一个新区间可能和前面任何一个重叠,没法增量处理。按左端点升序排序 之后局面完全变了:扫到区间 i 时,它前面所有区间的左端点都 ≤ 它的左端点------它能重叠的,只可能是"当前合并结果"的最后一个区间,更早的早就定型了。
  2. 于是一次扫描即可:当前区间 [s, e] 和结果尾 [ts, te] 比:
    • s <= te:重叠(注意是 ,端点相接如 [1,4][4,5] 也要合并),尾区间右端点扩张成 max(te, e)------取 max 是因为新区间可能整个被尾区间包住 (如 [1,10] 后跟 [2,3]),右端点不能变小;
    • s > te:彻底分开,尾区间出结果,[s, e] 成为新的尾。
  3. 每个区间进出结果各一次,加上排序就是全部开销。

时间复杂度 : O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n 为区间数,排序占大头;扫描本身 O ( n ) O(n) O(n)。

空间复杂度 : O ( log ⁡ n ) O(\log n) O(logn),排序的栈空间(不计返回值)。

java写法:

java 复制代码
class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals.length == 0) {
            return new int[0][2];
        }
        // 按左端点升序:保证当前区间只可能和结果尾重叠
        Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] interval1, int[] interval2) {
                return interval1[0] - interval2[0];
            }
        });
        List<int[]> ans = new ArrayList<int[]>();
        for (int i = 0; i < intervals.length; ++i) {
            // 彻底分开,新区间
            if (ans.size() == 0 || intervals[i][0] > ans.get(ans.size() - 1)[1]) {
                ans.add(new int[] { intervals[i][0], intervals[i][1] });
            } else {
                // 与结果尾重叠:只扩张右端点,取 max 防止新区间被尾完全包住
                ans.get(ans.size() - 1)[1] = Math.max(ans.get(ans.size() - 1)[1], intervals[i][1]);
            }
        }
        return ans.toArray(new int[ans.size()][]);
        // return ans.toArray(new int[0][]);
    }
}

ans.toArray(new int[0][]) 为什么对:toArray 的参数不是用来装结果的,只用来指明返回类型 (不传会返回 Object[],强转 int[][] 编译都过不了)。传入数组够装就直接用它;不够装(长度 0 必然不够)就由 ArrayList 自己 new 一个长度正确的 int[][] 装进去返回。所以 new int[0][]new int[res.size()][] 结果完全一样,前者还省去先问 size() 再分配这一步,是惯用写法。
高频出错点:合并条件写 当前区间左端点 < 待合并区间右端点(漏掉端点相接)或右端点直接赋 当前区间右端点 都是错误的。记住两条: 才算重叠;右端点永远取 max


189.轮转数组

题目链接189.轮转数组

题目描述 :把数组 nums 整体向右轮转 k 个位置 (尾部绕回头部),原地 修改。比如 [1,2,3,4,5,6,7]k = 3[5,6,7,1,2,3,4]。要求空间 O(1)------一个额外数组直接拷贝 O(n) 空间的写法在此题是被点名禁止的。


思路一:三次翻转(原地换块)

线代知识一样,类似矩阵翻转,或者离散数学里面的布尔矩阵的相关运算定律。

核心思想

  1. 右移 k(先 k %= n)后,数组变成 [n-k .. n-1] 在前、[0 .. n-k-1] 在后------本质是把尾部 k 个数和前部 n-k 个数换了块,两块内部顺序不变。
  2. 直接换块要 O(n) 空间。巧的是翻转恰好能完成换块 :先整体翻转(两块换位了,但块内是逆序的),再分别把两块各自翻转回来(块内顺序复原)。三步都是原地交换,O(1) 空间。
  3. [1,2,3,4,5,6,7]k = 3 为例:
    • 整体翻:[7,6,5,4,3,2,1]
    • 翻前 k=3 个:[5,6,7,4,3,2,1]
    • 翻后 n-k=4 个:[5,6,7,1,2,3,4]

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,三次翻转共约 2n 次交换。

空间复杂度 : O ( 1 ) O(1) O(1),纯原地交换。

java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n; // k 可能大于 n,轮转 n 次等于没转
        reverse(nums, 0, n - 1); // 1. 整体翻转:两块换位(块内暂为逆序)
        reverse(nums, 0, k - 1); // 2. 翻前 k 个:恢复前块内部顺序
        reverse(nums, k, n - 1); // 3. 翻后 n-k 个:恢复后块内部顺序
    }

    // 原地翻转 [l, r]
    private void reverse(int[] nums, int l, int r) {
        while (l < r) {
            int tmp = nums[l];
            nums[l] = nums[r];
            nums[r] = tmp;
            l++;
            r--;
        }
    }
}

思路二:环状替换(每个数直接跳到终点)

核心思想 :从下标 start 出发,把 nums[start] 直接送去它轮转后的终点 (start + k) % n,被顶出来的数再送去 的终点......如此接力,直到绕回 start,一圈完成。一圈不一定覆盖所有下标(gcd(n, k) 圈才覆盖完),外层用一个 count 数搬动了几个数,搬满 n 个就收工 。纯原地 O(1) 空间,但下标接力容易绕晕,笔试不如翻转版稳。

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,每个下标恰好被访问一次。

空间复杂度 : O ( 1 ) O(1) O(1)。

java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n;
        if (k == 0) {
            return; // 没得转,gcd 也不需要,直接收工
        }
        int count = 0; // 已就位的元素个数,搬满 n 个结束(防止 gcd 圈死循环)
        for (int start = 0; count < n; start++) { // 一圈回不到 start 就换新的起点再来一圈
            int cur = start;
            int prev = nums[start];
            do {
                int next = (cur + k) % n; // prev 的最终归宿
                int tmp = nums[next];     // 先取出被顶出来的数
                nums[next] = prev;        // 送 prev 就位
                prev = tmp;               // 被顶出来的继续接力
                cur = next;
                count++;
            } while (cur != start); // 绕回起点,这一圈结束
        }
    }
}

两版怎么选:翻转版胜在步骤可复述 (翻、翻、翻三步走完),环状替换胜在每一步都是"直接放对位置"。面试先讲翻转版,讲得清;环状替换当"还有个原地一步到位的做法"提一嘴。

思路三:新开数组

思路二:环状替换(每个数直接跳到终点)

核心思想:对于这种移位类的题目,增加长度或者新开数组是最直接的方法,可以减少思考空间。

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,每个下标恰好被访问一次。

空间复杂度 : O ( n ) O(n) O(n)。

java版写法:

java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        int[] newArr = new int[n];
        for (int i = 0; i < n; ++i) {
            newArr[(i + k) % n] = nums[i];
        }
        System.arraycopy(newArr, 0, nums, 0, n);
    }
}

C++版写法:

cpp 复制代码
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k % n;
        nums.resize(n + k);
        for (int i = n - 1; i >= 0; --i) { // 整体右移
            nums[i + k] = nums[i];
        }
        for (int i = 0; i < k;
             ++i) { // 超出的元素赋值(超出的元素必然是[n-k,n-1],共k个)
            nums[i] = nums[i + n];
        }
        nums.resize(n);
    }
};

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

题目链接238.除自身以外数组的乘积

题目描述 :给你数组 nums,返回 res,其中 res[i] 等于 numsnums[i] 之外所有元素的乘积 。要求不使用除法O(n) 时间。比如 [1,2,3,4][24,12,8,6]。数组可能含 0,正是"不让用除法"的原因------用总积除会遇到 0 除或除 0。


思路一:前后缀分解(左边乘积 × 右边乘积)

核心思想

  1. 除自身以外的乘积天然拆成两半:ans[i] = (i 左边所有数的积) × (i 右边所有数的积)------i 自己被左右两半精确地排除在外。
  2. 左半用一个从左到右的扫描算前缀积,右半对称。关键的空间优化:前缀积直接写进 ans ,第二趟从右往左扫时不再开数组,用一个变量 suf 滚动携带"右边所有数的积",边走边乘进 ans。额外空间压到 O(1)res 不算)。
  3. 两趟都是纯乘法,全程不出现除法 ,0 不 0 的完全不关心------每个 res[i] 都是从头乘出来的,没有"总积 ÷ 自己"这种会被 0 炸掉的操作。

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,两趟扫描。

空间复杂度 : O ( 1 ) O(1) O(1),除返回数组外只用了 suf 一个变量。

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        ans[0] = 1; // ans[0] 左边没数,左边积是 1(乘法单位元)
        // 第一趟从左到右:ans[i] = nums[0..i-1] 的积(i 左边的全部)
        for (int i = 1; i < n; i++) {
            ans[i] = ans[i - 1] * nums[i - 1]; // 前缀积递推,i 自己故意不乘进去
        }
        // 第二趟从右到左:suf = nums[i+1..n-1] 的积(i 右边的全部),滚动维护不开数组
        int suf = 1;
        for (int i = n - 1; i >= 0; i--) {
            ans[i] *= suf; // 左半 × 右半,nums[i] 两边都没碰它
            suf *= nums[i]; // 把 nums[i] 计入后缀,供下一轮(i-1)使用
        }
        return ans;
    }
}

这里借用k神题解里面的图会很好理解:k神题解

前后缀分解是个通用套路,Hot 100 里反复出现:42.接雨水 思路二的 leftMax/rightMax 是它(一侧最大值版),这题是"一侧乘积"版。凡是 res[i] 只由"i 左边的聚合"和"i 右边的聚合"拼出来、又不能包含 i 自己的题,先想前后缀


41.缺失的第一个正数

题目链接41.缺失的第一个正数

题目描述 :给你一个未排序的整数数组 nums(元素可正可负可为 0,可能有重复),找出没有出现在数组中的最小正整数 。要求 O(n) 时间、O(1) 空间。比如 [1,2,0] 答案是 3[3,4,-1,1] 答案是 1[7,8,9,11,12] 答案是 1O(1) 空间这条限制是题眼:不许开哈希表、不许排序,逼你把数组自己当哈希表用


思路一:原地哈希(置换回家,谁家没主人谁缺失)

核心思想

  1. 先把答案的搜索范围框死:长度为 n 的数组,答案一定落在 [1, n+1]n 个位置最多装下 1..nn 个正数,装齐了答案才是 n+1;只要 1..n 里缺一个,答案就是那个缺的。于是 ≤ 0> n 的数全是无关人员,看都不用看。
  2. 既不能开哈希表又想知道"1..n 谁没来",就让数组自己当哈希表 :约定下标 i 的家放值 i+1 。遍历每个位置,只要 nums[i] 是合法住户(在 [1, n] 内)且还没到家(nums[nums[i]-1] != nums[i]),就把它换到它家去;换回来的新数如果是合法住户也没到家,接着送,直到当前位置来的无关人员/已就位者为止。
  3. 这样每个数最多被送回家一次(到家后 nums[nums[i]-1] == nums[i] 永远成立,不会再换),总交换次数 ≤ n,均摊 O(n)判"到家"用值相等而不是下标相等,天然跳过重复值------后来的重复住户发现家里已有同名主人,就不折腾了。
  4. 全部归位后扫一遍:第一个 nums[i] != i+1 的位置,i+1 就是答案;全对上了说明 1..n 齐了,答案是 n+1

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度------内层 while 的总交换次数不超过 n(每个值最多被送回家一次),均摊到外层每轮 O(1)

空间复杂度 : O ( 1 ) O(1) O(1),全程原地交换,数组自己就是哈希表。

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        //【不变量:处理完下标 i 后,原始数组前 i+1 个位置上出现过的、值在 [1,n] 内的数 v 都已在家(nums[v-1] == v);且到家永久成立------只有 v 自己会看 v-1 这个家,值相等判重会拒绝一切打扰】
        for (int i = 0; i < n; i++) {
            // nums[i] 是合法住户(值在 [1,n])且它家那位不是它自己,就送它回家
            // 值相等判重:重复值发现家里已有同名主人就停手,避免死循环
            while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                int j = nums[i] - 1;          // nums[i] 的家
                int tmp = nums[j];
                nums[j] = nums[i];            // 送 nums[i] 回家
                nums[i] = tmp;                // 换回来的数留在 i 处,下一轮继续送
            }
        }
        // 找第一个下标与住户编号不匹配的就是所要找的数
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1) { // 下标 i 的家(该放 i+1)空着
                return i + 1;       // 第一个空的家,主人就是缺失的最小正数
            }
        }
        return n + 1; // 1..n 全齐,只能往 n+1 找
    }
}

高频出错点:while 条件若写成 nums[nums[i]-1] != nums[i] 之外的下标判断(比如记 i 是否处理过),遇到重复值会死循环 ------[1,1] 里第二个 1 永远想回家但家里已有 1。判"到家"必须用值相等 ,重复值停在原地即可。另外先算 j = nums[i] - 1 存下来再交换:先动 nums[i] 再用 nums[i]-1 当下标,取到的已是换进来的新数。

思路二:打标记(正负号当 BitSet 用)

核心思想 :同样吃"1..n 框定答案范围"这个洞察,但不动粗换位置,改就地做标记

  • 第一遍把无关人员(≤ 0)统一改成 n+1(清场);
  • 第二遍遇到值 v ∈ [1, n],就把下标 v-1 上的数标成负数 ------"v 来过了";
  • 第三遍找第一个还是正数的下标 ii+1 就是缺失者。用正负号当 bit、下标当桶,数组身兼数值和标记位两职。比置换好想一点,但要扫三遍、且改写原始数据的方式更粗暴。

时间复杂度 : O ( n ) O(n) O(n),其中 n 为数组长度,三趟扫描。

空间复杂度 : O ( 1 ) O(1) O(1),原地打标记。

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
        // 第一步:预处理,把非正整数换成n+1(超出范围,不影响标记)
            if (nums[i] <= 0) {
                nums[i] = n + 1; // 清场:无关人员统一改成 n+1,后面只会对 [1,n] 打标记
            }
        }
        // 第二步:标记存在性(逻辑归位)
        for (int i = 0; i < n; i++) {
            int v = Math.abs(nums[i]); // 取绝对值:标记可能已被之前的操作改成负数【补上缺失的取绝对值!】
            if (v <= n) { // 只处理1~n
            // 把v对应的位置标记为负数,表示v存在(逻辑归位)
                nums[v - 1] = -Math.abs(nums[v - 1]); // 把 v 对应的桶标成负:"v 来过了"
            }
        }
        // 第三步:找第一个未标记的位置(正数)
        for (int i = 0; i < n; i++) {
            if (nums[i] > 0) { // 桶 i 没被打过标记 → i+1 从没出现过
                return i + 1;
            }
        }
        // 所有1~len都存在,返回len+1
        return n + 1; // 1..n 全打过标记
    }
}

两版怎么选:置换版是这题的"官方灵魂"(一次把数送到位,扫完即答案),打标记版胜在不用处理换回来的接力数 、循环都是平的。笔试求稳选打标记(不容易死循环),面试先讲置换("原地哈希"这个名头更响)。共同的前提洞察只有一个:长度 n 框死了答案 ∈ 1, n+1


总结:普通数组五题是五种"把 O(n²) 暴力压到 O(n) 的手法"------53.最大子数组和以 i 结尾的 dp (负前缀舍弃)把枚举终点变成转移一步;56.合并区间排序换有序性 (左端点有序后只需和结果尾比较,重叠判断从"任意对"变"相邻对");189.轮转数组翻转等价换块 (数学性质替代额外空间);238.除自身以外数组的乘积前后缀分解i 的答案 = 左聚合 × 右聚合,避开除法和包含自己的问题);41.缺失的第一个正数原地哈希 (长度 n 框死答案 ∈ [1, n+1],把数组当下标哈希表,置换回家或打标记找缺失)。核心一句话:数组题先找"只看一边/相邻就能决策"的结构,找到了 O(n) 就到手

相关推荐
hqyjzsb4 小时前
零 AI 项目经验,学 Python 转型 AI 的正确顺序是什么?
开发语言·人工智能·python·算法·职场和发展·数据挖掘·数据分析
辰烨chenye4 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
微功夫信息技术4 小时前
分层多智能体强化学习驱动的非急救转运公平 - 效率统一调度系统研究与实践
人工智能·学习·算法·动态规划
sanjiaomao3335 小时前
从论文公式到Python实现:用单元测试校验滑动平均算法
python·算法·单元测试
乐迪信息5 小时前
如何通过AI防爆摄像机精准判断船舶超速?
大数据·人工智能·算法·安全·目标跟踪
Interview Aid1125 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
大圣编蚕6 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
小玮看世界7 小时前
[Python]OD算法在OD实际运用转化参考清单
开发语言·python·算法
黎阳之光7 小时前
数字孪生赋能全域水网,实现水资源管控与节水降碳双向提升
人工智能·物联网·算法·安全·数字孪生