Leetcode随机抽题检测--使用题库:Leetcode热题100
- [1 两数之和](#1 两数之和)
- [49 字母异位词分组](#49 字母异位词分组)
- [128 最长连续序列](#128 最长连续序列)
-
- 未看解答自己编写的青春版
- 重点
-
- [关于 left 和 right 的疑问?](#关于 left 和 right 的疑问?)
- [为什么只需要更新 num-left 和 num+right ?](#为什么只需要更新 num-left 和 num+right ?)
- 题解的代码
- 日后再次复习重新写
- [283 移动零](#283 移动零)
- [11 盛最多水的容器](#11 盛最多水的容器)
- [15 三数之和](#15 三数之和)
- [42 接雨水](#42 接雨水)
-
- 未看解答自己编写的青春版
- 重点
- [自己理解后的代码,这部分代码都是按我自己的风格编写的,会和题解有一些出入,比如大部分题解中,位置 i 的左右最大高度,初始值就是height[i],考虑了自己,但我没有考虑自己,初始值是0](#自己理解后的代码,这部分代码都是按我自己的风格编写的,会和题解有一些出入,比如大部分题解中,位置 i 的左右最大高度,初始值就是height[i],考虑了自己,但我没有考虑自己,初始值是0)
-
- [要注意这种双指针法的编写逻辑,和上一题,"盛最多水的容器",相似。如何确定是移动 left 还是 right ? 看当前 mexleft 和 maxright 哪个小。因为我们在计算当前位置所能接的雨水时,首先要判断 min(maxleft , maxright) ,那么当前应该计算的雨水位置,就是小值的那一侧,假如maxleft小,那么不管后面的循环中,maxright再如何变化,在当前left处,min(maxleft , maxright) 的值不会再变化了(注意,当前left位置的maxleft,是由该位置之前的元素值决定的),所以,如果maxleft小,那么就计算left处的雨水,然后更新maxleft,然后移动left。right同理。](#要注意这种双指针法的编写逻辑,和上一题,"盛最多水的容器",相似。如何确定是移动 left 还是 right ? 看当前 mexleft 和 maxright 哪个小。因为我们在计算当前位置所能接的雨水时,首先要判断 min(maxleft , maxright) ,那么当前应该计算的雨水位置,就是小值的那一侧,假如maxleft小,那么不管后面的循环中,maxright再如何变化,在当前left处,min(maxleft , maxright) 的值不会再变化了(注意,当前left位置的maxleft,是由该位置之前的元素值决定的),所以,如果maxleft小,那么就计算left处的雨水,然后更新maxleft,然后移动left。right同理。)
- [本题要让0入栈,不让0入栈是不自然的,然后计算当前能承接水的体积的方式是:当前最外层遍历的元素是 i , 叫做right ,pop()出来的元素是 mid (这个值也是下标) , 当前单调栈stack内 (pop后的单调栈,单调栈的栈顶元素 stack[-1] ),此时的栈顶元素 stack[-1],叫做 left (这个值也是下标) 。计算以当前位置 mid 为底,所能承载的水的体积是:(min(nums[left] , nums[right]) - nums[mid]) * (right-left-1) 。](#本题要让0入栈,不让0入栈是不自然的,然后计算当前能承接水的体积的方式是:当前最外层遍历的元素是 i , 叫做right ,pop()出来的元素是 mid (这个值也是下标) , 当前单调栈stack内 (pop后的单调栈,单调栈的栈顶元素 stack[-1] ),此时的栈顶元素 stack[-1],叫做 left (这个值也是下标) 。计算以当前位置 mid 为底,所能承载的水的体积是:(min(nums[left] , nums[right]) - nums[mid]) * (right-left-1) 。)
- 两篇题解的地址
- 日后再次复习重新写
- [84 柱状图中最大的矩形](#84 柱状图中最大的矩形)
- [3 无重复字符的最长子串](#3 无重复字符的最长子串)
- [438 找到字符串中所有字母异位词](#438 找到字符串中所有字母异位词)
- [560 和为 K 的子数组](#560 和为 K 的子数组)
- [239 滑动窗口最大值](#239 滑动窗口最大值)
- [76 最小覆盖子串](#76 最小覆盖子串)
- [53 最大子数组和](#53 最大子数组和)
- [56 合并区间](#56 合并区间)
- [189 轮转数组](#189 轮转数组)
- [238 除自身以外数组的乘积](#238 除自身以外数组的乘积)
- [41 缺失的第一个正数](#41 缺失的第一个正数)
- [73 矩阵置零](#73 矩阵置零)
- [54 螺旋矩阵](#54 螺旋矩阵)
- [48 旋转图像](#48 旋转图像)
- [240 搜索二维矩阵 II](#240 搜索二维矩阵 II)
1 两数之和
未看解答自己编写的青春版
哈希哈希,觉得这题应该不用去看题解了。
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
table = {}
n = len(nums)
for i in range(n):
if len(table) == 0 :
table[nums[i]] = table.get(nums[i],0) + i
else :
temp = target - nums[i]
if temp in table :
return [i,table[temp]]
else :
table[nums[i]] = table.get(nums[i],0) + i
return None
重点
题解的代码
日后再次复习重新写
49 字母异位词分组
未看解答自己编写的青春版
但是时间上仅打败5%,是哪里出了问题?好像也没法加速了啊
python
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
if len(strs) == 1 :
return [strs]
strs_sorted = [sorted(i) for i in strs]
n = len(strs)
used = [False]*n
res = []
for i in range(n):
if used[i] == False :
level = [strs[i]]
used[i] = True
for j in range(i+1,n):
if used[j] == False :
# 这句比较,引起了时间花费比字典直接索引大很多
if strs_sorted[i]==strs_sorted[j] :
level.append(strs[j])
used[j] = True
res.append(level)
return res
重点
为什么用了字典,就比我的used数组快呢?
我目前认为是:if strs_sorted[i]==strs_sorted[j] :
这句比较,引起了时间花费比字典直接索引大很多
题解的代码
python
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
dic = {}
for s in strs:
keys = "".join(sorted(s))
if keys not in dic:
dic[keys] = [s]
else:
dic[keys].append(s)
return list(dic.values())
日后再次复习重新写
128 最长连续序列
未看解答自己编写的青春版
没思路。错误的代码:
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
maxlength = 1
table = {}
for i in nums :
if i not in table :
table[i] = 1
if i-1 in table :
table[i] += table[i-1]
table[i-1] = table[i]
if i+1 in table :
table[i] += table[i+1]
table[i+1] = table[i]
maxlength = max(maxlength,table[i])
print(table)
return maxlength
重点
关于 left 和 right 的疑问?
代码中 if num not in hash_dict就保证了 left 和 right 不会有交叠 进来的这个值没有在字典中出现过,意味着目前还没有任何区间包含这个值,如果 left 和 right 出现重叠了的,就意味着这个值之前已经在区间中了,矛盾了。
不可能有交叉,不可能一个没出现过的数,既属于left又属于right
为什么只需要更新 num-left 和 num+right ?
因为我们在判断时,只看当前数的前一个数和后一个数,所以在更新时,只需要更新边界,不在序列中的新数如果要使用已有的最大长度,一定会碰到边界,碰不到边界,那么值一定就是 1 。
题解的代码
利用 get 函数,可以少写很多判断的风格:
python
class Solution(object):
def longestConsecutive(self, nums):
hash_dict = dict()
max_length = 0
for num in nums:
if num not in hash_dict:
left = hash_dict.get(num - 1, 0)
right = hash_dict.get(num + 1, 0)
cur_length = 1 + left + right
if cur_length > max_length:
max_length = cur_length
hash_dict[num] = cur_length
hash_dict[num - left] = cur_length
hash_dict[num + right] = cur_length
return max_length
判断 i-1 和 i+1 的风格:
python
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_map<int,int> consecutive;
int maxlong=0;
for(int x:nums){
if(consecutive.count(x)) continue;
int nowlong=1;
if(consecutive.count(x+1)&&consecutive.count(x-1)){
nowlong+=consecutive[x+1]+consecutive[x-1];
consecutive[x-consecutive[x-1]]=nowlong;
consecutive[x+consecutive[x+1]]=nowlong;
}
else{
if(consecutive.count(x-1)){
nowlong+=consecutive[x-1];
consecutive[x-consecutive[x-1]]=nowlong;
}
if(consecutive.count(x+1)){
nowlong+=consecutive[x+1];
consecutive[x+consecutive[x+1]]=nowlong;
}
}
consecutive[x]=nowlong;
maxlong=max(maxlong,nowlong);
}
return maxlong;
}
};
看了解答后,自己又写了一遍的风格:
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
maxlength = 0
table = {}
for i in nums :
if i not in table :
if i-1 in table and i+1 in table :
table[i] = table[i-1] + table[i+1] + 1
table[i-table[i-1]] = table[i]
table[i+table[i+1]] = table[i]
elif i-1 in table :
table[i] = table[i-1] + 1
table[i-table[i-1]] = table[i]
elif i+1 in table :
table[i] = table[i+1] + 1
table[i+table[i+1]] = table[i]
else :
table[i] = 1
maxlength = max(maxlength,table[i])
return maxlength
日后再次复习重新写
283 移动零
双指针,过
11 盛最多水的容器
未看解答自己编写的青春版
没思路,知道是双指针,但是不清楚怎么移动。
看了解答后明白了,让高小的移动。
重点
对O(n)的算法写一下自己的理解,一开始两个指针一个指向开头一个指向结尾,此时容器的底是最大的,接下来随着指针向内移动,会造成容器的底变小,在这种情况下想要让容器盛水变多,就只有在容器的高上下功夫。 那我们该如何决策哪个指针移动呢?我们能够发现不管是左指针向右移动一位,还是右指针向左移动一位,容器的底都是一样的,都比原来减少了 1。这种情况下我们想要让指针移动后的容器面积增大,就要使移动后的容器的高尽量大,所以我们选择指针所指的高较小的那个指针进行移动,这样我们就保留了容器较高的那条边,放弃了较小的那条边,以获得有更高的边的机会。
python
class Solution:
def maxArea(self, height: List[int]) -> int:
maxv = 0
n = len(height)
left = 0
right = n-1
while left < right :
value = min(height[left],height[right])*(right-left)
maxv = max(maxv,value)
if height[left] >= height[right] :
right -= 1
else :
left += 1
return maxv
题解的代码
日后再次复习重新写
15 三数之和
过。
42 接雨水
未看解答自己编写的青春版
不会。
重点
这道题是属于双指针法的题目,但是还是不清楚怎么用双指针,根本原因是对如何计算雨水,不清楚。
诸如类似计算雨水的题目,有两种计算方式,横向和竖向。
本题,我将会学习四种解法,暴力,DP,双指针,单调栈。其中,前三种方法属于一类,DP和双指针其实都是对暴力的优化,在本题上并没有体现出自身方法的优势。单调栈的使用,本题是一个很经典的题目。
需要注意的是,因为前三种方法,我们要做的都是依次遍历数组,所以使用的计算方式是:按列计算雨水,这也是符合直觉的。
单调栈因为涉及弹入弹出,是按行计算雨水。
自己理解后的代码,这部分代码都是按我自己的风格编写的,会和题解有一些出入,比如大部分题解中,位置 i 的左右最大高度,初始值就是height[i],考虑了自己,但我没有考虑自己,初始值是0
暴力法:超时
python
class Solution:
def trap(self, height: List[int]) -> int:
total = 0
n = len(height)
# 第一个和最后一个不可能接雨水
for i in range(1,n-1):
maxleft = 0
maxright = 0
j = i-1
while j > -1 :
maxleft = max(maxleft,height[j])
j -= 1
j = i+1
while j < n :
maxright = max(maxright,height[j])
j += 1
# 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
total += max(min(maxleft,maxright)-height[i],0)
return total
DP:是不是可以提前保存好每个位置的左右最大值?其实就是对最大高度进行记忆化。
python
class Solution:
def trap(self, height: List[int]) -> int:
total = 0
n = len(height)
dp = [[0]*2 for _ in range(n)]
# dp[i][0] : 第i个位置的maxleft ; dp[i][1] : 第i个位置的maxright
for i in range(1,n):
dp[i][0] = max(dp[i-1][0],height[i-1])
for i in range(n-2,-1,-1):
dp[i][1] = max(dp[i+1][1],height[i+1])
# 第一个和最后一个不可能接雨水
for i in range(1,n-1):
maxleft = dp[i][0]
maxright = dp[i][1]
# 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
total += max(min(maxleft,maxright)-height[i],0)
return total
双指针:这里存在了一点冲突,我参考的两篇题解,在这里有一些分歧,卡哥的双指针法,其实就是完全手册里的二维DP,都是用两个一维数组,去储存每个位置的maxleft和maxright。
但是手册中的双指针思路如下:
要注意这种双指针法的编写逻辑,和上一题,"盛最多水的容器",相似。如何确定是移动 left 还是 right ? 看当前 mexleft 和 maxright 哪个小。因为我们在计算当前位置所能接的雨水时,首先要判断 min(maxleft , maxright) ,那么当前应该计算的雨水位置,就是小值的那一侧,假如maxleft小,那么不管后面的循环中,maxright再如何变化,在当前left处,min(maxleft , maxright) 的值不会再变化了(注意,当前left位置的maxleft,是由该位置之前的元素值决定的),所以,如果maxleft小,那么就计算left处的雨水,然后更新maxleft,然后移动left。right同理。
python
class Solution:
def trap(self, height: List[int]) -> int:
total = 0
n = len(height)
maxleft = height[0]
maxright = height[n-1]
left = 1
right = n-2
while left <= right :
if maxleft <= maxright :
# 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
total += max(maxleft-height[left],0)
# 注意逻辑,注意编写顺序,这里的更新要写在left更新之前
# 因为在我的编写逻辑中,maxleft和maxright是不考虑当前值的
# 如果先更新left了,maxleft就会漏掉当前left位置的值
maxleft = max(maxleft,height[left])
left += 1
else :
# 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
total += max(maxright-height[right],0)
# 注意逻辑,注意编写顺序,这里的更新要写在right更新之前
maxright = max(maxright,height[right])
right -= 1
return total
单调栈:
本题要让0入栈,不让0入栈是不自然的,然后计算当前能承接水的体积的方式是:当前最外层遍历的元素是 i , 叫做right ,pop()出来的元素是 mid (这个值也是下标) , 当前单调栈stack内 (pop后的单调栈,单调栈的栈顶元素 stack[-1] ),此时的栈顶元素 stack[-1],叫做 left (这个值也是下标) 。计算以当前位置 mid 为底,所能承载的水的体积是:(min(nums[left] , nums[right]) - nums[mid]) * (right-left-1) 。
本题还有一个要注意的是:如果两个数值相等,怎么处理?照常入栈,顶替掉前一个!
python
class Solution:
def trap(self, height: List[int]) -> int:
total = 0
n = len(height)
# 栈里放的是索引
stack = [0]
for i in range(1,n):
while stack != [] and height[i] >= height[stack[-1]] :
mid = stack.pop()
if stack != []:
left = stack[-1]
total += (min(height[left],height[i])-height[mid])*(i-left-1)
stack.append(i)
return total
两篇题解的地址
日后再次复习重新写
84 柱状图中最大的矩形
未看解答自己编写的青春版
重点
题解的代码
日后再次复习重新写
3 无重复字符的最长子串
未看解答自己编写的青春版
一开始想用字典的,但是发现不行,因为要求子序列,要求必须连续,就想到了用双向队列。
python
from collections import deque
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dq = deque()
maxcount = 0
count = 0
for i in s :
if i in dq :
while dq[0] != i :
dq.popleft()
count -= 1
dq.popleft()
dq.append(i)
else :
dq.append(i)
count += 1
maxcount = max(count,maxcount)
return maxcount
重点
注意:这道题是属于滑动窗口的题目。
用 set 要比我用 deque 来的更巧妙一些。因为本题所要求为去重的子串,而 set 自带去重特性。
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = left = 0
window = set() # 维护从下标 left 到下标 right 的字符
for right, c in enumerate(s):
while c in window: # 加入 c 后,窗口内会有重复元素
window.remove(s[left])
left += 1 # 缩小窗口
window.add(c)
ans = max(ans, right - left + 1) # 更新窗口长度最大值
return ans
下面一版的代码解读:(下面这份代码不好理解,写的很简略,我觉得初学者还是先理解上面基于双向队列或者set的做法)
i是截至j,以j为最后一个元素的最长不重复子串的起始位置,即索引范围是[i,j]的子串是以索引j为最后一个元素的最长子串。 当索引从j-1增加到j时,原来的子串[i,j-1]新增了一个元素变为[i,j],需要判断j是否与[i,j-1]中元素有重复。所以if s[j] in st:是判断s[j]相同元素上次出现的位置,和i孰大孰小。如果i大,说明[i,j-1]中没有与s[j]相同的元素,起始位置仍取i;如果i小,则在[i,j-1]中有了与s[j]相同的元素,所以起始位置变为st[s[j]]+1,即[st[sj]+1,j]。而省略掉的else部分,由于s[j]是第一次出现所以前面必然没有重复的,仍然用i作为起始位置即可。 后面的ans=max(ans,j-i+1)中,括号中前者ans是前j-1个元素最长子串长度,j-i+1是以s[j]结尾的最长子串长度,两者(最长子串要么不包括j,要么包括j)取最大即可更新ans,遍历所有i后得到整个输入的最长子串长度。
python
class Solution:
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
st = {}
i, ans = 0, 0
for j in range(len(s)):
if s[j] in st:
i = max(st[s[j]], i)
ans = max(ans, j - i + 1)
st[s[j]] = j + 1
return ans;
题解的代码
日后再次复习重新写
集合方法复写:
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
maxcount = 0
window = set()
left = 0
for index,value in enumerate(s):
while value in window :
window.remove(s[left])
left += 1
window.add(value)
maxcount = max(maxcount,index-left+1)
return maxcount
438 找到字符串中所有字母异位词
未看解答自己编写的青春版
排序方法,时间复杂度过高。
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
n = len(s)
m = len(p)
res = []
target = sorted(p)
for i in range(0,n-m+1):
temp = s[i:i+m]
if sorted(temp) == target :
res.append(i)
return res
用字典记录:注意特殊情况的处理( m > n),注意初始化,不要放在循环里,放在外面初始化。注意收获结果的判断逻辑的放置位置,要放在循环逻辑的最后,所以在初始化后就应该立刻进行一次判断。
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
table = {}
m = len(p)
n = len(s)
if m > n :
return []
left = 0
right = 0
res = []
for i in p :
table[i] = table.get(i,0)+1
temp = {}
while right < m :
temp[s[right]] = temp.get(s[right],0)+1
right += 1
if temp == table :
res.append(left)
for i in range(right,n) :
temp[s[left]]-=1
if temp[s[left]] == 0 :
del temp[s[left]]
left += 1
temp[s[right]] = temp.get(s[right],0)+1
right += 1
if temp == table :
res.append(left)
#print(temp)
return res
根本不需要,上面,这么复杂的,初始化,以及 left right 走来走去,精简版:
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
table = {}
m = len(p)
n = len(s)
if m > n :
return []
res = []
temp = {}
for i in range(m) :
table[p[i]] = table.get(p[i],0)+1
temp[s[i]] = temp.get(s[i],0)+1
if temp == table :
res.append(0)
for i in range(m,n) :
temp[s[i-m]]-=1
if temp[s[i-m]] == 0 :
del temp[s[i-m]]
temp[s[i]] = temp.get(s[i],0)+1
if temp == table :
res.append(i-m+1)
#print(temp)
return res
用哈希,注意到题目说明:两个字符串均只包含小写字母。
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
if len(s) < len(p):
return []
Num = []
n = len(p)
A = [0] * 26
for i in range(n):
A[ord(p[i]) - ord('a')] += 1
A[ord(s[i]) - ord('a')] -= 1
if A == [0] * 26:
Num.append(0)
for i in range(n, len(s)):
A[ord(s[i]) - ord('a')] -= 1
A[ord(s[i - n]) - ord('a')] += 1
if A == [0] * 26:
Num.append(i + 1 - n)
return Num
数组哈希方法,复写:
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
m = len(p)
n = len(s)
if m > n :
return []
res = []
A = [0]*26
for i in range(m) :
A[ord(p[i])-ord('a')] += 1
A[ord(s[i])-ord('a')] -= 1
if A == [0]*26 :
res.append(0)
for i in range(m,n) :
A[ord(s[i])-ord('a')] -= 1
A[ord(s[i-m])-ord('a')] += 1
if A == [0]*26 :
res.append(i-m+1)
#print(temp)
return res
重点
注意:这道题是属于滑动窗口的题目。
题解的代码
日后再次复习重新写
560 和为 K 的子数组
未看解答自己编写的青春版
不会。
重点
为什么这题不可以用双指针/滑动窗口:因为nums[i]可以小于0,也就是说右指针i向后移1位不能保证区间会增大,左指针j向后移1位也不能保证区间和会减小。给定j,i的位置没有二段性,vice versa。
我也想到了不可以用双指针。
暴力:超时
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
count = 0
n = len(nums)
for i in range(n):
sums = 0
for j in range(i,n):
sums += nums[j]
if sums == k :
count += 1
return count
前缀和 + 哈希表优化:
这方法,如果不是见过,真的很难想到啊。
题解的代码
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
table = {}
table[0] = 1
total = 0
count = 0
n = len(nums)
for i in range(n):
total += nums[i]
if total-k in table :
count += table[total-k]
table[total] = table.get(total,0)+1
return count
日后再次复习重新写
239 滑动窗口最大值
未看解答自己编写的青春版
单调队列,我觉得这题考察的就是,如何根据题意,模拟出答案更新的过程,然后发现其中有什么规律,自然而然地去设计一个这样的单调队列。只保留大的数。
以及弹出元素和加入元素的逻辑,判断条件。
python
from collections import deque
class DQ:
def __init__(self):
self.dq = deque()
def push(self,val):
if len(self.dq)==0 :
self.dq.append(val)
else :
while self.dq and self.dq[-1] < val :
self.dq.pop()
self.dq.append(val)
def top(self):
return self.dq[0]
def popleft(self):
self.dq.popleft()
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
dq = DQ()
n = len(nums)
for i in range(k):
dq.push(nums[i])
res = [dq.top()]
for i in range(k,n):
remove = i-k
if nums[remove]==dq.top():
dq.popleft()
dq.push(nums[i])
res.append(dq.top())
return res
重点
当然我觉得我能顺利地解决这道题,还是之前已经学过了的缘故。
滑动窗口最大值
题解的代码
日后再次复习重新写
76 最小覆盖子串
未看解答自己编写的青春版
又没写出来,这道题的巧妙不仅仅在于,用一个字典或者哈希数组,去存储每个字符,而是用字符串 t 的长度,作为一个指示变量,来指导我们什么时候该上前推进了,而不是每次加入一个元素时,都要进行比较。
错误代码:只让 t 中有的元素,进入字典,这样做完全是自己给自己添麻烦,后续的移动也是一团糟。
python
class Solution:
def minWindow(self, s: str, t: str) -> str:
n = len(s)
m = len(t)
if n < m :
return ''
table = {}
for i in t :
table[i] = table.get(i,0)+1
left = 0
rightmin = inf
leftmin = 0
while s[left] not in table :
left += 1
right = left
flag = False
while right < n :
if s[right] in table :
table[s[right]] -= 1
if self.isright(table):
flag = True
break
right += 1
if not flag :
return ''
if right-left < rightmin-leftmin :
rightmin,leftmin = right,left
table[s[left]] += 1
left += 1
while left < n and right < n :
while s[left] not in table :
left += 1
if self.isright(table):
if right-left < rightmin-leftmin :
rightmin,leftmin = right,left
table[s[left]] += 1
left += 1
else :
right += 1
flag = False
while right < n :
if s[right] in table :
table[s[right]] -= 1
if self.isright(table):
flag = True
break
right += 1
if flag :
if right-left < rightmin-leftmin :
rightmin,leftmin = right,left
table[s[left]] += 1
left += 1
else :
break
return s[leftmin:rightmin+1]
def isright(self,table):
for i in table :
if table[i] > 0 :
return False
return True
重点
暂时不想写了,这道题,后面再复写。看是看懂了。
题解的代码
python
from collections import defaultdict
class Solution:
def minWindow(self, s: str, t: str) -> str:
char_t = defaultdict(int)
nt = len(t)
ns = len(s)
for i in range(nt):
char_t[t[i]] += 1
leftmin = 0
rightmin = 2*ns
left = 0
right = 0
while right < ns :
# 这里必须是大于0,因为对于不存在的元素,defaultdict的返回值也是0
if char_t[s[right]] > 0 :
char_t[s[right]] -= 1
nt -= 1
else :
char_t[s[right]] -= 1 # 不是t的值也要记录
if nt == 0 :
# 这里必须是小于0,因为对于不存在的元素,defaultdict的返回值也是0
while char_t[s[left]] < 0 :
char_t[s[left]] += 1
left += 1
if rightmin - leftmin > right-left :
rightmin , leftmin = right , left
nt = 1
# 要理解下面两行代码所代表的逻辑,如果进入判断nt==0,那么一定是在前面,新加入了一个
# 属于t的字符,并且t中所有字符目前的值是0,而上面的while循环,只能寻找小于0的字符,
# 当while循环终止时,找到的是最左边的为0的字符,那么此时,如果我们还想让left移动,
# 那么就要在right的右边,找一个和该最左边字符相等的字符,所以要让char_t[s[left]] += 1
# 同时也记得让left移动一格,这样在后面找到该字符时,向左移动的while循环是合法的
char_t[s[left]] += 1
left += 1
right += 1
if rightmin == 2* ns :
return ''
else :
return s[leftmin:rightmin+1]
日后再次复习重新写
复写:
python
class Solution:
def minWindow(self, s: str, t: str) -> str:
table = {}
m = len(t)
n = len(s)
for i in t:
table[i] = table.get(i,0)+1
left = 0
right = 0
rightmin = 2*n
leftmin = 0
while right < n :
if table.get(s[right],0) > 0 :
table[s[right]] -= 1
m -= 1
else :
# 这里处理的是两种情况,一是s[right]不在t中
# 二是s[right]是t中的值,但是前面已经出现过,将其值减为0或者更小了,
# 这里再次出现,也要减一,举例:s='ABBBC' t='ABC',其中的后两个B
table[s[right]] = table.get(s[right],0)-1
if m == 0 :
while table[s[left]] < 0 :
table[s[left]]+=1
left+=1
if rightmin-leftmin > right-left:
rightmin,leftmin = right,left
m = 1
table[s[left]]+=1
left+=1
right += 1
if rightmin == 2*n :
return ''
else :
return s[leftmin:rightmin+1]
53 最大子数组和
未看解答自己编写的青春版
这份代码其实有点牵强,单独写了,如果数组中全是负数的情况。
python
class Solution:
def maxSubArray(self, nums):
maxcount = max(nums)
if maxcount < 0 :
return maxcount
count = 0
for i in nums:
count += i
if count < 0:
count = 0
maxcount = max(maxcount,count)
return maxcount
重点
贪心 or DP 。
题解的代码
标准代码,不自主赋值,全部用数组中自己的值:
python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = nums[0]
maxsum = nums[0]
n = len(nums)
for i in range(1,n):
if maxsum <= 0 :
maxsum = nums[i]
else :
maxsum += nums[i]
result = max(maxsum,result)
return result
日后再次复习重新写
56 合并区间
未看解答自己编写的青春版
python
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
res = []
n = len(intervals)
temp = intervals[0]
for i in range(1,n):
if temp[1] >= intervals[i][0] :
# 这里要注意,因为我只是先按照第一个坐标进行排序,所以前一个区间的右边界
# 不一定比后一个区间的右边界小
temp[1] = max(temp[1],intervals[i][1])
else :
res.append(temp)
temp = intervals[i]
# 这里注意,根据前面的逻辑,不管最后一层循环走的是if还是else,都没有将最后的结果放入结果集中
res.append(temp)
return res
重点
注意两个点就好了,都写在了代码的注释中。
题解的代码
日后再次复习重新写
189 轮转数组
未看解答自己编写的青春版
能想到两种方法,第一种是先反转整个列表,再分别反转两段。第二种是,一次移动一个,移动K次。
错误代码:
错误原因:像这种要求在原数组上修改的,那么就不能用原数组进行一些赋值操作,就必须在上面操作,赋值之后,虽然新变量名字和原数组一样,但是这也是两个变量了,所以,本题,首先不能用python自带的切片,因为倒序切片没法部分倒序,一处理就是整个序列倒序,但这是不行的,会涉及申请新空间问题 (temp = nums[0:k]) ; 那么就必须自主编写倒序函数,也不能编写为 self.reverse 的格式,因为这意味着必须要有返回值,而承接返回值就意味着赋值,就不是原数组修改了!
要学习下面这种在函数内定义函数的方式。
python
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k = k % n
nums = nums[::-1]
nums = self.reverse(0,k-1,nums)
nums = self.reverse(k,n-1,nums)
def reverse(self,i,j,nums):
while i < j :
nums[i],nums[j] = nums[j],nums[i]
i += 1
j -= 1
return nums
一次向右循环移动一个数字,也是只需要O(1),但是时间复杂度上是O(k*n),这里不写这种方法的代码了。
重点
还有一种方法,就是申请一个K的空间,先把后K个数存起来。
python
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k = k % n
temp = nums[n-k:n]
if n > k :
for i in range(n-k-1,-1,-1):
nums[i+k] = nums[i]
nums[:k] = temp
题解的代码
日后再次复习重新写
238 除自身以外数组的乘积
未看解答自己编写的青春版
时间复杂度和空间复杂度均是 O(n) 的方法。
python
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0]*n
left = [0]*n
right = [0]*n
temp = 1
for i in range(1,n):
temp = temp * nums[i-1]
left[i] = temp
temp = 1
for i in range(n-2,-1,-1):
temp = temp*nums[i+1]
right[i] = temp
for i in range(n):
if i == 0 :
res[i] = right[i]
elif i == n-1 :
res[i] = left[i]
else :
res[i] = left[i] * right[i]
return res
进阶,空间复杂度如何优化到 O(1) ,同时保持时间复杂度还是 O(n) ?
这种方法想不到。
重点
空间复杂度 O(1) 方法:
左边走一遍,右边走一遍,其中规律,自己试一试就能发现,我一开始总想着双指针,才一直想不出来。
python
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [1]*n
left = 1
right = 1
for i in range(n):
res[i] = left
left = left * nums[i]
for i in range(n-1,-1,-1):
res[i] = res[i] * right
right = right * nums[i]
return res
题解的代码
日后再次复习重新写
41 缺失的第一个正数
未看解答自己编写的青春版
不会,hard题没有思路。
间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案,想不出来。
重点
主要思路:
第一遍把所有负数换成INT_MAX。第二遍,将出现的数的绝对值对应的位置的数置为负数。第三遍,不是负数就输出位置。
两个要点:
1、取abs的操作很巧妙,这样就可以避免前面的操作,覆盖了后面的值。
2、抓住本题的循环不变量:只要该元素出现过了,那么该位置的就必须是负数,所以必须加abs
题解的代码
python
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
for i in range(n):
if nums[i] <= 0 :
nums[i] = inf
for i in range(n) :
# 这里取abs的操作也很巧妙,这样就可以避免前面的操作,覆盖了后面的值
temp = abs(nums[i])
if temp > 0 and temp < n+1 :
# 这里的abs太关键了,防止的就是多次操作的情况
# 抓住本题的循环不变量:只要该元素出现过了,那么该位置的就必须是负数,所以必须加abs
nums[temp-1] = -abs(nums[temp-1])
for i in range(n) :
if nums[i] > 0 :
return i+1
return n+1
日后再次复习重新写
73 矩阵置零
未看解答自己编写的青春版
O(1) 方法,用 inf 对"原本0"进行标记 :(感觉我这有些投机取巧)
python
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
for i in range(m):
for j in range(n):
if matrix[i][j] == 0 :
matrix[i][j] = inf
for i in range(m):
for j in range(n):
if matrix[i][j] == inf :
for k in range(n) :
if matrix[i][k] != inf :
matrix[i][k] = 0
for k in range(m) :
if matrix[k][j] != inf :
matrix[k][j] = 0
for i in range(m):
for j in range(n):
if matrix[i][j] == inf :
matrix[i][j] = 0
O(mn)方法
必须用深拷贝,copy.deepcopy() ,我真是涨知识了。
python
import copy
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
copy_matrix = copy.deepcopy(matrix)
m = len(matrix)
n = len(matrix[0])
for i in range(m):
for j in range(n):
if matrix[i][j] == 0 and copy_matrix[i][j] == 0 :
for k in range(n) :
matrix[i][k] = 0
for k in range(m) :
matrix[k][j] = 0
O(m+n)方法:
python
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
exist_m = [False]*m
exist_n = [False]*n
for i in range(m):
for j in range(n):
if matrix[i][j] == 0 :
exist_m[i] = True
exist_n[j] = True
for i in range(m):
for j in range(n):
if matrix[i][j] == 0 :
# 注意这里,对行操作还是对列操作
# 逻辑和前面给exist_m(n)是相反的
if exist_n[j] :
for k in range(m) :
# 这是对一列进行操作
matrix[k][j] = 0
if exist_m[i] :
for k in range(n) :
# 这是对一行进行操作
matrix[i][k] = 0
重点
本题要求用三种不同的空间复杂度的方法。
今天做了这道题真是涨知识了,原来在前面二叉树和回溯算法章节,我一直使用的,list.copy() 方法,一直都是浅拷贝,是共享内存的,卡哥一直用的 list[::-1] ,但是我经过试验,一维列表的.copy(),拷贝后数据是不随原数据的更改而更改的,但是多维列表会更改!
多维就要用深拷贝,copy.deepcopy() 。
题解的代码
看了一下评论的解答,为我愚蠢的矩阵赋值方式感到抓狂。
思路:
O(1) :
python
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
row = False
col = False
'''错误逻辑
for i in range(0,m):
for j in range(0,n):
# 注意看,这段逻辑是错误的,后面的修改逻辑可能导致 col row 的值发生错误
# 而让每个位置都判断左右两边,是没有道理的
if matrix[i][0] == 0 :
col = True
if matrix[0][j] == 0 :
row = True
if matrix[i][j] == 0 :
matrix[0][j] = 0
matrix[i][0] = 0
'''
for i in range(0,m):
for j in range(0,n):
# 正确逻辑,先判断此位置是不是0
if matrix[i][j] == 0 :
if j == 0 :
col = True
if i == 0 :
row = True
matrix[0][j] = 0
matrix[i][0] = 0
for i in range(1,m):
for j in range(1,n):
if matrix[0][j] == 0 or matrix[i][0] == 0 :
matrix[i][j] = 0
if row :
for i in range(n):
matrix[0][i] = 0
if col :
for i in range(m):
matrix[i][0] = 0
O(m+n)方法:
python
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
exist_m = [False]*m
exist_n = [False]*n
for i in range(m):
for j in range(n):
if matrix[i][j] == 0 :
exist_m[i] = True
exist_n[j] = True
for i in range(m):
for j in range(n):
if exist_m[i] == True or exist_n[j] == True :
matrix[i][j] = 0
O(mn)的方法好难想,真的不需要这么大的空间!不考虑这种方法了,直接学习O(1)的方法不好吗!
日后再次复习重新写
54 螺旋矩阵
未看解答自己编写的青春版
加一减一好像有些乱的版本,因为我给 right 和 down 的定义都是 m n 。所以:在索引时,要记得减一。在倒序时,因为第一个值是可以取到的,所以要记得减一。在倒序时,因为最后一个值是不能取到的,所以要对 left 和 up 减一。
python
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m = len(matrix)
n = len(matrix[0])
left = 0
right = n
up = 0
down = m
total = m*n
res = [0]*total
count = 0
while count < total :
for i in range(left,right):
res[count] = matrix[up][i]
count += 1
up += 1
if up >= down :
break
for i in range(up,down):
res[count] = matrix[i][right-1]
count += 1
right -= 1
if right <= left :
break
for i in range(right-1,left-1,-1):
res[count] = matrix[down-1][i]
count += 1
down -= 1
if up >= down :
break
for i in range(down-1,up-1,-1):
res[count] = matrix[i][left]
count += 1
left += 1
if right <= left :
break
return res
更改下标逻辑:给 right 和 down 的定义变为 m-1 n-1 。值得注意的是,下标逻辑改变,判断跳出的逻辑也要相应改变。
python
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m = len(matrix)
n = len(matrix[0])
left = 0
right = n-1
up = 0
down = m-1
total = m*n
res = [0]*total
count = 0
while count < total :
for i in range(left,right+1):
res[count] = matrix[up][i]
count += 1
up += 1
if up > down :
break
for i in range(up,down+1):
res[count] = matrix[i][right]
count += 1
right -= 1
if right < left :
break
for i in range(right,left-1,-1):
res[count] = matrix[down][i]
count += 1
down -= 1
if up > down :
break
for i in range(down,up-1,-1):
res[count] = matrix[i][left]
count += 1
left += 1
if right < left :
break
return res
重点
本题要注意的是,对各个指标的定义,会影响判断跳出的逻辑,这个要举例来验证了。但是从直觉上说,还是第二种方法好,不管是在边界处理上更为简洁,还是在跳出逻辑上更符合直觉。
相等时,应该会再有加入元素的操作的,不能跳出。
学习上面第二种的写法。
题解的代码
日后再次复习重新写
48 旋转图像
未看解答自己编写的青春版
没思路
重点
这道题太巧妙了!已经练到这种程度了,如果做题的时候发现没思路,就不要按照老思路一直想下去了,很有可能是思路错了!改变思路,从其他方向入手!
本题思路:先转置,再镜像对称。
或者,先上下翻转,再转置也行。
先转置,再镜像对称,的代码:
python
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
for i in range(m):
for j in range(i+1,n):
matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j]
middle = n // 2
for i in range(m):
for j in range(middle):
matrix[i][j],matrix[i][n-1-j] = matrix[i][n-1-j],matrix[i][j]
题解的代码
日后再次复习重新写
240 搜索二维矩阵 II
未看解答自己编写的青春版
倒序遍历,这道题正序遍历不行。
python
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m = len(matrix)
n = len(matrix[0])
row = m-1
col = n-1
while row > -1 and matrix[row][0] > target :
row -= 1
while col > -1 and matrix[0][col] > target :
col -= 1
for i in range(row+1):
for j in range(col+1):
if matrix[i][j] == target :
return True
return False
重点
另一种思路:
python
class Solution:
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
m = len(matrix)
if m == 0:
return False
n = len(matrix[0])
if n == 0:
return False
i = m - 1
j = 0
while i >= 0 and j < n:
if matrix[i][j] == target:
return True
elif matrix[i][j] < target:
j = j + 1
else:
i = i - 1
return False