Leetcode 【442、1031】
问题描述:【Array】442. Find All Duplicates in an Array
解题思路:
这道题很容易想到用和数组一样大小的空间来统计每个数字出现的次数,然后输出出现次数为 2 的那些数字即可。但是这样时间和空间复杂度均为 O(n)。有没有办法在保持时间复杂度为 O(n) 的情况下让空间复杂度降为 O(1) 呢(即不需要额外的空间消耗)?
方法1(交换法):
注意到该数组中的数的范围是 1 ≤ a[i] ≤ n (n = size of array)
,因此我们可以想到利用原数组大小的空间,将各个数字交换到它们对应的索引位置处(比如将数字 4 放到 nums 的第 4 个位置上 nums[3]),我们只需要一次遍历就可以实现此操作。如果有些数字出现了两次,则肯定某些位置对应的数字不同(如 [1,2,3,4,2,3,7,8] 中的第 5 个位置和第 6 个位置不是 5 和 6)。我们只需要再遍历一次,就可以找到这些出现两次的数字。
这样遍历两次,时间复杂度为 O(n),但是空间复杂度为 O(1)。
问题的关键在于,第一次遍历,我们如何让数字归为到它对应的位置上呢?比如 nums = [4,3,2,7,8,2,3,1],可以这样做:
(1)交换 4 和 7,此时发现第 1 个位置不是 1,继续交换;交换 7 和 3;交换 3 和 2; 交换 2 和 3;发现第 1 个位置的 3 和第 3 个位置上的 3 重复了,就移动下标,继续向后走;
(2)第 2 个位置是 2,继续往后走;第 3 个位置是 3,继续往后走;... ,
(3)采取(1、2)两步的交换方式,最后可以得到 nums = [1,2,3,4,3,2,7,8],这样所有出现 1 次的数字就正确归位了。
注意:一种错误的交换方法是,交换一次就往后走,这样会导致 nums 最后变成 [7,2,3,4,1,2,3,8],即 1 和 7 没有交换到正确的位置。
python3 实现:
class Solution:
def findDuplicates(self, nums: List[int]) -> List[int]:
ans = []
i = 0
while i < len(nums): # 第一遍循环,将数字归位
if nums[i] != nums[nums[i]-1]:
tem = nums[i]
nums[i] = nums[tem-1]
nums[tem-1] = tem
else:
i += 1
for i in range(len(nums)): # 第二遍循环,找出数字与位置不对应的数字,就是答案
if i + 1 != nums[i]:
ans.append(nums[i])
return ans
方法2(数字改写成正负值当计数器):
遍历一遍,将数字 nums[i] 对应的下标位置 nums[i]-1 上的数字 nums[nums[i]-1] 改写成 nums[nums[i]-1]] 的相反数。因此,只遍历过一次的是负数,遍历两次的是正数。我们只需要在遍历的过程中判断 nums[abs(nums[i])-1] 是否为正数,就能找到出现两次的数字。换句话说,这种方法使用正负数来当做计数器,负数记为 1,代表第 i 个位置的数字 i 出现一次;正数记为 2,代表第 i 个位置的数字 i 出现 2 次。
比如:nums = [4,3,2,7,8,2,3,1],遍历的过程中,将第 4 个位置的 7 改写成 -7(代表第 4 个位置的数字 4 出现 1 次);将第 3 个位置的 2 改写成 -2(代表第 3 个位置的数字 3 出现 1 次);将第 2 个位置的 3 改写成 -3(代表第 2 个位置的数字 2 出现 1 次);将第 7 个位置的 3 改写成 -3(代表第 7 个位置的数字 7 出现 1 次);将第 8 个位置的 1 改写成 -1(代表第 8 个位置的数字 8 出现 1 次);将第 2 个位置的 -3 改写成 3(代表第 2 个位置的数字 2 出现 2 次);将第 3 个位置的 -2 改写成 2(代表第 3 个位置的数字 3 出现 2 次);将第 1 个位置的 4 改写成 -4(代表第一个位置的数字 1 出现 1 次)。因此,我们找到了正数 [2,3],即为出现两次的数字。
注意:下标有可能出现负数,所以在编程的时候,要使用下标的绝对值。
python3 实现:
class Solution:
def findDuplicates(self, nums: List[int]) -> List[int]:
ans = []
for i in range(len(nums)):
nums[abs(nums[i])-1] *= -1 # 第 i 个位置的数字取反
if nums[abs(nums[i])-1] > 0: # 第 i 个位置的数字取反过两次,负负得正
ans.append(abs(nums[i]))
return ans
问题描述:【Array】1031. Maximum Sum of Two Non-Overlapping Subarrays
解题思路:
这道题的思想很朴素:
- 因为我们要找到不相交的两个长度为 L 和 M 的连续子数组,所以我们很容易想到,先求出各个位置长度为 L 和长度为 M 的子数组之和,时间复杂度为 O(n),这样可以得到两个列表 Lsum,Msum,长度分别为
len(A)-L+1
和len(A)-M+1
; - 对于 Lsum 和 Msum,双层循环,
for i in range(len(Lsum)), for j in range(len(Msum))
; -
找出不相交的区域(满足条件
j >= i+L or j+M <= i
),更新 Lsum[i] 与 Msum[j] 的最大值; - 返回最大的两段子数组之和。
时间复杂度 O(n^2),空间复杂度 O(n)。
Python3 实现:
class Solution:
def maxSumTwoNoOverlap(self, A: List[int], L: int, M: int) -> int:
Lsum = []
Msum = []
max_ = 0
for i in range(len(A)-L+1): # 求出各个位置长度为 L 的子数组之和
Lsum.append(sum(A[i:i+L]))
for i in range(len(A)-M+1): # 求出各个位置长度为 M 的子数组之和
Msum.append(sum(A[i:i+M]))
for i in range(len(Lsum)):
for j in range(len(Msum)):
if j >= i+L or j+M <= i: # 不相交的区域,更新最大值
max_ = max(max_, Lsum[i] + Msum[j])
return max_