刷题:数组

题目:LeetCode 283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

要求:原地操作,不能拷贝额外数组。

示例

输入: [0,1,0,3,12] 输出: [1,3,12,0,0]

解法一:双指针(快慢指针)

思路

使用两个指针 slow 和 fast。

slow 指向下一个应该放置非零元素的位置(即已处理好的非零区间的右边界+1)。

fast 用来遍历数组,寻找非零元素。

当 fast 遇到非零元素时,就把它交换到 slow 的位置,然后 slow 前进一位。

这样遍历完后,slow 左边全是非零且保持原序,slow 右边自然全是零(无需再写零,因为交换已经把零换到了后面)。

图解

初始: [0,1,0,3,12] slow=0, fast=0

fast=0 → nums[0]==0,不交换,fast++

fast=1 → nums[1]=1 !=0 → 交换 nums[0]与nums[1] → [1,0,0,3,12],slow=1,fast++

fast=2 → 0,不交换,fast++

fast=3 → 3 !=0 → 交换 nums[1]与nums[3] → [1,3,0,0,12],slow=2,fast++

fast=4 → 12 !=0 → 交换 nums[2]与nums[4] → [1,3,12,0,0],slow=3,fast++ 结束

python 复制代码
def moveZeroes(nums):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow], nums[fast] = nums[fast], nums[slow]
            slow += 1
cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0;
        for(int fast = 0; fast < nums.size(); ++fast)
        {
            if(nums[fast] != 0)
            {
                swap(nums[fast],nums[slow]);
                slow++;
            }
        }
        
    }
};

复杂度:

时间:O(n),每个元素最多被访问一次。

空间:O(1),原地操作

题目:LeetCode 26. 删除有序数组中的重复项

给定一个 有序数组 nums原地 删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。
要求:原地修改,不能使用额外数组空间。元素的相对顺序应保持一致。

示例

复制代码
输入: [1,1,2]
输出: 2,且数组前两个元素为 [1,2](后面的元素无关紧要)

输入: [0,0,1,1,1,2,2,3,3,4]
输出: 5,且数组前五个元素为 [0,1,2,3,4]

解法:双指针(快慢指针)

思路

因为数组已经有序,重复的元素一定相邻。

  • 使用两个指针:slowfast
  • slow 指向 下一个要放置的不重复元素的位置(即已处理好的不重复区间的右边界 + 1)。
  • fast 用来遍历数组,寻找与 nums[slow-1] 不同的新元素。
  • nums[fast] != nums[slow-1] 时,说明遇到了新元素,将其复制到 slow 位置,然后 slow 前进一位。
  • 最终 slow 的值就是不重复元素的个数。

与"移动零"类似,但这里不是交换,而是覆盖(因为不需要保留重复值)。


图解

初始: [1,1,2],slow = 1(因为第一个元素默认保留),fast = 1

复制代码
[1, 1, 2]
 s     f
  • fast=1nums[1]=1nums[slow-1]=nums[0]=1,相等 → 重复,fast++

    [1, 1, 2]
    s f

  • fast=2nums[2]=2nums[slow-1]=1,不相等 → 将 nums[2] 复制到 nums[slow][1,2,2]slow++(slow=2),fast++ 结束

最终 slow=2,数组前两个元素为 [1,2]


代码

Python
python 复制代码
def removeDuplicates(nums):
    if not nums:
        return 0
    slow = 1  # 第一个元素默认保留,从第二个位置开始判断
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow - 1]:
            nums[slow] = nums[fast]
            slow += 1
    return slow
C++
cpp 复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.empty()) return 0;
        int slow = 1;
        for (int fast = 1; fast < nums.size(); ++fast) {
            if (nums[fast] != nums[slow - 1]) {
                nums[slow] = nums[fast];
                ++slow;
            }
        }
        return slow;
    }
};

复杂度分析

  • 时间复杂度:O(n),每个元素最多被访问一次。
  • 空间复杂度:O(1),只使用了常数个指针,原地修改。

题目:LeetCode 485. 最大连续 1 的个数

给定一个二进制数组 nums,计算其中最大连续 1 的个数。

要求:仅遍历一次,空间 O(1)。

示例

输入: [1,1,0,1,1,1]

输出: 3

解释:开头的两位和最后的三位都是连续 1,最大连续 1 的个数是 3。


解法一:单指针 + 计数器(一次遍历)

思路

用一个变量 count 记录当前连续 1 的个数,另一个变量 max_count 记录历史最大值。

遍历数组:

  • 如果遇到 1,count++
  • 如果遇到 0,则更新 max_count = max(max_count, count),然后将 count 重置为 0。
    遍历结束后,还需要再更新一次 max_count(防止数组以 1 结尾的情况)。

图解

初始: nums = [1,1,0,1,1,1], count=0, max_count=0

  • i=0, nums[0]=1 → count=1
  • i=1, nums[1]=1 → count=2
  • i=2, nums[2]=0 → max_count = max(0,2)=2, count=0
  • i=3, nums[3]=1 → count=1
  • i=4, nums[4]=1 → count=2
  • i=5, nums[5]=1 → count=3
  • 遍历结束 → max_count = max(2,3)=3

最终输出 3


代码实现

Python
python 复制代码
def findMaxConsecutiveOnes(nums):
    count = 0
    max_count = 0
    for num in nums:
        if num == 1:
            count += 1
        else:
            max_count = max(max_count, count)
            count = 0
    return max(max_count, count)
C++
cpp 复制代码
class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int count = 0;
        int max_count = 0;
        for (int num : nums) {
            if (num == 1) {
                count++;
            } else {
                max_count = max(max_count, count);
                count = 0;
            }
        }
        return max(max_count, count);
    }
};

复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组。
  • 空间复杂度:O(1),只使用了常数个变量。

题目:LeetCode 27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,仅使用 O(1) 额外空间。元素的顺序可以改变,不需要考虑数组中超出新长度后面的元素。

示例

输入: nums = [3,2,2,3], val = 3

输出: 2, 且 nums 前两个元素为 [2,2]
输入: nums = [0,1,2,2,3,0,4,2], val = 2

输出: 5, 且 nums 前五个元素为 [0,1,3,0,4](顺序任意)


解法一:双指针(快慢指针,保留非目标元素)

思路

使用两个指针 slowfast

  • fast 用于遍历数组,寻找不等于 val 的元素。
  • slow 指向下一个应该存放非 val 元素的位置。
    fast 遇到不等于 val 的元素时,将其复制到 slow 位置,然后 slow 前进一位。
    遍历结束后,slow 就是新数组的长度,且前 slow 个元素都是不等于 val 的值。

与「移动零」的区别:本题不需要交换,直接覆盖即可,因为多余的元素不需要保留。

图解

输入: nums = [3,2,2,3], val = 3

初始: slow = 0, fast = 0

  • fast=0 → nums[0]=3 等于 val,跳过,fast++
  • fast=1 → nums[1]=2 不等于 val → 赋值: nums[0]=2,slow=1,fast++
  • fast=2 → nums[2]=2 不等于 val → 赋值: nums[1]=2,slow=2,fast++
  • fast=3 → nums[3]=3 等于 val,跳过,fast++ 结束

数组变为 [2,2,2,3],前 slow=2 个元素为 [2,2],返回 2。


代码实现

Python
python 复制代码
def removeElement(nums, val):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != val:
            nums[slow] = nums[fast]
            slow += 1
    return slow
C++
cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0;
        for (int fast = 0; fast < nums.size(); ++fast) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
};

复杂度分析

  • 时间复杂度:O(n),每个元素最多访问一次。
  • 空间复杂度:O(1),原地操作,没有使用额外数组。

题目:LeetCode 88. 合并两个有序数组

给定两个有序整数数组 nums1nums2,将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

要求

  • nums1 的长度为 m + n,其中前 m 个元素是待合并的元素,后 n 个位置为 0 占位。
  • nums2 的长度为 n
  • 原地修改 nums1,不使用额外数组。

示例

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]


解法一:双指针(从后往前填充)

思路

因为 nums1 尾部有足够的空闲空间,我们可以从两个数组的有效尾部 开始比较,将较大的元素放到 nums1 的最后面,避免从前向后覆盖导致数据丢失。

使用三个指针:

  • p1 指向 nums1 有效元素的最后一个位置(m-1
  • p2 指向 nums2 的最后一个位置(n-1
  • p 指向 nums1 数组的最后一个位置(m+n-1

每次比较 nums1[p1]nums2[p2],将较大的元素放入 nums1[p],然后相应的指针向前移动一步。直到其中一个数组被遍历完。如果 nums2 还有剩余,再将其全部拷贝到 nums1 前面(nums1 的剩余部分已经有序且不需要移动)。

图解

初始:
nums1 = [1,2,3,0,0,0], p1=2, p=5
nums2 = [2,5,6], p2=2

  • 比较 nums1[2]=3 和 nums2[2]=6 → 6 大,放到 nums1[5] → [1,2,3,0,0,6],p2=1,p=4
  • 比较 3 和 5 → 5 大,放到 nums1[4] → [1,2,3,0,5,6],p2=0,p=3
  • 比较 3 和 2 → 3 大,放到 nums1[3] → [1,2,3,3,5,6],p1=1,p=2
  • 比较 nums1[1]=2 和 nums2[0]=2 → 2 相等,取 nums2[0] 放到 nums1[2](也可取 nums1 的,稳定)→ [1,2,2,3,5,6],p2=-1,结束
  • nums2 已空,剩余 nums1 前部自动有序。

代码实现

Python
python 复制代码
def merge(nums1, m, nums2, n):
    p1 = m - 1
    p2 = n - 1
    p = m + n - 1
    
    while p1 >= 0 and p2 >= 0:
        if nums1[p1] >= nums2[p2]:
            nums1[p] = nums1[p1]
            p1 -= 1
        else:
            nums1[p] = nums2[p2]
            p2 -= 1
        p -= 1
    
    # 如果 nums2 还有剩余,直接拷贝到 nums1 前面
    nums1[:p2+1] = nums2[:p2+1]
C++
cpp 复制代码
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1;
        int p2 = n - 1;
        int p = m + n - 1;
        
        while (p1 >= 0 && p2 >= 0) {
            if (nums1[p1] >= nums2[p2]) {
                nums1[p--] = nums1[p1--];
            } else {
                nums1[p--] = nums2[p2--];
            }
        }
        
        while (p2 >= 0) {
            nums1[p--] = nums2[p2--];
        }
    }
};

复杂度分析

  • 时间复杂度:O(m + n),每个元素最多被比较和移动一次。
  • 空间复杂度:O(1),只使用了常数个指针变量,原地修改。

题目:LeetCode 15. 三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a, b, c,使得 a + b + c = 0。请你找出所有和为 0 且不重复的三元组。

要求:答案中不可以包含重复的三元组。

示例

输入:nums = [-1,0,1,2,-1,-4]

输出:[[-1,-1,2],[-1,0,1]]

解释:
(-1) + 0 + 1 = 0
(-1) + (-1) + 2 = 0

注意:[-1,0,1][0,1,-1] 视为重复,只保留一个。


解法:排序 + 双指针

思路

  1. 排序:将数组升序排序,便于使用双指针和去重。
  2. 固定一个数 :遍历数组,将当前元素 nums[i] 作为三元组的第一个数,然后在 i+1 到末尾之间使用双指针 leftright 寻找另外两个数。
  3. 双指针查找
    • 计算当前和 sum = nums[i] + nums[left] + nums[right]
    • sum == 0:记录三元组,然后移动 leftright 并跳过重复元素。
    • sum < 0:说明需要增大,left++
    • sum > 0:说明需要减小,right--
  4. 去重
    • 外层循环中,如果 nums[i] == nums[i-1],跳过(避免重复的三元组)。
    • 内层找到解后,移动 leftright 时,跳过与之前相等的值。

注意 :当 nums[i] > 0 时可以直接终止循环,因为排序后后面的数都大于 0,和不可能为 0。

图解

nums = [-1, 0, 1, 2, -1, -4] 为例:

排序后[-4, -1, -1, 0, 1, 2]

复制代码
i=0, nums[i]=-4:
  left=1(-1), right=5(2): sum=-4+(-1)+2=-3 <0 → left++ (left=2)
  left=2(-1), right=5(2): sum=-4+(-1)+2=-3 <0 → left++ (left=3)
  left=3(0), right=5(2): sum=-4+0+2=-2 <0 → left++ (left=4)
  left=4(1), right=5(2): sum=-4+1+2=-1 <0 → left++ (left=5) 结束

i=1, nums[i]=-1 (注意跳过重复: nums[1]==nums[0]? 否):
  left=2(-1), right=5(2): sum=-1+(-1)+2=0 → 记录[-1,-1,2], left++(3), right--(4)
  left=3(0), right=4(1): sum=-1+0+1=0 → 记录[-1,0,1], left++(4), right--(3) 结束

i=2, nums[i]=-1 (与nums[1]相等, 跳过)

i=3, nums[i]=0:
  left=4(1), right=5(2): sum=0+1+2=3 >0 → right-- (right=4) 结束 (left>=right)

i=4, nums[i]=1 >0 终止循环

最终结果: [[-1,-1,2],[-1,0,1]]

代码实现

Python
python 复制代码
def threeSum(nums):
    nums.sort()
    res = []
    n = len(nums)
    for i in range(n):
        # 如果当前数大于0,后面不可能有三数之和为0
        if nums[i] > 0:
            break
        # 跳过重复的固定数
        if i > 0 and nums[i] == nums[i-1]:
            continue
        left, right = i + 1, n - 1
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            if total == 0:
                res.append([nums[i], nums[left], nums[right]])
                # 跳过重复的左指针
                while left < right and nums[left] == nums[left+1]:
                    left += 1
                # 跳过重复的右指针
                while left < right and nums[right] == nums[right-1]:
                    right -= 1
                left += 1
                right -= 1
            elif total < 0:
                left += 1
            else:
                right -= 1
    return res
C++
cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            if (nums[i] > 0) break;
            if (i > 0 && nums[i] == nums[i-1]) continue;
            int left = i + 1, right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    res.push_back({nums[i], nums[left], nums[right]});
                    while (left < right && nums[left] == nums[left+1]) left++;
                    while (left < right && nums[right] == nums[right-1]) right--;
                    left++;
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度 :O(n²)
    排序 O(n log n),外层循环 O(n),内层双指针遍历 O(n),总 O(n²)。
  • 空间复杂度 :O(1)(忽略存储答案的空间)
    仅使用了常数个额外变量。

题目:LeetCode 11. 盛最多水的容器

给定一个长度为 n 的整数数组 height。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:不能倾斜容器。

示例

输入:[1,8,6,2,5,4,8,3,7]

输出:49

解释:选择第 2 条线(高度 8,索引 1)和第 9 条线(高度 7,索引 8),容器宽度为 7,高度取较小者 7,面积 = 7 × 7 = 49。


解法:双指针(对撞指针)

思路

使用两个指针 leftright 分别指向数组的开头和结尾。

面积计算公式:area = min(height[left], height[right]) × (right - left)

每次计算当前面积,并更新最大面积。

然后移动高度较小的指针(因为移动高度较大的指针不可能得到更大面积------宽度减小,高度受限于较矮的一边)。

核心逻辑

  • height[left] < height[right],则 left++(移动左指针)
  • 否则 right--(移动右指针)

这样每次宽度减少 1,但有机会遇到更高的线,从而可能获得更大面积。

图解

height = [1,8,6,2,5,4,8,3,7] 为例:

复制代码
初始:left=0(1), right=8(7), width=8, area=min(1,7)*8=8, max=8
移动较矮的 left → left=1(8)

left=1(8), right=8(7), width=7, area=min(8,7)*7=49, max=49
移动较矮的 right → right=7(3)

left=1(8), right=7(3), width=6, area=min(8,3)*6=18, max=49
移动较矮的 right → right=6(8)

left=1(8), right=6(8), width=5, area=min(8,8)*5=40, max=49
移动任意(假设 left++)→ left=2(6)

left=2(6), right=6(8), width=4, area=min(6,8)*4=24, max=49
移动较矮的 left → left=3(2)

left=3(2), right=6(8), width=3, area=min(2,8)*3=6, max=49
移动较矮的 left → left=4(5)

left=4(5), right=6(8), width=2, area=min(5,8)*2=10, max=49
移动较矮的 left → left=5(4)

left=5(4), right=6(8), width=1, area=min(4,8)*1=4, max=49
移动较矮的 left → left=6, 结束

最终结果:49

代码实现

Python
python 复制代码
def maxArea(height):
    left, right = 0, len(height) - 1
    max_area = 0
    while left < right:
        width = right - left
        h = min(height[left], height[right])
        area = width * h
        max_area = max(max_area, area)
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1
    return max_area
C++
cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int max_area = 0;
        while (left < right) {
            int width = right - left;
            int h = min(height[left], height[right]);
            int area = width * h;
            max_area = max(max_area, area);
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return max_area;
    }
};

复杂度分析

  • 时间复杂度:O(n),只需一次遍历,两个指针总共移动 n 步。
  • 空间复杂度:O(1),只使用了常数个额外变量。

题目:LeetCode 53. 最大子数组和

给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

要求:实现时间复杂度 O(n) 的解法。

示例

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]

输出:6

解释:连续子数组 [4,-1,2,1] 的和最大,为 6。


解法一:动态规划(Kadane 算法)

思路

维护两个变量:

  • current_sum:以当前元素结尾的最大子数组和
  • max_sum:全局最大子数组和

递推公式

对于每个元素 num,要么从当前元素重新开始 ,要么加入之前的子数组

current_sum = max(num, current_sum + num)

然后更新 max_sum = max(max_sum, current_sum)

核心思想:如果之前的子数组和是负数,那么加上它会拖累当前元素,不如从当前元素重新开始。

图解

nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] 为例:

索引 元素 current_sum = max(元素, current_sum+元素) max_sum
0 -2 max(-2, -∞+(-2)) = -2 -2
1 1 max(1, -2+1=-1) = 1 max(-2,1)=1
2 -3 max(-3, 1+(-3)=-2) = -2 max(1,-2)=1
3 4 max(4, -2+4=2) = 4 max(1,4)=4
4 -1 max(-1, 4+(-1)=3) = 3 max(4,3)=4
5 2 max(2, 3+2=5) = 5 max(4,5)=5
6 1 max(1, 5+1=6) = 6 max(5,6)=6
7 -5 max(-5, 6+(-5)=1) = 1 max(6,1)=6
8 4 max(4, 1+4=5) = 5 max(6,5)=6

最终 max_sum = 6


代码实现

Python
python 复制代码
def maxSubArray(nums):
    current_sum = max_sum = nums[0]
    for num in nums[1:]:
        current_sum = max(num, current_sum + num)
        max_sum = max(max_sum, current_sum)
    return max_sum
C++
cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int current_sum = nums[0];
        int max_sum = nums[0];
        for (int i = 1; i < nums.size(); ++i) {
            current_sum = max(nums[i], current_sum + nums[i]);
            max_sum = max(max_sum, current_sum);
        }
        return max_sum;
    }
};

复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组。
  • 空间复杂度:O(1),只使用了常数个变量。

解法二:分治法(了解即可)

思路

将数组分成左右两半,最大子数组和可能出现在:

  1. 完全在左半部分
  2. 完全在右半部分
  3. 跨越中点(包含左半部分的后缀和右半部分的前缀)

递归求解,取三者最大值。

代码(Python)

python 复制代码
def maxSubArray(nums):
    def cross_sum(nums, left, right, mid):
        # 从中点向左扫描最大后缀和
        left_sum = float('-inf')
        cur = 0
        for i in range(mid, left - 1, -1):
            cur += nums[i]
            left_sum = max(left_sum, cur)
        # 从中点向右扫描最大前缀和
        right_sum = float('-inf')
        cur = 0
        for i in range(mid + 1, right + 1):
            cur += nums[i]
            right_sum = max(right_sum, cur)
        return left_sum + right_sum

    def helper(nums, left, right):
        if left == right:
            return nums[left]
        mid = (left + right) // 2
        left_max = helper(nums, left, mid)
        right_max = helper(nums, mid + 1, right)
        cross_max = cross_sum(nums, left, right, mid)
        return max(left_max, right_max, cross_max)

    return helper(nums, 0, len(nums) - 1)

复杂度:时间 O(n log n),空间 O(log n) 递归栈。

面试建议:优先掌握 Kadane 算法(动态规划),简洁高效。分治法作为扩展理解。

题目:LeetCode 56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [start_i, end_i]。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]

输出:[[1,6],[8,10],[15,18]]

解释:区间 [1,3][2,6] 重叠,合并为 [1,6]
输入:intervals = [[1,4],[4,5]]

输出:[[1,5]]

解释:区间 [1,4][4,5] 被视为重叠(边界相接也算重叠)。


解法:排序 + 合并

思路

  1. 排序 :将所有区间按照左端点(start)升序排序。如果左端点相同,可以按右端点升序(非必须)。
  2. 初始化 :将第一个区间加入结果列表 merged
  3. 遍历合并 :对于每个后续区间 current = [start, end]
    • 获取 merged 中最后一个区间 last = [last_start, last_end]
    • 如果 current 的左端点 start 大于 last_end,说明两个区间不重叠,直接将 current 加入结果。
    • 否则,说明重叠,更新 last_end = max(last_end, end)
  4. 返回merged 即为合并后的不重叠区间列表。

图解

intervals = [[1,3],[2,6],[8,10],[15,18]] 为例:

排序后(已有序):

复制代码
merged = []
加入第一个区间 → merged = [[1,3]]

遍历 [2,6]:
  last = [1,3], 2 <= 3 → 重叠 → 更新右端点为 max(3,6)=6 → merged = [[1,6]]

遍历 [8,10]:
  last = [1,6], 8 > 6 → 不重叠 → 加入 → merged = [[1,6],[8,10]]

遍历 [15,18]:
  last = [8,10], 15 > 10 → 不重叠 → 加入 → merged = [[1,6],[8,10],[15,18]]

代码实现

Python
python 复制代码
def merge(intervals):
    if not intervals:
        return []
    # 按照左端点排序
    intervals.sort(key=lambda x: x[0])
    merged = [intervals[0]]
    for start, end in intervals[1:]:
        last_start, last_end = merged[-1]
        if start > last_end:   # 无重叠
            merged.append([start, end])
        else:                  # 有重叠,合并右端点
            merged[-1][1] = max(last_end, end)
    return merged
C++
cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if (intervals.empty()) return {};
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> merged;
        merged.push_back(intervals[0]);
        for (int i = 1; i < intervals.size(); ++i) {
            int start = intervals[i][0];
            int end = intervals[i][1];
            int last_end = merged.back()[1];
            if (start > last_end) {
                merged.push_back({start, end});
            } else {
                merged.back()[1] = max(last_end, end);
            }
        }
        return merged;
    }
};

复杂度分析

  • 时间复杂度:O(n log n),主要开销在排序上。遍历合并只需 O(n)。
  • 空间复杂度 :O(log n) 或 O(n)
    排序所需的栈空间为 O(log n)(取决于排序算法),存储答案需要 O(n)(如果算输出空间则不计入额外空间)。

题目:LeetCode 238. 除自身以外数组的乘积

给你一个整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

要求:不要使用除法,且在 O(n) 时间复杂度内完成。

进阶:在 O(1) 额外空间内完成(输出数组不计入空间复杂度)。

示例

输入:nums = [1,2,3,4]

输出:[24,12,8,6]

解释:

answer[0] = 23 4 = 24

answer[1] = 13 4 = 12

answer[2] = 12 4 = 8

answer[3] = 123 = 6
输入:nums = [-1,1,0,-3,3]

输出:[0,0,9,0,0]


解法:前缀积 × 后缀积

思路

对于每个位置 i,其乘积等于 左边所有元素的乘积 × 右边所有元素的乘积

  1. 前缀积 :先遍历一次,计算每个位置 i 左侧所有元素的乘积,存入 answer 数组(此时 answer[i] 表示 nums[0] * ... * nums[i-1],左边界的乘积为 1)。
  2. 后缀积 :从右向左遍历,维护一个变量 right_product 记录右侧所有元素的乘积,将 answer[i] 乘以 right_product,然后更新 right_product *= nums[i]

空间优化 :使用输出数组 answer 存储前缀积,后缀积用一个变量滚动更新,从而实现 O(1) 额外空间(输出数组不计入)。

图解

nums = [1, 2, 3, 4] 为例:

第一步:计算前缀积(存入 answer)

复制代码
初始 answer = [1, 1, 1, 1]
遍历 i=0: answer[0]=1, 前缀积更新=1*1=1
i=1: answer[1]=1, 前缀积=1*2=2
i=2: answer[2]=2, 前缀积=2*3=6
i=3: answer[3]=6, 前缀积=6*4=24
结果 answer = [1, 1, 2, 6]

此时 answer[i] 表示 i 左边所有数的乘积。

第二步:从右向左乘以后缀积

复制代码
right_product = 1
i=3: answer[3] = 6 * 1 = 6, 更新 right_product = 1 * 4 = 4
i=2: answer[2] = 2 * 4 = 8, 更新 right_product = 4 * 3 = 12
i=1: answer[1] = 1 * 12 = 12, 更新 right_product = 12 * 2 = 24
i=0: answer[0] = 1 * 24 = 24, 更新 right_product = 24 * 1 = 24
最终 answer = [24, 12, 8, 6]

代码实现

Python
python 复制代码
def productExceptSelf(nums):
    n = len(nums)
    answer = [1] * n
    
    # 前缀积:answer[i] 存储 nums[0..i-1] 的乘积
    prefix = 1
    for i in range(n):
        answer[i] = prefix
        prefix *= nums[i]
    
    # 后缀积:从右向左乘
    suffix = 1
    for i in range(n-1, -1, -1):
        answer[i] *= suffix
        suffix *= nums[i]
    
    return answer
C++
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> answer(n, 1);
        
        int prefix = 1;
        for (int i = 0; i < n; ++i) {
            answer[i] = prefix;
            prefix *= nums[i];
        }
        
        int suffix = 1;
        for (int i = n-1; i >= 0; --i) {
            answer[i] *= suffix;
            suffix *= nums[i];
        }
        
        return answer;
    }
};

复杂度分析

  • 时间复杂度:O(n),遍历两次数组。
  • 空间复杂度 :O(1)(输出数组 answer 不计入额外空间)。
相关推荐
tankeven2 小时前
HJ182 画展布置
c++·算法
CS_Zero4 小时前
无人机路径规划算法——EGO-planner建模总结—— EGO-planner 论文笔记(一)
论文阅读·算法·无人机
杰梵4 小时前
聚酯切片DSC热分析应用报告
人工智能·算法
@BangBang4 小时前
leetcode (4): 连通域/岛屿问题
算法·leetcode·深度优先
Ulyanov4 小时前
像素迷宫:路径规划算法的可视化与实战
大数据·开发语言·python·算法
Mr_pyx4 小时前
【LeetCode Hot 100】 除自身以外数组的乘积(238题)多解法详解
算法·leetcode·职场和发展
Trouvaille ~4 小时前
零基础入门 LangChain 与 LangGraph(五):核心组件上篇——消息、提示词模板、少样本与输出解析
人工智能·算法·langchain·prompt·输入输出·ai应用·langgraph
MOON404☾5 小时前
Chapter 002. 线性回归
算法·回归·线性回归
故事和你915 小时前
洛谷-数据结构-1-3-集合3
数据结构·c++·算法·leetcode·贪心算法·动态规划·图论