LeetCode 每周算法 2(子串、普通数组)
子串算法:
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
# 初始化哈希表,用于存储前缀和及其出现的次数
# 初始时,前缀和为0的情况出现1次,因为空子数组的和视为0
prefix_sum_count = {0: 1}
count = 0 # 用于记录满足条件的子数组个数
current_sum = 0 # 当前位置之前的元素和
for num in nums:
# 更新当前位置之前的元素和
current_sum += num
# 检查当前前缀和与之前的某个前缀和之差是否为k
# 如果存在这样的前缀和差值,那么说明这两个前缀和之间的子数组和就是k
if current_sum - k in prefix_sum_count:
# 如果存在,则将满足条件的子数组个数增加
# 注意,这里可能会多次计算到同一个满足条件的子数组(比如数组中有重复元素)
# 但由于题目只要求个数,所以这样是允许的
count += prefix_sum_count[current_sum - k]
# 将当前前缀和加入哈希表,并更新其出现的次数
# 注意,如果当前前缀和已经存在于哈希表中,则更新其计数,而不是替换
if current_sum in prefix_sum_count:
prefix_sum_count[current_sum] += 1
else:
prefix_sum_count[current_sum] = 1
# 返回满足条件的子数组个数
return count
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
# 初始化一个列表作为窗口,用于存储窗口内的元素索引
# 这里使用索引而不是元素值,以便在O(1)时间内移除窗口最左侧的元素
window = []
# 初始化结果列表
result = []
# 遍历数组nums
for i in range(len(nums)):
# 如果窗口不为空,并且当前元素比窗口最左侧的元素大
# 那么窗口最左侧的元素在后续窗口滑动中不会再是最大值
# 因此需要移除它(通过移除其索引)
while window and nums[i] >= nums[window[-1]]:
window.pop() # 移除窗口最右侧的索引(即当前窗口中最小的元素索引)
# 将当前元素的索引添加到窗口的末尾
window.append(i)
# 当窗口大小达到k时,开始记录窗口的最大值
if i >= k - 1:
# 窗口最左侧的元素索引对应的值即为当前窗口的最大值
result.append(nums[window[0]])
# 如果窗口最左侧的元素索引已经不在当前窗口内(即已经滑出窗口)
# 则将其从窗口中移除
if window[0] <= i - k:
window.pop(0) # 移除窗口最左侧的索引
# 返回记录的结果列表
return result
class Solution(object):
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
# 初始化需求哈希表,记录 t 中每个字符的需求次数
need = {}
for char in t:
need[char] = need.get(char, 0) + 1
# 初始化窗口哈希表,记录当前窗口中每个字符的计数
window = {}
# formed 用于记录当前窗口中已满足 t 中字符需求的种类数
formed = 0
# 初始化左右指针、最小长度和结果字符串
left, right = 0, 0
min_length = float('inf')
result = ""
# 滑动窗口逻辑
while right < len(s):
# 将右指针指向的字符加入窗口
char = s[right]
if char in need:
# 如果字符在 need 中,增加窗口哈希表中该字符的计数
window[char] = window.get(char, 0) + 1
# 如果该字符的计数达到了需求,则 formed 加一
if window[char] == need[char]:
formed += 1
# 尝试缩小窗口
while formed == len(need):
# 更新最小长度和结果字符串(如果当前窗口更小)
if right - left + 1 < min_length:
min_length = right - left + 1
result = s[left:right+1]
# 尝试移动左指针来缩小窗口
char_to_remove = s[left]
# 如果左指针指向的字符在窗口哈希表中
if char_to_remove in window:
# 减少窗口哈希表中该字符的计数
window[char_to_remove] -= 1
# 如果该字符的计数不再满足需求,则 formed 减一
if window[char_to_remove] < need[char_to_remove]:
formed -= 1
# 移动左指针
left += 1
# 移动右指针以继续探索
right += 1
# 返回结果字符串
return result
普通数组算法:
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 初始化当前子数组的最大和(current_max)为数组的第一个元素
# 初始化全局子数组的最大和(global_max)也为数组的第一个元素
if not nums: # 如果数组为空,则直接返回0
return 0
current_max = nums[0]
global_max = nums[0]
# 从数组的第二个元素开始遍历
for num in nums[1:]:
# 如果当前子数组的和加上当前元素的值比当前元素的值还小
# 那么就重新以当前元素为新的子数组的起点
current_max = max(num, current_max + num)
# 更新全局最大和
global_max = max(global_max, current_max)
# 遍历结束后,global_max即为所求的最大和的连续子数组的和
return global_max
class Solution(object):
def merge(self, intervals):
"""
:type intervals: List[List[int]]
:rtype: List[List[int]]
"""
# 如果输入的区间列表为空,则直接返回空列表
if not intervals:
return []
# 根据区间的起始位置对区间列表进行排序
# 使用lambda函数作为sort方法的key参数,指定按照区间的第一个元素(即起始位置)进行排序
intervals.sort(key=lambda x: x[0])
# 初始化一个空列表merged,用于存储合并后的区间
# 同时,将排序后的第一个区间添加到merged列表中,作为合并的起点
merged = [intervals[0]]
# 从排序后的区间列表的第二个区间开始遍历
for i in range(1, len(intervals)):
# 取出当前遍历到的区间
current_interval = intervals[i]
# 取出merged列表中的最后一个区间(即当前已经合并好的最后一个区间)
last_merged_interval = merged[-1]
# 检查当前区间是否与最后一个合并区间重叠
# 重叠的条件是:当前区间的起始位置小于或等于最后一个合并区间的结束位置
if current_interval[0] <= last_merged_interval[1]:
# 如果重叠,则更新最后一个合并区间的结束位置为两个区间结束位置的最大值
# 这样做是为了合并两个重叠的区间
last_merged_interval[1] = max(last_merged_interval[1], current_interval[1])
else:
# 如果不重叠,则将当前区间直接添加到merged列表中
# 因为当前区间与之前的区间都不重叠,所以它可以作为一个新的合并区间的起点
merged.append(current_interval)
# 遍历结束后,merged列表中就包含了所有合并后的区间
# 返回这个列表作为函数的结果
return merged
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
n = len(nums) # 获取数组nums的长度
k = k % n # 如果k大于数组长度,则取模,保证k在有效范围内
# 定义一个辅助函数reverse,用于反转数组nums中从start到end(不包含end)的部分
def reverse(start, end):
while start < end:
# 交换start和end位置的元素
nums[start], nums[end] = nums[end], nums[start]
start += 1 # 向前移动start指针
end -= 1 # 向后移动end指针
# 反转整个数组
reverse(0, n-1)
# 反转前k-1%n个元素,即反转轮转后应该位于数组前面的部分
reverse(0, k-1)
# 反转剩余的n-k-1个元素,即反转轮转后应该位于数组后面的部分
reverse(k, n-1)
class Solution(object):
def productExceptSelf(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
n = len(nums)
# 初始化结果数组,全部填充为1,因为乘积的初始值是1
answer = [1] * n
# 左侧乘积
left_product = 1
for i in range(n):
# 当前位置的答案数组中的值是左侧乘积
answer[i] *= left_product
# 更新左侧乘积为当前左侧乘积乘以当前元素的值
left_product *= nums[i]
# 右侧乘积,初始化为1
right_product = 1
# 从右向左遍历,更新答案数组
for i in range(n - 1, -1, -1):
# 当前位置的答案数组中的值还需要乘以右侧乘积
answer[i] *= right_product
# 更新右侧乘积为当前右侧乘积乘以当前元素的值
#(注意这里是nums[i],因为我们是从右向左遍历的)
right_product *= nums[i]
return answer
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
# 第一步:将所有非正整数和大于n的整数替换为n+1(或其他大于n的值,目的是排除它们对后续步骤的干扰)
for i in range(n):
if nums[i] <= 0 or nums[i] > n:
nums[i] = n + 1
# 第二步:根据数组中的值,将其放到正确的位置上(索引为值减1)
# 注意,这里可能产生循环依赖,例如nums[i] = j 和 nums[j] = i,此时我们需要用一个临时变量来避免覆盖
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
# 交换nums[i]和nums[nums[i]-1],直到nums[i]处于正确的位置或nums[i]已经处于循环中
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
# 第三步:遍历数组,找到第一个不在正确位置上的正整数索引+1,即为所求
for i in range(n):
if nums[i] != i + 1:
return i + 1
# 如果所有正整数都出现了,则返回n+1(因为题目要求的是未出现的最小正整数)
return n + 1