刷题
1. 无重复字符的最长子串
滑动窗口方法
滑动窗口的思路:利用左右指针表示字符串某个子串的左右边界。窗口是指左右指针形成的闭合区间。
题目:给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。分析:子串是连续的且要求不能出现重复的字符,考虑到使用两个指针划定区间,首先左指针确定开始位置,右指针依次移动直至出现重复字符或到达字符串尾部,每次移动一次右指针,哈希表中就要增加一个字符。然后依次移动左指针的位置直至到达字符串尾部,每次移动一次左指针,哈希表中就删除一个字符。在每次右指针移动结束后,记录以左指针开始的位置,右指针结束的位置。这就是在当前左指针开始位置的不包含重复字符的子串。
从上述分析可知,需要两层循环,外层循环用于移动左指针,内层循环用于移动右指针。除此之外,需要一个哈希表用于存储子串(哈希表中不能有重复元素)。
代码步骤
1)定义哈希表
hash
,用于存储子串。定义右指针r_k
和最长子串的长度ans
。2)两层循环遍历。外层循环遍历
[0:n)
左指针,内层循环遍历右指针(直至移动到字符串尾部或右指针所指字符出现在哈希表中)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
hash = set() # 哈希表,无重复字符
n = len(s)
r_k, ans = 0,0 # 右指针,结果
for l_k in range(n):
if l_k!=0: # 左指针,除第一轮外,均需从哈希表中去除第一位(因为,下方while的停止条件是,右指针发现了相同字符或已经访问完字符串)
hash.remove(s[l_k-1])
while r_k<n and s[r_k] not in hash:
# 右指针持续后移,直至字符串访问完毕或指针所指字符与哈希表中的字符相同
hash.add(s[r_k])
r_k+=1
ans = max(ans,r_k-l_k)
return ans
2. 最长回文子串
动态规划(DP)方法
DP的思路: 利用原问题与子问题的关系,将其变成 大问题的解 = 小问题的解的函数, 从而将问题变成size的扩展即可,当size到达最大后,原问题解决了。大问题划分为小问题
题目:给你一个字符串
s
,找到s
中最长的回文子串。分析:回文串去掉头尾字符后,仍然是回文串。即,若一个字符串的头尾字符不相等,那么这个字符串一定不是回文串。
大问题:字符串是否为回文串
小问题:字符串去掉头尾字符后,是否为回文串,且头尾字符必须相等
定义状态:
dp[i][j]
表示子串s[i...j]
是否为回文子串,s[i...j]
表示区间状态转移矩阵:
dp[i][j]
,由于s[i...j]
表示子串,因此j>=i
,只需填写矩阵的上三角状态转移方程:
dp[i][j]=dp[i+1,j-1] and (s[i]==s[j])
考虑两种情况:
1)当
s[i+1...j-1]
的长度小于2时(无法构成区间),即j-1-(i+1)+1<2
,推导为j-i<3
。s[i+1,j-1]
是一个字符或不存在,若s[i]==s[j]
,则S[i...j]
一定是回文子串。2)当
s[i+1...j-1]
的长度大于2时,则需要判断dp[i+1][j-1]
是否为True(s[i+1...j-1]
是否为回文子串)并且还需判断s[i]==s[j]
。
1 | class Solution: |
3. 字母异位词分组
HashMap方法
字母异位词:将字符串中的字符按照字母顺序排序后,得到相同的字母列表。例如’are’和’rae’,排序后都为’aer’
题目:给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
分析:HashMap的键存储排序后的字母组成的字符串,值存储一个列表(用于存储相同字母列表的字母异位词)
遍历每一个单词,通过排序建立HashMap的键
判断键是否存在于HashMap中,考虑两种情况:
1)存在,按照键,添加到对应的值列表中
2)不存在,键值添加到HashMap中。
注意:键不存在时,值用List存储,因为一个键可能对应多个值
1 | class Solution: |
4. 最长连续序列
HashMap方法
题目:给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求时间复杂度为O(n)。
分析:HashMap的键存储数组中的数字,值存储连续区间的长度
- 用哈希表存储每个端点值对应连续区间的长度
若数已在哈希表中:跳过不做处理
若是新数加入:
- 取出其左右相邻数已有的连续区间长度 left 和 right
- 计算当前数的区间长度为:cur_length = left + right + 1
- 根据 cur_length 更新最大长度 max_length 的值
- 更新区间两端点的长度值(因为连续区间内的中间数字不会再被访问)
注意:需要判断num是否在字典中,在的话说明该num有连续区间长度,无需计算
1 | class Solution: |
5. 移动零
双指针方法
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。要求时间复杂度为O(n)。
分析:指针i遍历整个数组,指针j记录当前有多少非0元素
- 第一次遍历,遍历整个数组。i指针遇到非0元素,就替换j指针所指元素且j指针后移。(保证j指针之前的元素都是非0元素)
- 第二次遍历,遍历j指针之后的数组。将所有元素置为0(因为非0元素已经替换到了j指针的前面)。
1 | class Solution: |
6. 盛最多水的容器
双指针方法
题目:给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。要求时间复杂度为O(n)。
分析:左指针left,右指针right
- 初始化左右指针,左指针为0,右指针为len(height)-1
- 遍历数组的条件是left<right
- 计算面积,长为两个指针对应的线中宽度最小的(木桶原理)min(height[left],height[right]),宽为两个指针的距离right-left
- 判断左右指针对应的线的宽度,哪个宽度小,哪个指针移动(寻找宽度大的线)
注意:循环的条件是左指针比右指针小,不用把整个List遍历
1 | class Solution: |
7. 三数之和
双指针方法
题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。
分析:一个循环用于遍历数组中的元素,固定住一个数nums[i],找寻两个数nums[i+1]和nums[n-1],满足两个数之和=0-nums[i]。双指针用于搜寻满足条件的两个数。构造一个列表reslut用于存储结果。
- 特殊判定,如果数组的大小<3,则返回[](不足三个数,不可能满足三数之和为0)
- 对数组进行排序(升序排序)
- 遍历排序后的数组
- 设定左右指针,开始和结束位置为当前顺序i的后一位到数组最后一位。
- 特殊判定:1)去除重复元素。2)当nums[i]>0时,说明数组后面的数值均>0,不可能再有满足三数之和为0的三个数。返回reslut。
- 双指针循环,while L<R
- 判断三数之和nums[i]+nums[L]+nums[R]
- 三数之和=0,将三个数加入到reslut列表中。去除重复元素,设置while,条件是L<R and L和L+1的元素相等/R和R-1的元素相等。相等的就移动指针L=L+1/R=R-1。双指针移动。
- 三数之和<=0,说明当前三数的和小,需要一个大一些的数,移动左指针。L=L+1
- 三数之和>0,说明当前三数的和大,需要一个小一些的数,移动右指针。R=R-1
- 判断三数之和nums[i]+nums[L]+nums[R]
1 | class Solution: |
8. 接雨水
双指针方法
题目:给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。分析:定义 left[i] 表示下标 i位置及其左边的最高柱子的高度,定义 right[i]表示下标 i位置及其右边的最高柱子的高度。那么下标 i位置能接的雨水量为 min(left[i],right[i])−height[i]。我们遍历数组,计算出 left[i] 和 right[i],最后答案为 $\sum \limits_{i=0}^{n-1}=min(left[i],right[i])−height[i]$
- 初始化左右指针
- 遍历数组(从1开始到n-1)
- 计算left[i],左指针从1开始,到n-1结束。因为0位置没有左柱子
- 计算right[n-i-1],右指针从n-2开始,到0结束。因为n-1位置没有右柱子
- 遍历left,right,height。
- 计算每个柱子的雨水量。min(left[i],right[i])-height[i]
1 | class Solution: |
9. 找到字符串中所有字母异位词
滑动窗口方法
题目:给定两个字符串
s
和p
,找到s
中所有p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
分析:采用滑动窗口的模板(遍历右指针,左指针移动)
1 | class Solution: |
10. 最小覆盖子串
滑动窗口方法
题目:给你一个字符串
s
、一个字符串t
。返回s
中涵盖t
所有字符的最小子串。如果s
中不存在涵盖t
所有字符的子串,则返回空字符串""
。分析:采用滑动窗口的模板(遍历右指针,左指针移动)
1 | class Solution: |
11. 有效的括号
栈方法
题目:给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
分析:栈的特点是先入后出,括号配对题目也满足先入后出特点(因为当出现右括号时,如果想要配对,栈顶的元素一定是与其对应的左括号,并且这个元素一定是距离当前右括号最近的左括号)。
初始化字典(存储左右括号的配对,键为左括号,值为右括号)和栈(存储左括号)
循环遍历字符串s。
- 当s为左括号时入栈;
- 当s为右括号时出栈,并比较出栈元素是否与左括号配对。不配对则返回False
- 最终判断栈的长度是否为1,因为初始化栈时有一个元素’?’(便于判断第一个元素是右括号的情况,这种情况需要栈中有元素,否则无法出栈)
注意:因为考虑到第一个字符就是右括号的情况,所以栈初始化时有一个元素,并且字典中也有它的配对。
1 | class Solution: |