记录题解和思路。
思路:
创建哈希表: 初始化了一个空字典来存储已经访问过的数字及其对应的索引。
遍历数组: 逐一遍历数组中的每个元素。在遍历过程中,针对每个元素 num
,计算出它需要的配对数,即 target - num
。
查找配对: 检查哈希表中是否存在这个配对数。如果存在,说明找到了目标数对,此时返回这两个数的索引(即哈希表中存储的索引和当前数的索引)。
更新哈希表: 如果没有找到对应的配对数,将当前数 num
和它的索引存入哈希表中,以备后续查找使用。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashtable=dict()
for i,num in enumerate(nums):
if target-num in hashtable:
return [hashtable[target-num],i]
hashtable[num]=i
return []
时空复杂度都是O(n) 。
enumerate
是 Python 内置的一个非常有用的函数,它用于在遍历可迭代对象(如列表、元组或字符串)时,同时获取元素的索引和值。
enumerate(iterable, start=0)
思路:
字母异位词是由相同字符组成的不同排列形式。我们可以利用这一特性,通过对每个字符串的字符进行计数,将相同字符组成的字符串归到同一组中。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
mp = collections.defaultdict(list)
for st in strs:
counts = [0] * 26
for ch in st:
counts[ord(ch) - ord("a")] += 1
# 需要将 list 转换成 tuple 才能进行哈希
mp[tuple(counts)].append(st)
return list(mp.values())
时空复杂度为O(nk),n是字符串个数,k是字符串的平均长度。
可以利用集合(set)的特性高效地判断某个数字是否存在。具体而言,对于每个数字,如果它的前驱(即该数字减去 1)不存在,那么这个数字可能是某个连续序列的起点。我们通过迭代来寻找最长的连续序列。
longest_streak
用来记录最长的连续序列长度,同时将输入的数组转换为集合 num_set
以便快速查找。num
。如果 num-1
不在集合中,说明 num
是某个连续序列的起点。num+1
、num+2
等),直到找不到为止。记录当前序列的长度。longest_streak
进行比较,如果当前序列更长,则更新 longest_streak
。longest_streak
作为最长的连续序列长度。class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
longest_streak = 0
num_set = set(nums)
for num in num_set:
if num - 1 not in num_set:
current_num = num
current_streak = 1
while current_num + 1 in num_set:
current_num += 1
current_streak += 1
longest_streak = max(longest_streak, current_streak)
return longest_streak
时空复杂度为O(n)。
一个指针遍历数组,另一个指针记录非零元素应该放置的位置。
left
和 right
。left
用于跟踪下一个非零元素应该放置的位置,而 right
用于遍历数组中的每个元素。right
指针从数组的第一个元素开始,遍历整个数组。right
指针指向的元素不是 0,那么我们将该元素与 left
指针指向的位置交换,同时将 left
指针右移一位。right
指针无论如何都会继续右移。class Solution:
def moveZeroes(self, nums: List[int]) -> None:
n = len(nums)
left = right = 0
while right < n:
if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right += 1
时间复杂度是O(n),空间复杂度是O(1)。
通过双指针的方法来解决这一问题:指针 l
从数组左端开始,指针 r
从数组右端开始,通过计算两个指针间形成的矩形面积来找到最大容积。每次根据两边柱子高度的比较移动指针,以期找到可能更大的容积。
l
指向数组的左端,r
指向数组的右端。同时,初始化 ans
变量用于记录当前找到的最大容积。l < r
的条件下,计算 l
和 r
指向的柱子形成的容积。使用较小的柱子高度乘以指针间的距离来计算面积。ans
。ans
,即最大容积。class Solution:
def maxArea(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
ans = 0
while l < r:
area = min(height[l], height[r]) * (r - l)
ans = max(ans, area)
if height[l] <= height[r]:
l += 1
else:
r -= 1
return ans
时间复杂度是O(n),空间复杂度是O(1)。
我们首先对数组进行排序,然后通过固定一个元素,再在剩下的部分中使用双指针寻找符合条件的另外两个元素,从而找到所有满足条件的三元组。
a
,对于每一个固定的数,我们希望找到另外两个数 b
和 c
,使得 a + b + c = 0
。a
后,我们使用双指针的方法在剩下的部分寻找 b
和 c
。其中,一个指针从左侧开始,另一个指针从右侧开始,并根据三者的和来调整指针的位置。class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
nums.sort()
ans = list()
# 枚举 a
for first in range(n):
# 需要和上一次枚举的数不相同
if first > 0 and nums[first] == nums[first - 1]:
continue
# c 对应的指针初始指向数组的最右端
third = n - 1
target = -nums[first]
# 枚举 b
for second in range(first + 1, n):
# 需要和上一次枚举的数不相同
if second > first + 1 and nums[second] == nums[second - 1]:
continue
# 需要保证 b 的指针在 c 的指针的左侧
while second < third and nums[second] + nums[third] > target:
third -= 1
# 如果指针重合,随着 b 后续的增加
# 就不会有满足 a+b+c=0 并且 b
时间复杂度是O(N²),空间复杂度是O(logN)
接雨水问题的核心在于计算每个位置上方可以存储的水量。对于每个位置,能够存储的水量取决于它左边和右边最高柱子的较小值减去当前位置的高度。
参考:【盛最多水的容器 接雨水】https://www.bilibili.com/video/BV1Qg411q7ia?vd_source=b93b9c2a7846dd3e8bd33feb227c4ac5
为了快速计算每个位置的左侧最大高度和右侧最大高度,可以分别构建前缀最大值数组和后缀最大值数组。然后遍历每个位置,利用这两个数组计算水量并累加。
prefix[i]
表示从左到右扫描数组时,位置 i
及其左侧的最大高度。suffix[i]
表示从右到左扫描数组时,位置 i
及其右侧的最大高度。prefix
和 suffix
数组中的值,计算当前位置可以存储的水量并累加。第i个柱子能存储的水量取决于它左侧柱子的最大高度和右侧柱子的最大高度中的较小值。要在当前位置存储水量,必须形成两侧高中间低的“低洼”形态。根据“短板效应”,存水量取决于两侧高度中较低的那一侧。
以图中粉线框柱的部分举例,柱子的高度为1,它左侧高度最大值为2,右侧高度最大值是3,此时形成了一个低洼。接下来考虑水量存储,如果存水高度高于了左侧最高点,那么水会从左侧溢出,因此限制存水量的就是现在左侧的最高点2,所以当前位置的存水量是2-1=1。
因此,用公式表达第i个位置的存水量为:min(leftmax,rightmax)-height[i],按照这个思路就可以得到以下代码:
class Solution:
def trap(self, height: List[int]) -> int:
ans=0;n=len(height)
prefix=[0]*n;prefix[0]=height[0]
suffix=[0]*n;suffix[-1]=height[-1]
for i in range(1,n):
prefix[i]=max(prefix[i-1],height[i])
for i in range(n-2,-1,-1):
suffix[i]=max(suffix[i+1],height[i])
for h,pre,suf in zip(height,prefix,suffix):
ans+=min(pre,suf)-h
return ans
时间复杂度和空间复杂度都是O(n)。
left
和 right
,分别指向数组的左右两端。此外,还需要两个变量 leftMax
和 rightMax
,用于记录当前左边和右边的最大高度。ans
变量用于累计雨水总量。left
指针小于 right
指针的情况下,逐步遍历数组。leftMax
和 rightMax
,分别表示当前左侧和右侧的最大高度。height[left]
小于 height[right]
,说明左侧的最大高度决定了当前能存储的雨水量,雨水量为 leftMax - height[left]
,然后左指针右移。否则,右侧的最大高度决定当前能存储的雨水量,雨水量为 rightMax - height[right]
,然后右指针左移。ans
中存储了整个柱子之间可以储存的总雨水量,返回该值。利用双指针,从左向右和从右向左两个方向遍历数组。维护两个变量指示左右两侧最大高度,一个是leftmax用来指示当前left指针所指位置及其左侧的最高高度,一个是rightmax用来指示当前right指针所指位置及其右侧的最高高度。
在Left小于right的情况下遍历数组,如果height[left] 使用滑动窗口,滑动窗口通过两个指针来动态维护一个子串,保证子串内的字符没有重复。在遍历过程中,动态调整窗口的大小,并记录最长的无重复子串长度。 初始化:使用一个字典 扩展窗口:右指针 收缩窗口:如果加入的字符在窗口中出现次数超过一次,说明有重复字符。通过移动左指针 更新结果:每次调整窗口后,计算当前窗口的长度,并更新 继续扩展:移动右指针,继续扩展窗口,直到遍历完整个字符串。 通过滑动窗口,我们可以将 不用设置left了,因为窗口大小是固定的,只移动一个指针就可以了。 时间复杂度:O(n+m+Σ),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,其中Σ 为所有可能的字符数。空间复杂度:O(Σ)。用于存储滑动窗口和字符串 p 中每种字母数量的差。 整体思路是利用了前缀和,求和为k的连续子数组。如果我们知道了两个前缀和 初始化: 遍历数组: 返回结果:class Solution:
def trap(self, height: List[int]) -> int:
ans=0;n=len(height)
left,right=0,n-1
leftmax,rightmax=0,0
while left
三、滑动窗口
1、无重复字符的最长子串
char
来记录当前窗口内每个字符的出现次数。两个指针 left
和 right
分别指向窗口的左边界和右边界。res
记录最长无重复子串的长度。right
向右移动,将新字符加入窗口,并更新其在字典中的计数。left
来收缩窗口,直到窗口内没有重复字符。res
,确保它始终记录最大长度。class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
char=collections.defaultdict(int)
n=len(s);res=0
left,right=0,0
while right
注:滑动窗口问题模板
//外层循环扩展右边界,内层循环扩展左边界
for (int l = 0, r = 0 ; r < n ; r++) {
//当前考虑的元素
while (l <= r && check()) {//区间[left,right]不符合题意
//扩展左边界
}
//区间[left,right]符合题意,统计相关信息
}
2、找到字符串中所有字母异位词
p
的长度作为窗口的大小,在 s
上滑动窗口,逐步检查当前窗口是否为 p
的异位词。class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
n, m = len(s), len(p)
cntp = Counter(p)
cnts = Counter()
ans = []
for i, x in enumerate(s):
cnts[x]+=1
if i+1>=m:
if cntp == cnts:
ans.append(i+1-m)
cnts[s[i+1-m]]-=1
return ans
三、子串
1、和为k的子数组
prefix[j]
和 prefix[i]
,并且 prefix[j]-prefix[i]==k
,那么说明从 i + 1
到 j
之间的子数组的和为 k
。
pre
:用于记录当前的前缀和,即从数组起始位置到当前元素的累加和。count
:用于记录和为 k
的子数组的数量。mp
:一个 defaultdict
(默认值为 0
),用于记录前缀和出现的次数。初始时,mp[0] = 1
,这是为了处理从数组起始位置开始的子数组情况。
num
,更新前缀和 pre
。pre - k
是否存在于哈希表 mp
中。如果存在,说明从之前某个位置到当前元素的子数组和为 k
,因此累加 mp[pre - k]
到 count
中。【因为数组中的元素并非都是正值,因此可能有多个位置的前缀和相同】pre
的出现次数记录在哈希表 mp
中,以便后续使用。
count
,即满足条件的子数组数量。class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
pre=0;count=0
mp=collections.defaultdict(int)
mp[0]=1
for num in nums:
pre+=num
if pre-k in mp:
count+=mp[pre-k]
mp[pre]+=1
return count
2、滑动窗口内找最大值
deque
来存储当前窗口中的元素索引,deque
中的元素按它们在 nums
中对应值的大小递减排列,确保 deque
的头部始终是当前窗口的最大值。deque
中的元素有效并且顺序正确。class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n=len(nums);ans=[]
q=collections.deque()
i=0
while i