53.最大子数组和
给你一个整数数组nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
题解:使用dpi表示以索引为i的最大子数组和
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
dp = [0] * n
dp[0] = nums[0]
for i in range(1, n):
dp[i] = max(nums[i], nums[i] + dp[i - 1])
res = float("-inf")
for i in range(n):
res = max(res, dp[i])
return res
dpi只与dpi-1有关,利用空间压缩实现优化
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
# base case
dp_0 = nums[0]
dp_1 = 0
res = dp_0
for i in range(1, n):
# dp[i] = max(nums[i], nums[i] + dp[i-1])
dp_1 = max(nums[i], nums[i] + dp_0)
dp_0 = dp_1
# 顺便计算最大的结果
res = max(res, dp_1)
return res
也可以使用前缀和,nums[i]
为结尾的最大子数组之和是多少?其实就是preSum[i+1]-min(preSum[0..i])
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
preSum = [0] * (n + 1)
preSum[0] = 0
# 构造 nums 的前缀和数组
for i in range(1, n + 1):
preSum[i] = preSum[i - 1] + nums[i - 1]
res = float('-inf')
minVal = float('inf')
for i in range(n):
# 维护 minVal 是 preSum[0..i] 的最小值
minVal = min(minVal, preSum[i])
# 以 nums[i] 结尾的最大子数组和就是 preSum[i+1] - min(preSum[0..i])
res = max(res, preSum[i + 1] - minVal)
return res
另外也可以使用滑动窗口,在窗口内元素之和大于等于0
时扩大窗口,在窗口内元素之和小于0
时缩小窗口,在每次移动窗口时更新答案
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
left, right = 0, 0
windowSum, maxSum = 0, float('-inf')
while right < len(nums):
# 扩大窗口并更新窗口内的元素和
windowSum += nums[right]
right += 1
# 更新答案
maxSum = max(windowSum, maxSum)
# 判断窗口是否要收缩
while windowSum < 0:
# 缩小窗口并更新窗口内的元素和
windowSum -= nums[left]
left += 1
return maxSum
区间问题
1288.删除被覆盖区间
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。只有当c <= a
且b <= d
时,我们才认为区间[a,b)
被区间[c,d)
覆盖。在完成所有删除操作后,请你返回列表中剩余区间的数目。
题解:将所有区间按起点排序,则无非有以下3种情况
1.找到了覆盖区间
2.两个区间有交集,可以合并成一个大区间
3.两个区间完全不相交。
class Solution:
def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: (x[0], -x[1])) #按照起点升序排列,起点相同时降序排列
left = intervals[0][0]
right = intervals[0][1]
res = 0
for i in range(1, len(intervals)):
intv = intervals[i]
if left <= intv[0] and right >= intv[1]: #找到覆盖区间
res += 1
if right >= intv[0] and right <= intv[1]: #找到相交区间,合并
right = intv[1]
if right < intv[0]: #完全不相交,更新起点和终点
left = intv[0]
right = intv[1]
return len(intervals) - res
56.合并区间
以数组intervals
表示若干个区间的集合,其中单个区间为intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x: x[0]) # 按区间的 start 升序排列
left = intervals[0][0]
right = intervals[0][1]
res = []
res.append([left, right])
for i in range(1, len(intervals)):
intv = intervals[i]
if intv[0] <= res[-1][1]:
res[-1][1] = max(res[-1][1], intv[1]) # 找到最大的 end
else:
res.append(intv)
return res
986.区间列表的交集
给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。
返回这 两个区间列表的交集 。形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。
两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。
题解:
两个区间可分为两种情况:相交和不相交
①不相交:b2<a1 or a2<b1
②相交:b2<a1 and a2<b1
我们只需考虑相交情况,又可分为以下情况:
交集区间
[c1,c2]
具有一定的规律:c1 = max(a1,b1)
,c2 = min(a2,b2)
class Solution:
def intervalIntersection(
self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
res = []
i, j = 0, 0
while i < len(firstList) and j < len(secondList):
a1 = firstList[i][0]
a2 = firstList[i][1]
b1 = secondList[j][0]
b2 = secondList[j][1]
if b2 >= a1 and a2 >= b1:
res.append([max(a1, b1), min(a2, b2)])
if b2 < a2:
j += 1
else:
i += 1
return res
189.轮转数组
给定一个整数数组nums
,将数组中的元素向右轮转k
个位置,其中k
是非负数。
方法1:使用切片赋值(需注意nums[:],必须带[:],否则原地址的数据并未发生改变)
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
k = k % len(nums) #确保k在数组长度内
nums[:] = nums[-k:] + nums[:-k] #
方法2:翻转字符,将数组分成两部分进行翻转,然后再翻转整个数组来达到目标。
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
k = k % len(nums)
def reverse(nums, start, end): #翻转函数
while start < end:
nums[start], nums[end] = nums[end], nums[start]
start += 1
end -= 1
reverse(nums, 0, len(nums) - 1) #翻转整个数组
reverse(nums, 0, k - 1)# 翻转前 k 个元素
reverse(nums, k, len(nums) - 1) # 翻转后 len(nums) - k 个元素
方法3:将末尾删除后插入到数组开头
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
for i in range(k):
nums.insert(0,nums.pop())
# nums.pop() 从数组末尾删除最后一个元素,并返回它。
#nums.insert(0, element) 在数组的起始位置插入这个元素。
238.除自身以外数组的乘积
给你一个整数数组nums
,返回 数组answer
,其中answer[i]
等于nums
中除nums[i]
之外其余各元素的乘积 。
题目数据保证数组nums
之中任意元素的全部前缀元素和后缀的乘积都在32位整数范围内。
请 不要使用除法,且在O(n)
时间复杂度内完成此题。
题解:构造一个 prefix 数组记录「前缀积」,再用一个 suffix 记录「后缀积」,根据前缀和后缀积就能计算除了当前元素之外其他元素的积。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
prefix = [0] * n # 构造前缀积
prefix[0] = nums[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] * nums[i]
suffix = [0] * n #构造后缀积
suffix[n - 1] = nums[n - 1]
for i in range(n - 2, -1, -1):
suffix[i] = suffix[i + 1] * nums[i]
res = [0] * n
res[0] = suffix[1] # 开头结果
res[n - 1] = prefix[n - 2] # 末尾结果
for i in range(1, n - 1):
res[i] = prefix[i - 1] * suffix[i + 1]
return res
进阶版:对前缀积和后缀积的数据进行优化
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [1] * n
prefix = 1
for i in range(n):
res[i] = prefix
prefix *= nums[i] #前缀积
suffix = 1
for i in range(n - 1, -1, -1):
res[i] *= suffix
suffix *= nums[i]
return res
41.缺失的第一个正数
给你一个未排序的整数数组nums
,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
题解:将所有数字都放在它应该在的位置,若出现不应该在的位置则说明缺失
1.遍历数组,将数字nums[i]
放到索引nums[i] - 1
上。例如:[1,2,3,4,...,n]
2.遍历数组,找到第一个不在正确位置上的数字
3.若都满足,则缺失的最小整数为n+1
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
for i in range(n):
while 0 < nums[i] <= n and nums[nums[i]-1] !=nums[i]:
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
return n+1