抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

题单LeetCode 热题 100的普通数组部分,给出解析。


53. Maximum Subarray

原题链接

题目描述

Given an integer array nums, find the subarray with the largest sum, and return its sum.

Constraints:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

Follow up: If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

解题方法

Kadane’s Algorithm

Kadane’s Algorithm 是一种用于解决 最大子数组和问题(Maximum Subarray Sum Problem)的动态规划算法。它可以在 O(n) 时间复杂度内找到一个数组中和最大的连续子数组。

其基本思想是通过遍历数组,记录到目前为止的子数组最大和以及包含当前元素的最大和,来逐步更新全局最大子数组的和。

算法步骤:

  1. 初始化两个变量:
    • max_so_far:记录全局最大子数组的和。
    • max_ending_here:记录以当前元素结尾的最大子数组的和。
  2. 对数组的每个元素 a[i],更新 max_ending_here 为以下两者之一:
    • a[i] 自己,表示从当前元素重新开始一个子数组。
    • max_ending_here + a[i],表示将当前元素继续添加到已有的子数组中。
  3. 每次更新 max_ending_here 后,将它与 max_so_far 进行比较,更新全局最大和。
  4. 返回 max_so_far 即为最终的答案。

此算法的关键在于最大的子序列中不会包含求和值为负的前缀,当累加的结果成为一个负值时,就将下一个元素作为潜在的最大子序列的左端,重新进行累加。

如果需要更详细的解释,此处是维基百科上关于这个算法的详细解释最大子数组和

分治

分治法是一种递归策略,基于「分而治之」的思想,将问题划分为较小的子问题,然后将其结果合并。对于「最大子数组和问题」,分治法的主要思路是:将数组划分为左右两部分,递归求解左右两部分的最大子数组和,同时考虑跨越中间的最大子数组和。

分治法的步骤:

  1. 划分子问题:将数组从中间位置分成左右两部分,分别递归求解左右部分的最大子数组和。
  2. 求解跨越中间的子数组:跨越中间点的最大子数组,必须包含左部分的最后一个元素和右部分的第一个元素。可以分别从中间向左、向右扩展,找到跨越中间的最大子数组和。
  3. 合并结果:返回三者中最大的结果:
    • 左部分的最大子数组和;
    • 右部分的最大子数组和;
    • 跨越中间的最大子数组和。

代码实现

Kadane’s Algorithm

Python版本:

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_so_far = nums[0]
max_ending_here = nums[0]

for i in range(1, len(nums)):
max_ending_here = max(nums[i], max_ending_here + nums[i])
max_so_far = max(max_ending_here, max_so_far)

return max_so_far

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int maxSubArray(int[] nums) {
int max_ending_here = nums[0];
int max_so_far = nums[0];

for(int i = 1; i < nums.length; i++){
max_ending_here = Math.max(nums[i], max_ending_here + nums[i]);
max_so_far = Math.max(max_ending_here, max_so_far);
}

return max_so_far;
}
}

分治

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
def max_crossing_sum(nums, left, mid, right):
# 包含中间的左半部分最大和
left_sum = float('-inf')
current_sum = 0
for i in range(mid, left - 1, -1):
current_sum += nums[i]
if current_sum > left_sum:
left_sum = current_sum

# 包含中间的右半部分最大和
right_sum = float('-inf')
current_sum = 0
for i in range(mid + 1, right + 1):
current_sum += nums[i]
if current_sum > right_sum:
right_sum = current_sum

# 返回跨越中间的最大和
return left_sum + right_sum

def max_sub_array_divide_and_conquer(nums, left, right):
# 基本情况:当只有一个元素时
if left == right:
return nums[left]

mid = (left + right) // 2

# 分别递归求解左半部分、右半部分和跨越中间的子数组最大值
left_max = max_sub_array_divide_and_conquer(nums, left, mid)
right_max = max_sub_array_divide_and_conquer(nums, mid + 1, right)
cross_max = max_crossing_sum(nums, left, mid, right)

# 返回三个部分的最大值
return max(left_max, right_max, cross_max)

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

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
public int maxSubArray(int[] nums) {
return maxSubArrayDivideAndConquer(nums, 0, nums.length - 1);
}

private int maxCrossingSum(int[] nums, int left, int mid, int right) {
// 包含中间的左半部分最大和
int leftSum = Integer.MIN_VALUE;
int currentSum = 0;
for (int i = mid; i >= left; i--) {
currentSum += nums[i];
if (currentSum > leftSum) {
leftSum = currentSum;
}
}

// 包含中间的右半部分最大和
int rightSum = Integer.MIN_VALUE;
currentSum = 0;
for (int i = mid + 1; i <= right; i++) {
currentSum += nums[i];
if (currentSum > rightSum) {
rightSum = currentSum;
}
}

// 返回跨越中间的最大和
return leftSum + rightSum;
}

private int maxSubArrayDivideAndConquer(int[] nums, int left, int right) {
// 基本情况:当只有一个元素时
if (left == right) {
return nums[left];
}

int mid = (left + right) / 2;

// 分别递归求解左半部分、右半部分和跨越中间的子数组最大值
int leftMax = maxSubArrayDivideAndConquer(nums, left, mid);
int rightMax = maxSubArrayDivideAndConquer(nums, mid + 1, right);
int crossMax = maxCrossingSum(nums, left, mid, right);

// 返回三个部分的最大值
return Math.max(Math.max(leftMax, rightMax), crossMax);
}
}

复杂度分析

Kadane’s Algorithm

时间复杂度:$O(n)$,仅需遍历一次。

空间复杂度:$O(1)$,常数个额外变量。

分治

时间复杂度: $O(n log n)$,每次将数组划分为两部分,递归进行求解,合并的过程是 $O(n)$,因此总时间复杂度为 $O(n log n)$。

空间复杂度: $O(log n)$,由于递归调用的栈深度为 $log n$。


56. Merge Intervals

原题链接

题目描述

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

Constraints:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

解题方法

解决这个问题的步骤如下:

  1. 排序:首先,我们按照每个区间的起始位置 starti 对区间进行排序。这是因为如果两个区间有重叠,它们的起始位置必然相近或重叠,因此排序后方便依次处理。
  2. 合并区间:从第一个区间开始,逐个检查区间是否与前一个区间有重叠。如果当前区间的起始位置小于等于前一个区间的结束位置,说明它们有重叠,则将两个区间合并,即更新前一个区间的结束位置为两个区间结束位置的最大值。否则,将当前区间作为新的非重叠区间加入结果数组。
  3. 返回结果:经过上述步骤,最终我们可以得到一个包含所有合并后的非重叠区间的数组。

代码实现

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# 如果输入为空,直接返回空列表
if not intervals:
return []

# 按照区间的起始位置进行排序
intervals.sort(key=lambda x: x[0])

# 初始化结果列表
merged = []

for interval in intervals:
# 如果结果列表为空,或者当前区间的起始位置大于结果列表最后一个区间的结束位置
# 则将当前区间添加到结果列表
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
else:
# 否则,合并当前区间到结果列表的最后一个区间
merged[-1][1] = max(merged[-1][1], interval[1])

return merged

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int[][] merge(int[][] intervals) {
// 如果输入为空,直接返回空数组
if (intervals.length == 0) {
return new int[0][0];
}

// 按照区间的起始位置进行排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));

List<int[]> merged = new LinkedList<>();

for (int[] interval : intervals) {
// 如果结果列表为空,或者当前区间的起始位置大于结果列表最后一个区间的结束位置
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
merged.add(interval);
} else {
// 否则,合并当前区间到结果列表的最后一个区间
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], interval[1]);
}
}

return merged.toArray(new int[merged.size()][]);
}
}

复杂度分析

时间复杂度:$O(nlogn)$

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


189. Rotate Array

原题链接

题目描述

Given an integer array nums, rotate the array to the right by k steps, where k is non-negative.

Constraints:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

Follow up:

  • Try to come up with as many solutions as you can. There are at least three different ways to solve this problem.
  • Could you do it in-place with O(1) extra space?

解题方法

要将数组中的元素向右轮转 k 个位置,可以使用多种方法。以下是三种不同的解决方案:

使用额外数组

  1. 创建一个新数组 temp,大小与 nums 相同。
  2. nums 中的元素移动到 temp 中的正确位置。
  3. temp 中的元素复制回 nums

反转法

  1. 先反转整个数组。
  2. 然后反转前 k 个元素。
  3. 最后反转剩下的元素。

循环替换

  1. 使用一个循环,逐个替换每个元素的位置,直到所有元素都被替换。
  2. 需要注意处理重复的元素。

代码实现

使用额外数组

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: void Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k %= n # 处理 k 大于 n 的情况
temp = [0] * n
for i in range(n):
temp[(i + k) % n] = nums[i]
nums[:] = temp

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n; // 处理 k 大于 n 的情况
int[] temp = new int[n];

for (int i = 0; i < n; i++) {
temp[(i + k) % n] = nums[i];
}

System.arraycopy(temp, 0, nums, 0, n);
}
}

反转法

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: void Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k %= n # 处理 k 大于 n 的情况

def reverse(start, end):
while start < end:
nums[start], nums[end] = nums[end], nums[start]
start += 1
end -= 1

reverse(0, n - 1) # 反转整个数组
reverse(0, k - 1) # 反转前 k 个元素
reverse(k, n - 1) # 反转剩下的元素

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n; // 处理 k 大于 n 的情况

reverse(nums, 0, n - 1); // 反转整个数组
reverse(nums, 0, k - 1); // 反转前 k 个元素
reverse(nums, k, n - 1); // 反转剩下的元素
}

private void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}

循环替换

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: void Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k %= n # 处理 k 大于 n 的情况

count = 0 # 记录替换次数
start = 0 # 记录循环的起始位置
while count < n:
current = start
prev_value = nums[start]
while True:
next_index = (current + k) % n
nums[next_index], prev_value = prev_value, nums[next_index]
current = next_index
count += 1
if start == current:
break
start += 1

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n; // 处理 k 大于 n 的情况

int count = 0; // 记录替换次数
for (int start = 0; count < n; start++) {
int current = start;
int prevValue = nums[start];
do {
int nextIndex = (current + k) % n;
int temp = nums[nextIndex];
nums[nextIndex] = prevValue;
prevValue = temp;
current = nextIndex;
count++;
} while (start != current);
}
}
}

复杂度分析

时间复杂度:所有方法的时间复杂度都是 $O(n)$。

空间复杂度:

  • $O(n)$(额外数组)
  • $O(1)$(原地反转)
  • $O(1)$(原地循环替换)

238. Product of Array Except Self

原题链接

题目描述

Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i].

The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.

You must write an algorithm that runs in O(n) time and without using the division operation.

Constraints:

  • 2 <= nums.length <= 10^5
  • -30 <= nums[i] <= 30
  • The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.

Follow up: Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)

解题方法

要求数组中除 i 位置以外的所有元素的乘积,考虑使用前缀后缀数组解决问题。

使用两个辅助数组分别存储前缀积和后缀积,最后组合这两个数组来得到结果。

前缀积: 遍历数组 nums 来构建一个前缀积数组 prefixprefix[i] 表示从数组开头到 nums[i - 1] 的所有元素的乘积。数组中第一个元素,prefix[0] 设置为 1,因为没有元素在 nums[0] 之前。

后缀积: 从数组末尾遍历数组 nums 来构建一个后缀积数组 suffixsuffix[i] 表示从 nums[i + 1] 到数组末尾的所有元素的乘积。数组中最后一个元素,suffix[n - 1] 设置为 1,因为没有元素在 nums[n - 1] 之后。

返回数组 answer[i] 等于 prefix[i]suffix[i] 的乘积,这样就得到了除去当前元素 nums[i] 以外所有元素的乘积。

代码实现

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]
        n = len(nums)

        # 返回数组、前缀积、后缀积初始化
        ans, pre, suf = [1] * n, [1] * n, [1] * n

        # 前缀积计算
        for i in range(1, n):
            pre[i] = nums[i - 1] * pre[i - 1]

        # 后缀积计算
        for j in range(n - 2 , -1 , -1):
            suf[j] = nums[j + 1] * suf[j + 1]

        # 答案数组计算
        for k in range(n):
            ans[k] = pre[k] * suf[k]

        return ans

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;

// 返回数组、前缀积、后缀积初始化
int[] ans = new int[n];
int[] pre = new int[n];
int[] suf = new int[n];

Arrays.fill(pre, 1); // 填充数组初值为 1
Arrays.fill(suf, 1);
Arrays.fill(ans, 1);

// 前缀积计算
for (int i = 1; i < n; i++) {
pre[i] = nums[i - 1] * pre[i - 1];
}

// 后缀积计算
for (int j = n - 2; j >= 0; j--) {
suf[j] = nums[j + 1] * suf[j + 1];
}

// 答案数组计算
for (int k = 0; k < n; k++) {
ans[k] = pre[k] * suf[k];
}

return ans;
}
}

优化空间复杂度:不用实际存储 prefix 数组与 suffix 数组,用一个变量来跟踪前缀积并直接更新 answer 数组;然后,再用另一个变量来跟踪后缀积并更新 answer

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [1] * n

        # 前缀积
        pre = 1
        for i in range(n):
            ans[i] = pre
            pre *= nums[i]
       
        # 后缀积
        suf = 1
        for j in range(n - 1, -1, -1):
            ans[j] *= suf
            suf *= nums[j]

        return ans

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] ans = new int[n];

// 前缀积
int pre = 1;
for (int i = 0; i < n; i++) {
ans[i] = pre;
pre *= nums[i];
}

// 后缀积
int suf = 1;
for (int j = n - 1; j >= 0; j--) {
ans[j] *= suf;
suf *= nums[j];
}

return ans;
}
}

复杂度分析

时间复杂度:$O(n)$

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


41. First Missing Positive

原题链接

题目描述

Given an unsorted integer array nums. Return the smallest positive integer that is not present in nums.

You must implement an algorithm that runs in O(n) time and uses O(1) auxiliary space.

Constraints:

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1

解题方法

这个问题要求我们找出未出现在数组 nums 中的最小正整数。我们需要使用时间复杂度为 O(n),并且额外空间复杂度为 O(1) 的算法。

考虑使用原地哈希的解法,基本思想是将对应的值放到对应的索引位置上。

  1. 将数字放置到正确的位置:由于我们要求找出最小的正整数,而数组中可能包含无效值(负数,零,超出范围的数值),因此我们可以通过重新排列数组来确定哪些数存在。

    • 遍历数组,对于每个元素 nums[i],如果它的值在 [1, n] 的范围内(n 是数组的长度),则将其放到对应的索引位置,即 nums[i] 应该放在 nums[nums[i] - 1]
    • 我们通过交换操作将这些数字放到它们应该在的位置。
  2. 查找第一个不匹配的数:遍历重新排列后的数组,第一个值与索引不匹配的位置就是缺失的最小正整数。

代码实现

Python版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)

# 将数字放置到正确的位置
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
# 交换 nums[i] 和 nums[nums[i] - 1]
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]

# 找到第一个不匹配的索引
for i in range(n):
if nums[i] != i + 1:
return i + 1

# 如果所有的数字都在正确的位置上,则返回 n + 1
return n + 1

Java版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;

// 将数字放置到正确的位置
for (int i = 0; i < n; i++) {
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
// 交换 nums[i] 和 nums[nums[i] - 1]
swap(nums, i, nums[i] - 1);
}
}

// 找到第一个不匹配的索引
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}

// 如果所有的数字都在正确的位置上,则返回 n + 1
return n + 1;
}

// 辅助函数,用于交换数组中的两个元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

复杂度分析

时间复杂度:$O(n)$

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

评论