题目: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]
解法:双指针(快慢指针)
思路
因为数组已经有序,重复的元素一定相邻。
- 使用两个指针:
slow和fast。 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=1:nums[1]=1,nums[slow-1]=nums[0]=1,相等 → 重复,fast++[1, 1, 2]
s f -
fast=2:nums[2]=2,nums[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](顺序任意)
解法一:双指针(快慢指针,保留非目标元素)
思路
使用两个指针 slow 和 fast。
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. 合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 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]视为重复,只保留一个。
解法:排序 + 双指针
思路
- 排序:将数组升序排序,便于使用双指针和去重。
- 固定一个数 :遍历数组,将当前元素
nums[i]作为三元组的第一个数,然后在i+1到末尾之间使用双指针left和right寻找另外两个数。 - 双指针查找 :
- 计算当前和
sum = nums[i] + nums[left] + nums[right] - 若
sum == 0:记录三元组,然后移动left和right并跳过重复元素。 - 若
sum < 0:说明需要增大,left++ - 若
sum > 0:说明需要减小,right--
- 计算当前和
- 去重 :
- 外层循环中,如果
nums[i] == nums[i-1],跳过(避免重复的三元组)。 - 内层找到解后,移动
left和right时,跳过与之前相等的值。
- 外层循环中,如果
注意 :当 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。
解法:双指针(对撞指针)
思路
使用两个指针 left 和 right 分别指向数组的开头和结尾。
面积计算公式: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),只使用了常数个变量。
解法二:分治法(了解即可)
思路
将数组分成左右两半,最大子数组和可能出现在:
- 完全在左半部分
- 完全在右半部分
- 跨越中点(包含左半部分的后缀和右半部分的前缀)
递归求解,取三者最大值。
代码(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]被视为重叠(边界相接也算重叠)。
解法:排序 + 合并
思路
- 排序 :将所有区间按照左端点(
start)升序排序。如果左端点相同,可以按右端点升序(非必须)。 - 初始化 :将第一个区间加入结果列表
merged。 - 遍历合并 :对于每个后续区间
current = [start, end]:- 获取
merged中最后一个区间last = [last_start, last_end]。 - 如果
current的左端点start大于last_end,说明两个区间不重叠,直接将current加入结果。 - 否则,说明重叠,更新
last_end = max(last_end, end)。
- 获取
- 返回 :
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,其乘积等于 左边所有元素的乘积 × 右边所有元素的乘积。
- 前缀积 :先遍历一次,计算每个位置
i左侧所有元素的乘积,存入answer数组(此时answer[i]表示nums[0] * ... * nums[i-1],左边界的乘积为 1)。 - 后缀积 :从右向左遍历,维护一个变量
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不计入额外空间)。