感觉或许写思路比较好,便于梳理总结思路,在文中会写出自己的第一思路,以及优化之后的,说人话,就是啰嗦,所以本打算设置私密的,但是发现私密不太友好,不能在自己博客上直接出来,标签归档啥的也不友好,所以设成公开,若你不小心看见了,觉得我很啰嗦,可以默默叉掉。
目录
340. 至多包含 K 个不同字符的最长子串
485. 最大连续1的个数
487. 最大连续1的个数 II
1004. 最大连续1的个数 III
424. 替换后的最长重复字符
992. K 个不同整数的子数组
438. 找到字符串中所有字母异位词
76. 最小覆盖子串
567. 字符串的排列
1313. 解压缩编码列表
https://leetcode-cn.com/problems/longest-substring-with-at-most-k-distinct-characters/
给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。
示例 1:输入: s = "eceba", k = 2,输出: 3,解释: 则 T 为 "ece",所以长度为 3。
示例 2:输入: s = "aa", k = 1,输出: 2,解释: 则 T 为 "aa",所以长度为 2。
思路
一:159 - 至多包含两个不同字符的最长子串的解法二,只要把3改成k即可。
from collections import defaultdict
class Solution(object):
def lengthOfLongestSubstringTwoDistinct(self, s):
"""
:type s: str
:rtype: int
"""
n = len(s)
if n < 3:
return n
l, r, res, rec = 0, 0, 0, defaultdict(int)
while r < n:
if len(rec) < 3:
rec[s[r]] = r
r += 1
if len(rec) == 3:
del_idx = min(rec.values())
del rec[s[del_idx]]
l = del_idx + 1
res = max(res, r - l)
return res
https://leetcode-cn.com/problems/max-consecutive-ones/
给定一个二进制数组, 计算其中最大连续1的个数。
示例 1:输入: [1,1,0,1,1,1],输出: 3,解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
注意:输入的数组只包含 0 和1。输入数组的长度是正整数,且不超过 10,000。
思路
一:一次遍历,遇到1,count加1,并与res比较确定是否需要更新;若为0,抛弃原来的计数count,令count=0。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
res, count = 0, 0
for i in range(len(nums)):
if nums[i] == 1:
count += 1
res = max(res, count)
else:
count = 0
return res
二:copy一下官方解答,https://leetcode-cn.com/problems/max-consecutive-ones/solution/zui-da-lian-xu-1de-ge-shu-by-leetcode/,在 Python 中可以使用 map
和 join
来解决此问题。使用 splits
函数在 0
处分割将数组转换成字符串。在获取子串的最大长度就是最大连续 1
的长度。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
# print("".join(map(str,nums)).split('0'))
return max(map(len, "".join(map(str,nums)).split('0')))
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
if not nums:
return 0
l, r, res, nearst = 0, 0, 0, -1
while r < len(nums):
if nums[r] == 0:
nearst = r
res = max(res, r - l)
r += 1
l = max(l, nearst + 1)
res = max(res, r - l)
return res
https://leetcode-cn.com/problems/max-consecutive-ones-ii/submissions/
给定一个二进制数组,你可以最多将 1 个 0 翻转为 1,找出其中最大连续 1 的个数。
示例 1:输入:[1,0,1,1,0],输出:4,解释:翻转第一个 0 可以得到最长的连续 1。当翻转以后,最大连续 1 的个数为 4。
注:输入数组只包含 0 和 1.输入数组的长度为正整数,且不超过 10,000
进阶:如果输入的数字是作为 无限流 逐个输入如何处理?换句话说,内存不能存储下所有从流中输入的数字。您可以有效地解决吗?
思路
一:时间复杂度O(n),rec[i]-表示以i结尾的最大连续1的个数(最多将1个0变为1)。nums[i]若是1,则直接能接到前一个元素的后面;否则,需要寻找到最近的一个零元素的下一个位置,这其中元素的个数count就是以i结尾的最大连续1的个数(最多将1个0变为1)。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
rec = [1] * len(nums)
for i in range(1, len(nums)):
if nums[i] != 0:
rec[i] = rec[i-1] + 1
else:
count, j = 1, i-1
while j >= 0:
if nums[j] == 0:
break
count += 1
j -= 1
rec[i] = count
return max(rec)
二:时间复杂度O(n),滑动窗口,r是正考虑的元素,确保[l,r)中至多只有一个0。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
if not nums:
return 0
l, r, count, res = 0, 0, 0, 0
while r < len(nums):
if nums[r] != 0:
r += 1
elif count == 0:
count = 1
r += 1
else:
while nums[l] != 0:
l += 1
l += 1
count = 0
res = max(res, r - l)
return res
三:改善前两种方法,因为前两种方法,均需要挪动左指针,此处记录离的最近的一个0元素的下标,无需一个个挪动左指针。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
res, count, last_zero = 0, 0, -1
for i in range(0, len(nums)):
if nums[i] != 0:
count += 1
else:
res = max(res, count)
count = i - last_zero
last_zero = i
res = max(res, count)
return res
https://leetcode-cn.com/problems/max-consecutive-ones-iii/
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。返回仅包含 1 的最长(连续)子数组的长度。
示例 1:输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2,输出:6,解释: [1,1,1,0,0,1,1,1,1,1,1],粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3,输出:10,解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:1 <= A.length <= 20000;0 <= K <= A.length;A[i] 为 0 或 1
思路
一:双指针,滑动窗口,确保[l,r)中最多只有K个0元素,这三道题均可这么理解,确保窗口中的元素符合要求。直觉k=0,可能会出错,最终通过了,但是感觉也不太逻辑,此处当K为0时会出现l>r的情况,但是好在l>r时,zero_num为-1,即下一次一定会添加元素,即下一次一定会追平,至少l<=r,所以与结果无碍,但终究不甚合乎逻辑。
class Solution(object):
def longestOnes(self, A, K):
"""
:type A: List[int]
:type K: int
:rtype: int
"""
res, zero_num, l, r = 0, 0, 0, 0
while r < len(A):
if A[r] != 0:
r += 1
elif zero_num < K:
r += 1
zero_num += 1
else:
while l < r and A[l] != 0:
l += 1
l += 1
zero_num -= 1
res = max(res, r - l)
return res
二:先添加,再剔除的方法,依旧是[l,r)中保存的符合条件的元素。
class Solution(object):
def longestOnes(self, A, K):
res, zero_num, l, r = 0, 0, 0, 0
while r < len(A):
if A[r] == 0:
zero_num += 1
r += 1
if zero_num > K:
while A[l] != 0:
l += 1
l += 1
zero_num -= 1
res = max(res, r - l)
return res
class Solution(object):
def longestOnes(self, A, K):
if len(A) <= K:
return len(A)
cnt, res = 0, 0
l = 0
for r in range(len(A)):
if A[r] != 0:
res = max(res, r - l + 1)
elif cnt < K:
cnt += 1
else:
cnt += 1
while A[l] != 0:
l += 1
l += 1
cnt -= 1
res = max(res, r - l + 1)
return res
https://leetcode-cn.com/problems/longest-repeating-character-replacement/
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。
注意:字符串长度 和 k 不会超过 104。
示例 1:输入:s = "ABAB", k = 2,输出:4,解释:用两个'A'替换为两个'B',反之亦然。
示例 2:输入:s = "AABABBA", k = 1,输出:4,解释:将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。子串 "BBBB" 有最长重复字母, 答案为 4。
思路
一:滑动窗口,最近发现先添加,再依据条件排除,即每次右指针均向右挪动,然后判断窗口是否满足条件,满足条件更新res,不满足调整窗口大小,即挪动左指针。这边我们精确的寻找需替换的字母个数num,即窗口中所有字母个数减去频率最高的字母的个数直至小于等于k,即满足窗口条件,更新res。
from collections import defaultdict
class Solution(object):
def characterReplacement(self, s, k):
"""
:type s: str
:type k: int
:rtype: int
"""
if len(s) <= k:
return len(s)
rec, l = defaultdict(int), 0
res = 1
for r in range(len(s)):
rec[s[r]] += 1
num = sum(rec.values()) - max(rec.values())
if num <= k:
res = max(res, r - l + 1)
else:
while num > k:
rec[s[l]] -= 1
l += 1
num = sum(rec.values()) - max(rec.values())
res = max(res, r - l + 1)
return res
二:参考leetcode大神题解,其实我们只需要找出替换后最长的重复字符串。这边先解释一下,max_freq:其记录的是窗口中出现的字母的最高频率(历史上),另一方面他也控制了窗口的增长,若我们新增加的字符并没有提高max_freq,则窗口长度需要维持不变,但是由于右指针右移了,故左指针也需右移,若提高了max_freq,则窗口长度可以变长,即左指针无需右移。因为每次添加一个字符,收缩最多收缩一个字符所以用的是if。其实法一中每个窗口都符合条件,但是法二中只有第一次扩大窗口长度的窗口一定是符合条件的(即下图中绿色的三个,每次更新max_freq的时候),其他的未必。好好理解哈,若太绕,就用方法一解吧,毕竟限制了只有26个字母,字典的遍历就是常数级别的,故还是O(n)时间复杂度。甩题解链接 https://leetcode-cn.com/problems/longest-repeating-character-replacement/solution/。
from collections import defaultdict
class Solution(object):
def characterReplacement(self, s, k):
if len(s) <= k:
return len(s)
rec, l = defaultdict(int), 0
res = 1
for r in range(len(s)):
rec[s[r]] += 1
num = sum(rec.values()) - max(rec.values())
if num <= k:
res = max(res, r - l + 1)
else:
rec[s[l]] -= 1
l += 1
res = max(res, r - l + 1)
return res
https://leetcode-cn.com/problems/subarrays-with-k-different-integers/
给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。
(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)
返回 A 中好子数组的数目。
示例 1:输出:A = [1,2,1,2,3], K = 2,输入:7,解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].
示例 2:输入:A = [1,2,1,3,4], K = 3,输出:3,解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4].
提示:1 <= A.length <= 20000,1 <= A[i] <= A.length,1 <= K <= A.length
思路
一:先暴力了一波,两重循环,有提前终止,O(n^2)的时间复杂度,但是没通过。
class Solution(object):
def subarraysWithKDistinct(self, A, K):
"""
:type A: List[int]
:type K: int
:rtype: int
"""
if len(A) < K:
return 0
n, res = len(A) - K + 1, 0
for i in range(n):
rec = set()
for j in range(i, len(A)):
if (len(rec) < K) or (len(rec) == K and A[j] in rec):
rec.add(A[j])
else:
break
if len(rec) == K:
res += 1
return res
二:该题与通常的滑动窗口不太一样的地方在于是计数,而不是求最长最短。以[1,2,1,2,3]为例,[1,2,1]中以A[r]结尾的子数组还有[2,1],[1,2,1,2]中以A[r]结尾的子数组还有[2,1,2],[1,2],关键是如何将这些考虑进去。该方法中,每次右指针均会挪一格,若不满足条件(窗口中不同元素个数大于K,则挪动左指针直到符合要求),在符合要求的窗口中(不同元素个数为k个),再继续寻找以r结尾的所有可行解,但是不能改变左指针的位置,此处用一个新的下标来做这件事。
from collections import defaultdict
class Solution(object):
def subarraysWithKDistinct(self, A, K):
if len(A) < K:
return 0
l, r, res, rec= 0 , 0, 0, defaultdict(int)
while r < len(A):
rec[A[r]] += 1
while len(rec) > K:
rec[A[l]] -= 1
if rec[A[l]] == 0:
del rec[A[l]]
l += 1
if len(rec) == K:
tmp_l = l
while len(rec) == K:
res += 1
rec[A[tmp_l]] -= 1
if rec[A[tmp_l]] == 0:
break
tmp_l += 1
while tmp_l >= l:
rec[A[tmp_l]] += 1
tmp_l -= 1
r += 1
return res
https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/submissions/
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:字母异位词指字母相同,但排列不同的字符串。不考虑答案输出的顺序。
示例 1:输入:s: "cbaebabacd" p: "abc",输出:[0, 6],解释:起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
示例 2:输入:s: "abab" p: "ab",输出:[0, 1, 2],解释:起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
思路
一:滑动窗口,此处窗口是[l,r],窗口大小固定(是字符串p的长度),因窗口大小固定,窗口长度就是p的长度,即右指针移动到某个位置后,左指针必须一同移动,且每次移动也都是一格。
from collections import defaultdict
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
if not s or not p or len(s) < len(p):
return []
l, r, n = 0, 0, len(p)
rec_p, rec_s, result = defaultdict(int), defaultdict(int), []
for item in p:
rec_p[item] += 1
# 窗口[l,r]
while r < len(s):
rec_s[s[r]] += 1
r += 1
if r >= n:
if self._same(rec_s, rec_p):
result.append(l)
rec_s[s[l]] -= 1
if rec_s[s[l]] == 0:
del rec_s[s[l]]
l += 1
return result
def _same(self, s, p):
for k, v in s.items():
if v != p[k]:
return False
return True
from collections import Counter
class Solution(object):
def findAnagrams(self, s, p):
if not p or not s or len(s) < len(p):
return []
rec, n, l = Counter(p), len(p), 0
window, res = {}, []
for r in range(len(s)):
window[s[r]] = window.get(s[r], 0) + 1
if r - l + 1 == n:
if self._check(window, rec):
res.append(l)
window[s[l]] -= 1
l += 1
return res
def _check(self, window, rec):
for k, v in rec.items():
if window.get(k) != v:
return False
return True
二:同样的滑动窗口,这个是参照网上一个大神的模板写出来的https://mp.weixin.qq.com/s/6YeZUCYj5ft-OGa85sQegw,以下同样转自大神文章,首先窗口是固定的,窗口长度就是输入参数中第二个字符串的长度,也就是说,右指针移动到某个位置后,左指针必须跟着一同移动,且每次移动都是一格,模版中 count 用来记录窗口内满足条件的元素,直到 count 和窗口长度相等即可更新答案。相比于方法一,该方法优化了判断是否是异位词的方法,尽管时间复杂度依旧是O(n)。
from collections import defaultdict
class Solution(object):
def findAnagrams(self, s, p):
# 输入参数有效性判断
if not s or not p or len(s) < len(p):
return []
l, r, n = 0, 0, len(p)
hash_table, result, count = defaultdict(int), [], 0
# 申请一个散列,用于记录窗口中具体元素的个数情况
for item in p:
hash_table[item] += 1
# 窗口[l,r]
# l 表示左指针
# count 记录当前的条件,具体根据题目要求来定义
# result 用来存放结果
while r < len(s):
# 更新新元素在散列中的数量
hash_table[s[r]] -= 1
# 根据窗口的变更结果来改变条件值
if hash_table[s[r]] >= 0:
count += 1
# 如果当前条件不满足,移动左指针直至条件满足为止
# 这里的条件指窗口的长度必须固定为字符串p的长度
if r > n - 1:
hash_table[s[l]] += 1
if hash_table[s[l]] > 0:
count -= 1
l += 1
r += 1
# 更新结果
if count == n:
result.append(l)
return result
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:输入: S = "ADOBECODEBANC", T = "ABC",输出: "BANC",说明:如果 S 中不存这样的子串,则返回空字符串 ""。如果 S 中存在这样的子串,我们保证它是唯一的答案。
思路
一:
from collections import Counter
class Solution(object):
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
if not t or not s or len(s) < len(t):
return ""
rec, l, n = Counter(t), 0, len(t)
local_rec = {}
res = s + "O"
for r in range(len(s)):
local_rec[s[r]] = local_rec.get(s[r], 0) + 1
if r - l + 1 >= n:
while self._check(local_rec, rec):
if r - l + 1 < len(res):
res = s[l: r + 1]
local_rec[s[l]] -= 1
l += 1
if len(res) > len(s):
return ""
return res
def _check(self, local_rec, rec):
for k, v in rec.items():
if local_rec.get(k, 0) < v:
return False
return True
二:每一次保证右指针向前挪一格。若满足条件(得到可行性窗口),即窗口中包含 T 所有字母,则把左指针往前挪动若得到的窗口依然可行,则更新最小窗口大小。若窗口不再可行挪动右指针。
from collections import defaultdict
class Solution(object):
def minWindow(self, s, t):
if not s or not t or len(s) < len(t):
return ""
hash_rec = defaultdict(int)
for c in t:
hash_rec[c] += 1
l, r, count = 0, 0, 0
res_len, res_l, res_r = len(s) + 1, 0, 0
while r < len(s):
hash_rec[s[r]] -= 1
if hash_rec[s[r]] >= 0:
count += 1
while count >= len(t):
if count >= len(t):
if res_len > r - l + 1:
res_len = r - l + 1
res_l, res_r = l, r
hash_rec[s[l]] += 1
if hash_rec[s[l]] > 0:
count -= 1
l += 1
if count >= len(t):
if res_len > r - l + 1:
res_len = r - l + 1
res_l, res_r = l, r
r += 1
if res_len > len(s):
return ""
return s[res_l: res_r + 1]
https://leetcode-cn.com/problems/permutation-in-string/
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:输入: s1 = "ab" s2 = "eidbaooo",输出: True,解释: s2 包含 s1 的排列之一 ("ba").
示例2:输入: s1= "ab" s2 = "eidboaoo",输出: False
注意:输入的字符串只包含小写字母,两个字符串的长度都在 [1, 10,000] 之间
思路
一:其实该题与438. 找到字符串中所有字母异位词几乎完全一样,同样的代码改一下变量即可,此处题解仿的是438的方法二。
from collections import defaultdict
class Solution(object):
def checkInclusion(self, s1, s2):
"""
:type s1: str
:type s2: str
:rtype: bool
"""
if not s1:
return True
if len(s2) < len(s1):
return False
hash_rec = defaultdict(int)
for c in s1:
hash_rec[c] += 1
l, r, count = 0, 0, 0
while r < len(s2):
hash_rec[s2[r]] -= 1
if hash_rec[s2[r]] >= 0:
count += 1
if r >= len(s1):
hash_rec[s2[l]] += 1
if hash_rec[s2[l]] > 0:
count -= 1
l += 1
if count == len(s1):
return True
r += 1
return False
https://leetcode-cn.com/problems/decompress-run-length-encoded-list/
给你一个以行程长度编码压缩的整数列表 nums 。考虑每对相邻的两个元素 [a, b] = [nums[2*i], nums[2*i+1]] (其中 i >= 0 ),每一对都表示解压后有 a 个值为 b 的元素。请你返回解压后的列表。
示例:输入:nums = [1,2,3,4],输出:[2,4,4,4],解释:第一对 [1,2] 代表着 2 的出现频次为 1,所以生成数组 [2]。第二对 [3,4] 代表着 4 的出现频次为 3,所以生成数组 [4,4,4]。最后将它们串联到一起 [2] + [4,4,4] = [2,4,4,4]。
提示:2 <= nums.length <= 100,nums.length % 2 == 0,1 <= nums[i] <= 100
思路
一
class Solution(object):
def decompressRLElist(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
res = []
for i in range(0, len(nums), 2):
res.extend([nums[i + 1]] * nums[i])
return res