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