LeetCode 每周算法 1(哈希、双指针、滑动窗口)
哈希算法:
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
# 创建一个空字典来存储遍历过的元素及其索引
num_dict = {}
# 使用for循环遍历nums数组,enumerate函数会同时返回索引(i)和值(num)
for i, num in enumerate(nums):
# 计算target减去当前元素的值,得到我们需要找的“补数”
complement = target - num
# 检查这个补数是否已经在字典num_dict中
if complement in num_dict:
# 如果在,说明我们找到了一个元素对,它们的和等于target
# num_dict[complement]是补数的索引,i是当前元素的索引
# 返回这两个索引的列表
return [num_dict[complement], i]
# 如果补数不在字典中,将当前元素及其索引添加到字典中
# 这样,在下一次迭代中,如果遇到了与当前元素相加等于target的元素,
# 我们就可以通过查找字典来快速找到它的索引
num_dict[num] = i
# 如果没有找到满足条件的两个数(但根据题目描述,这种情况不会发生),
# 理论上这里应该返回一个空列表。然而,由于题目保证每种输入只会对应一个答案,
# 所以这行代码实际上不会被执行到。
return []
class Solution(object):
def groupAnagrams(self, strs):
"""
:type strs: List[str]
:rtype: List[List[str]]
"""
# 创建一个空字典,用于存储排序后的字符串(作为键)和对应的原始字符串列表(作为值)
anagrams = {}
# 遍历输入字符串数组 strs 中的每个字符串 s
for s in strs:
# 对字符串 s 进行排序,得到排序后的字符串。注意,这里返回的是一个字符列表
sorted_chars = sorted(s)
# 将排序后的字符列表转换为字符串。
# 这里使用 ''.join() 方法将列表中的字符拼接成一个新的字符串
sorted_s = ''.join(sorted_chars)
# 检查排序后的字符串 sorted_s 是否已经作为键存在于字典 anagrams 中
if sorted_s in anagrams:
# 如果存在,则将当前字符串 s 添加到该键对应的列表中
anagrams[sorted_s].append(s)
else:
# 如果不存在,则在字典 anagrams 中创建一个新的键 sorted_s,
# 并将其值设置为一个包含当前字符串 s 的新列表
anagrams[sorted_s] = [s]
# 字典 anagrams 的值(即所有字母异位词的列表)现在包含了所有分组的结果
# 使用 list() 函数将字典的 values 视图转换为列表,并返回这个列表
# 注意:这里的列表是包含多个列表的列表,每个内部列表代表一组字母异位词
return list(anagrams.values())
class Solution(object):
def longestConsecutive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 创建一个空集合来存储数组中的所有元素
num_set = set(nums)
longest_streak = 0
# 遍历数组中的每个元素(或遍历可能的序列起始点,但直接遍历数组更简单)
for num in nums:
# 如果当前数字减1不在集合中,说明它可能是某个连续序列的起点
if num - 1 not in num_set:
# 初始化当前序列的长度为1
current_num = num
current_streak = 1
# 尝试扩展当前序列,只要num+1还在集合中
while current_num + 1 in num_set:
current_num += 1
current_streak += 1
# 更新最长序列的长度
longest_streak = max(longest_streak, current_streak)
return longest_streak
双指针算法:
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
# 初始化一个指针left,指向数组的开始位置
# 这个指针用来追踪当前应该放置非零元素的位置
left = 0
# 使用for循环遍历整个数组nums
# right指针是循环变量,它的值在每次迭代中都会自动增加,从而遍历数组中的每个元素
for right in range(len(nums)):
# 检查当前right指针所指的元素是否不等于0
if nums[right] != 0:
# 如果不等于0,那么将该元素与left指针所指的元素进行交换
# 这确保了所有非零元素都被放置在数组的前面部分
nums[left], nums[right] = nums[right], nums[left]
# 交换之后,将left指针向后移动一位
# 这样,left指针就指向了下一个应该放置非零元素的位置
left += 1
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
# 初始化左指针指向数组的开始位置
left, right = 0, len(height) - 1
# 初始化最大面积为0
maxArea = 0
# 当左指针小于右指针时,继续循环
while left < right:
# 计算当前左右指针所构成容器的面积
# 容器面积 = 较小的高度 * 容器宽度(即左右指针之间的距离)
currentArea = min(height[left], height[right]) * (right - left)
# 更新最大面积
# 如果当前面积大于已知的最大面积,则更新maxArea
maxArea = max(maxArea, currentArea)
# 移动指向较短线的指针
# 这样做是因为容器的容量由较短的线决定,移动长线的指针并不能增加容器的容量
if height[left] < height[right]:
# 如果左线较短,则左指针右移
left += 1
else:
# 如果右线较短或两线等长(但按题意,我们总是先尝试移动左指针或右指针),
# 则右指针左移
right -= 1
# 当循环结束时,maxArea存储了容器可以容纳的最大水量
return maxArea
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 对数组进行排序,方便后续使用双指针法并去除重复的三元组
nums.sort()
result = [] # 用来存储结果的三元组列表
n = len(nums)
# 遍历数组中的每个元素作为三元组的第一个数
for i in range(n):
# 跳过重复的元素,避免产生重复的三元组
if i > 0 and nums[i] == nums[i - 1]:
continue
# 初始化双指针,left指向i的下一个位置,right指向数组末尾
left, right = i + 1, n - 1
# 当left小于right时,执行循环
while left < right:
# 计算当前三数之和
total = nums[i] + nums[left] + nums[right]
# 如果和为0,则找到一个三元组
if total == 0:
# 将找到的三元组添加到结果列表中
result.append([nums[i], nums[left], nums[right]])
# 移动left和right指针,同时跳过重复的元素
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
# 继续寻找下一个可能的三元组
left += 1
right -= 1
# 如果和小于0,说明需要增大总和,因此移动left指针向右
elif total < 0:
left += 1
# 如果和大于0,说明需要减小总和,因此移动right指针向左
else:
right -= 1
# 返回所有找到的三元组
return result
class Solution(object):
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
# 如果高度图为空或包含的柱子数量少于3个,则无法形成积水,直接返回0
if not height or len(height) < 3:
return 0
# 初始化左右指针,分别指向高度图的开始和结束位置
left, right = 0, len(height) - 1
# 初始化左右两侧的最大高度,初始时分别为左右指针所在位置的高度
left_max, right_max = height[left], height[right]
# 初始化积水量为0
volume = 0
# 当左指针小于右指针时,继续遍历
while left < right:
# 如果左侧最大高度小于右侧最大高度,说明左侧边界较低,限制了左侧的水量
if left_max < right_max:
# 计算从left到left_max之间的水量
# (注意:这里实际上是left_max - height[left],因为只考虑左侧边界)
# 并累加到总积水量中
volume += left_max - height[left]
# 将左指针向右移动一位
left += 1
# 更新左侧最大高度(如果新的高度更大)
left_max = max(left_max, height[left])
# 否则,如果右侧最大高度较小或相等,则处理右侧
else:
# 类似地,计算并累加右侧的水量
volume += right_max - height[right]
# 将右指针向左移动一位
right -= 1
# 更新右侧最大高度(如果新的高度更大)
right_max = max(right_max, height[right])
# 当左指针不再小于右指针时,说明已经遍历完所有可能形成积水的位置
# 返回总积水量
return volume
滑动窗口:
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
# 初始化最长无重复字符子串的长度为0
max_length = 0
# 初始化一个字典,用于存储字符最后出现的位置
char_map = {}
# 初始化窗口的左边界
left = 0
# 遍历字符串s中的每个字符
for right in range(len(s)):
# 如果当前字符已经存在于char_map中,并且它出现的位置在左边界的右侧
# 则说明这个字符破坏了当前窗口的唯一性,需要移动左边界
if s[right] in char_map and char_map[s[right]] >= left:
left = char_map[s[right]] + 1 # 更新左边界为重复字符的下一个位置
# 更新当前字符在char_map中的位置
char_map[s[right]] = right
# 计算当前窗口的长度,并更新最长长度
max_length = max(max_length, right - left + 1)
# 返回最长无重复字符子串的长度
return max_length
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
# 创建一个字典来存储字符串p中每个字符的出现次数
p_count = {}
for char in p:
p_count[char] = p_count.get(char, 0) + 1
# 初始化一个列表来存储所有满足条件的子串的起始索引
result = []
# 创建一个字典来记录当前窗口(子串)中每个字符的计数
current_count = {}
# left指针表示当前窗口的起始位置
left = 0
# 遍历字符串s
for right in range(len(s)):
# 如果当前字符在current_count中已存在,则增加其计数
# 否则,将当前字符添加到current_count中并初始化为1
if s[right] in current_count:
current_count[s[right]] += 1
else:
current_count[s[right]] = 1
# 当当前窗口的大小与p的长度相等时
if right - left + 1 == len(p):
# 检查current_count是否与p_count相等,即检查当前窗口是否是p的异位词
if current_count == p_count:
# 如果是,则将当前窗口的起始索引left添加到结果列表中
result.append(left)
# 准备移动窗口,首先减少窗口最左边字符的计数
current_count[s[left]] -= 1
# 如果最左边字符的计数变为0,则从current_count中删除该字符
if current_count[s[left]] == 0:
del current_count[s[left]]
# 移动窗口的左边界
left += 1
# 返回所有满足条件的子串的起始索引列表
return result