文章目录
53. 最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
Solution
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
left, right = 0, 0
max_sum = float('-inf')
current_sum = 0
while right < len(nums):
current_sum += nums[right]
max_sum = max(max_sum, current_sum)
if current_sum < 0:
left = right + 1
right = left
current_sum = 0
else:
right += 1
return max_sum
动态规划是解决这个问题的一种常见方法,而且不需要显式地编写逻辑来决定何时丢弃前面的数。
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], dp[i-1] + nums[i])
return max(dp)
Kadane’s Algorithm 是一个求解“最大子数组和”问题的经典算法,核心思想实际上是一种简化版本的动态规划。
对于最大子数组和问题,滑动窗口的直观想法是:当当前和为正数时,继续扩展窗口(增加元素),因为这会增加总和;但当当前和为负数时,应该移动窗口的左边界到下一个元素,从而开始一个新的子数组,因为一个负的子数组和不会增加后续元素的和。
Kadane’s Algorithm 也遵循了这个逻辑,但它不显式地维护窗口的左边界和右边界。它简单地遍历数组,通过 current_max 来隐式地跟踪潜在的滑动窗口。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
current_sum = 0
max_sum = nums[0]
for num in nums:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
return max_sum
这个问题还可以用分治法解决,分治法的基本思想是将大问题分解成小问题,然后合并小问题的解来得到大问题的解。
如果只有一个元素,那么最大子数组和就是这个元素本身。
如果有多个元素,我们将数组从中间划分为两个部分,那么最大子数组和要么在左半部分,要么在右半部分,要么跨越了中点。对于前两种情况,我们可以用递归的方式解决。对于第三种情况,我们可以分别从中点向两边扩展,找到最大的和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 定义一个辅助函数,用于找到跨越中点的最大子数组和
def findMaxCrossingSubarray(nums, low, mid, high):
left_sum = float('-inf')
total = 0
for i in range(mid, low - 1, -1):
total += nums[i]
if total > left_sum:
left_sum = total
right_sum = float('-inf')
total = 0
for i in range(mid + 1, high + 1):
total += nums[i]
if total > right_sum:
right_sum = total
return left_sum + right_sum
def findMaxSubarray(nums, low, high):
if low == high:
return nums[low]
mid = (low + high) // 2
return max(
findMaxSubarray(nums, low, mid),
findMaxSubarray(nums, mid + 1, high),
findMaxCrossingSubarray(nums, low, mid, high)
)
return findMaxSubarray(nums, 0, len(nums) - 1)
分治法不仅可以解决区间 [0,n−1],还可以用于解决任意的子区间 [l,r] 的问题。如果我们把 [0,n−1] 分治下去出现的所有子区间的信息都用堆式存储的方式记忆化下来,即建成一棵真正的树之后,我们就可以在 O(logn) 的时间内求到任意区间内的答案,我们甚至可以修改序列中的值,做一些简单的维护,之后仍然可以在 O(logn) 的时间内求到任意区间内的答案,对于大规模查询的情况下,这种方法的优势便体现了出来。
56. 合并闭区间
以数组 intervals 表示若干个闭区间的集合,其中单个闭区间为 intervals[i] = [start_i, end_i] 。请你合并所有重叠的闭区间,并返回 一个不重叠的闭区间数组,该数组需恰好覆盖输入中的所有闭区间 。
示例:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
Solution
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
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
189. 轮转数组
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
Solution
我们可以使用额外的数组来将每个元素放到正确的位置。新的位置可以通过(i + k) % n来计算,其中n是数组的长度。然后我们再把额外的数组复制回原数组。
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
rotated_nums = [0] * n
for i in range(n):
rotated_nums[(i + k) % n] = nums[i]
nums[:] = rotated_nums
从另一个角度,我们可以将被替换的元素保存在变量 temp 中,从而避免了额外数组的开销。
我们可以把每个元素都移到正确的位置,然后再把新位置的元素移到它的正确位置,这样形成一个替换的环。为了确保每个元素都移动到正确的位置,我们需要从每个被替换的元素开始一个新的环,这样可以确保我们移动了所有元素。
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
start = count = 0
while count < n:
current, temp = start, nums[start]
while True:
next_idx = (current + k) % n
nums[next_idx], temp = temp, nums[next_idx]
current = next_idx
count += 1
if start == current:
break
start += 1
我们还可以用数组翻转的方法来解决这个问题。
这个方法基于这样一个事实:当我们把k个尾部元素旋转到头部后,原先的第一个元素就被放到第k + 1个位置。因此,我们可以首先把所有元素翻转,这样尾部的k个元素就被放到头部。然后我们把前k个元素翻转,再把剩下的元素翻转,就能得到我们需要的结果。
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
def reverse(i, j):
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
reverse(0, n - 1)
reverse(0, k - 1)
reverse(k, n - 1)
238. 除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
示例:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
Solution
这个问题的关键在于找到一种不直接计算除nums[i]之外的所有元素乘积的方法。一个直观的想法是先计算整个数组的乘积,然后对每个元素,用总乘积除以它。但这种方法在元素为0时会遇到问题,所以需要另一种方法。
为了找到除nums[i]之外的所有元素的乘积,我们可以先计算出数组的每个元素的前缀乘积和后缀乘积。前缀乘积指的是当前元素之前所有元素的乘积,后缀乘积则是当前元素之后的所有元素的乘积。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
prefix = [1] * n
suffix = [1] * n
result = [1] * n
for i in range(1, n):
prefix[i] = prefix[i - 1] * nums[i - 1]
for i in range(n - 2, -1, -1):
suffix[i] = suffix[i + 1] * nums[i + 1]
result[i] = prefix[i] * suffix[i]
result[-1] = prefix[-1] # 更新最后一个元素
return result
136. 只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例:
输入:nums = [2,2,1]
输出:1
Solution
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for num in nums:
res ^= num
return res
169. 多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
Solution
75. 颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
Solution
31. 下一个排列
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例:
输入:nums = [1,2,3]
输出:[1,3,2]
Solution
287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例:
输入:nums = [1,3,4,2,2]
输出:2
Solution
把数组看作是一个链表,其中数组的索引是节点,数组的值是下一个节点的指针。由于存在重复的数字,链表中一定存在环。
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow, fast = nums[0], nums[nums[0]]
while slow != fast:
slow = nums[slow]
fast = nums[nums[fast]]
slow = 0
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow
41. 缺失的第一个正数
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。