Leetcode随机抽题检测
- 46 全排列
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 78 子集
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 17 电话号码的字母组合
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 39 组合总和
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 22 括号生成
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 79 单词搜索
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 131 分割回文串
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 一段用于复制的标题
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 35 搜索插入位置
- 未看解答自己编写的青春版
- 重点
- 借着这道题,再次温习,二分查找中的难题,带重复元素的情况。
- 在排序数组中查找元素的第一个和最后一个位置
- 搜索右边界,相等时移动左指针;搜索左边界,相等时移动右指针;本题我的解法和代码随想录的解法稍有不同,在我的解法中,right_edge和left_edge一定都会被赋值,如果left_edge > right_edge,说明没有找到该值(这个可以自己拿没有找到的例子试试),如果找到了,范围就是对的,和代码随想录的还是不太一样。
- 对于有重复元素,求给定target的左右边界的问题,要处理的细节更多一点,这里我固定一套编写风格,采用左闭右闭区间风格。牢记切记
- 求左边界:就是在nums[middle]=target时,让right更新,最后退出循环,左边界=right。
- 求右边界:就是在nums[middle]=target时,让left更新,最后退出循环,右边界=left。
- 最后,target值所在的左闭右闭区间就是 [ 左边界+1 ,右边界-1 ] .
- 题解的代码
- 日后复习重新编写
- 74 搜索二维矩阵
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 34 在排序数组中查找元素的第一个和最后一个位置
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 33 搜索旋转排序数组
- 未看解答自己编写的青春版
- 重点
- 这道题可以先主要学习方法一。
- 题解的代码
- 日后复习重新编写
- 153 寻找旋转排序数组中的最小值
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 4 寻找两个正序数组的中位数
- 未看解答自己编写的青春版
- 重点
- 这道题太牛逼了,一定要多复习。
- 题解的代码
- 日后复习重新编写
- 121 买卖股票的最佳时机
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 55 跳跃游戏
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 45 跳跃游戏 II
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 763 划分字母区间
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 20 有效的括号
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 155 最小栈
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 394 字符串解码
- 未看解答自己编写的青春版
- 重点
- 学习了,这种压栈弹栈的操作,确实比我的要少操作一些。之前做过的栈的题目,都是将元素压入,第一次遇见这种,压入某种信息的,思路拓宽!
- 题解的代码
- 日后复习重新编写
- 739 每日温度
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
- 84 柱状图中最大的矩形
- 未看解答自己编写的青春版
- 重点
- 题解的代码
- 日后复习重新编写
46 全排列
未看解答自己编写的青春版
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
self.res = []
path = []
used = [False]*n
self.backtracking(nums,n,used,path)
return self.res
def backtracking(self,nums,n,used,path):
if len(path)==n:
self.res.append(path[:])
for i in range(n):
if used[i] == False :
path.append(nums[i])
used[i] = True
self.backtracking(nums,n,used,path)
used[i] = False
path.pop()
重点
过。
题解的代码
日后复习重新编写
78 子集
未看解答自己编写的青春版
不需要特殊处理边界情况。
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
self.res = []
n = len(nums)
idx = 0
path = []
self.backtracking(nums,n,idx,path)
return self.res
def backtracking(self,nums,n,idx,path):
self.res.append(path[:])
for i in range(idx,n):
path.append(nums[i])
self.backtracking(nums,n,i+1,path)
path.pop()
重点
题解的代码
日后复习重新编写
17 电话号码的字母组合
未看解答自己编写的青春版
回溯算法内部,一次循环就够了,当前所选的数字是确定的。
class Solution:
def __init__(self):
self.lettermap = [
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
]
def letterCombinations(self, digits: str) -> List[str]:
self.res = []
path = []
idx = 0
if len(digits)==0:
return []
self.backtracking(digits,idx,path)
return self.res
def backtracking(self,digits,idx,path):
# 这里的 if 也可以写成 if idx == len(digits) :
if len(path) == len(digits):
self.res.append(''.join(path))
return
digit = int(digits[idx])
letters = self.lettermap[digit]
for letter in letters :
path.append(letter)
self.backtracking(digits,idx+1,path)
path.pop()
return
重点
题解的代码
日后复习重新编写
39 组合总和
未看解答自己编写的青春版
无重复元素,不需要去重,只需要控制 idx 每次都从当前 i 开始即可,不需要 i+1 ,因为同一元素可以选取多次。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.res = []
candidates.sort()
path = []
idx = 0
self.backtracking(candidates,target,idx,path)
return self.res
def backtracking(self,candidates,target,idx,path):
if target == 0 :
self.res.append(path.copy())
return
if target < 0 :
return
for i in range(idx,len(candidates)):
if target - candidates[i] < 0 :
return
path.append(candidates[i])
self.backtracking(candidates,target-candidates[i],i,path)
path.pop()
return
重点
题解的代码
日后复习重新编写
22 括号生成
未看解答自己编写的青春版
这道题的思路想错了,以为是一个很复杂的回溯算法,需要每次对结果进行合理性判断,才能够得到结果集,这个判断的过程又是一个栈模拟的过程,觉得代码太复杂了就没有写。
重点
看了评论,确实不需要每次都对结果进行合理性判断,只要在回溯算法中进行适当剪枝即可,即:当前所使用的括号中,右括号 > 左括号 , 那么就不合法,return 就好了。
题解的代码
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
self.res = []
path = []
left = right = 0 # 目前所使用的左右括号数
self.backtracking(n,left,right,path)
return self.res
def backtracking(self,n,left,right,path):
if left == n and right == n :
self.res.append(''.join(path))
return
if left > n or right > n or right > left :
return
path.append('(')
self.backtracking(n,left+1,right,path)
path.pop()
path.append(')')
self.backtracking(n,left,right+1,path)
path.pop()
日后复习重新编写
79 单词搜索
未看解答自己编写的青春版
我觉得我是增加了很多剪枝的操作了,力扣上反馈的耗时和空间占用都排名较高,我觉得我写的没啥问题。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m = len(board)
n = len(board[0])
idx = 0
L = len(word)
if m == 1 and n==1 :
if word == board[0][0]:
return True
else :
return False
used = [[False]*n for _ in range(m)]
for i in range(m):
for j in range(n):
if self.backtracking(m,n,board,idx,word,L,i,j,used):
return True
return False
def backtracking(self,m,n,board,idx,word,L,i,j,used):
if idx == L :
return True
if used[i][j] == False and board[i][j]==word[idx]:
used[i][j] = True
if i != 0 :
if self.backtracking(m,n,board,idx+1,word,L,i-1,j,used):
return True
if i != m-1 :
if self.backtracking(m,n,board,idx+1,word,L,i+1,j,used):
return True
if j != 0 :
if self.backtracking(m,n,board,idx+1,word,L,i,j-1,used):
return True
if j != n-1 :
if self.backtracking(m,n,board,idx+1,word,L,i,j+1,used):
return True
used[i][j] = False
return False
重点
没看评论,觉得自己写的这版代码就不错。
题解的代码
日后复习重新编写
131 分割回文串
未看解答自己编写的青春版
回溯法中的切割问题。
class Solution:
def partition(self, s: str) -> List[List[str]]:
idx = 0
path = []
self.res = []
n = len(s)
self.backtracking(s,n,idx,path)
return self.res
def backtracking(self,s,n,idx,path):
if idx == n :
self.res.append(path[:])
return
for i in range(idx,n):
temp = s[idx:i+1]
if self.is_right(temp):
path.append(temp)
self.backtracking(s,n,i+1,path)
path.pop()
def is_right(self,s):
n = len(s)
left = 0
right = n-1
while left < right :
if s[left] != s[right] :
return False
left += 1
right -= 1
return True
重点
题解的代码
日后复习重新编写
一段用于复制的标题
未看解答自己编写的青春版
写的有些冗余了,在 is_right 函数里,不需要每次都去判断所有点,每次都只判断新加入的元素就可以了。
比较冗余的代码:
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.res = []
path = []
row = 0
self.backtracking(n,row,path)
result = []
count = len(self.res)
for i in range(count):
temp1 = []
for j in range(n):
temp2 = ['.']*n
temp2[self.res[i][j][1]] = 'Q'
temp1.append(''.join(temp2))
result.append(temp1[:])
return result
def backtracking(self,n,row,path):
if row == n :
self.res.append(path[:])
return
for i in range(n):
path.append([row,i])
if self.is_right(path):
self.backtracking(n,row+1,path)
path.pop()
def is_right(self,path):
n = len(path)
if n == 1 :
return True
else :
for i in range(n):
for j in range(i+1,n):
if path[i][1] == path[j][1] :
return False
for i in range(n):
for j in range(i+1,n):
if abs((path[i][1]-path[j][1])/(path[i][0]-path[j][0])) == 1:
return False
return True
改了之后,快了一些:
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.res = []
path = []
row = 0
self.backtracking(n,row,path)
result = []
count = len(self.res)
for i in range(count):
temp1 = []
for j in range(n):
temp2 = ['.']*n
temp2[self.res[i][j][1]] = 'Q'
temp1.append(''.join(temp2))
result.append(temp1[:])
return result
def backtracking(self,n,row,path):
if row == n :
self.res.append(path[:])
return
for i in range(n):
path.append([row,i])
if self.is_right(path):
self.backtracking(n,row+1,path)
path.pop()
def is_right(self,path):
n = len(path)
if n == 1 :
return True
else :
node = path[-1]
for i in range(n-1):
if path[i][1] == node[1] :
return False
for i in range(n-1):
if abs((path[i][1]-node[1])/(path[i][0]-node[0])) == 1:
return False
return True
但是整体还是较慢,那就是,在得到结果后,我构造解的过程,是非常耗时且耗内存的。那还是要学习卡哥的代码。
重点
学习卡哥直接在递归中构造的方法。
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.res = []
chessboard = ['.' * n for _ in range(n)]
idx = 0
self.backtracking(n,idx,chessboard)
return [[''.join(row) for row in solution] for solution in self.res]
def backtracking(self,n,idx,chessboard):
if idx == n :
self.res.append(chessboard.copy())
return
for i in range(n):
if self.is_valid(idx,i,chessboard):
chessboard[idx] = chessboard[idx][:i]+'Q'+chessboard[idx][i+1:]
self.backtracking(n,idx+1,chessboard)
chessboard[idx] = chessboard[idx][:i]+'.'+chessboard[idx][i+1:]
return
def is_valid(self,row,col,chessboard):
for i in range(row):
if chessboard[i][col] == 'Q':
return False
i,j = row-1 , col-1
while i>=0 and j>=0 :
if chessboard[i][j] == 'Q':
return False
i-=1
j-=1
i,j = row-1,col+1
while i >= 0 and j < len(chessboard):
if chessboard[i][j] == 'Q':
return False
i-=1
j+=1
return True
题解的代码
日后复习重新编写
35 搜索插入位置
未看解答自己编写的青春版
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n-1
while left <= right :
mid = left + (right-left)//2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
return mid
return left
重点
通过本题,明确二分法中我自己的编写风格,左闭右闭。本题中,找到就返回 middle , 找不到就返回 left ,因为退出循环的条件是 left > right ,所以插入位置就是 left 。
借着这道题,再次温习,二分查找中的难题,带重复元素的情况。
在排序数组中查找元素的第一个和最后一个位置
有重复元素,这道题会了,才算是掌握了二分法。
搜索右边界,相等时移动左指针;搜索左边界,相等时移动右指针;本题我的解法和代码随想录的解法稍有不同,在我的解法中,right_edge和left_edge一定都会被赋值,如果left_edge > right_edge,说明没有找到该值(这个可以自己拿没有找到的例子试试),如果找到了,范围就是对的,和代码随想录的还是不太一样。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left_edge = -2
right_edge = -2
left = 0
right = n-1
# 搜索左边界
while left <= right :
middle = left + (right-left)//2
if nums[middle] < target :
left = middle + 1
elif nums[middle] > target :
right = middle - 1
else :
right = middle - 1 # 相等时,移动右指针
left_edge = right + 1
left = 0
right = n-1
# 搜索右边界
while left <= right :
middle = left + (right-left)//2
if nums[middle] < target :
left = middle + 1
elif nums[middle] > target :
right = middle - 1
else :
left = middle + 1 # 相等时,移动左指针
right_edge = left - 1
if right_edge < left_edge :
return [-1,-1]
else :
return [left_edge,right_edge]
对于有重复元素,求给定target的左右边界的问题,要处理的细节更多一点,这里我固定一套编写风格,采用左闭右闭区间风格。牢记切记
求左边界:就是在nums[middle]=target时,让right更新,最后退出循环,左边界=right。
求右边界:就是在nums[middle]=target时,让left更新,最后退出循环,右边界=left。
最后,target值所在的左闭右闭区间就是 [ 左边界+1 ,右边界-1 ] .
题解的代码
日后复习重新编写
74 搜索二维矩阵
未看解答自己编写的青春版
一开始想的是,将二维矩阵展开为一维,再用最基础的二分法。但是这样要额外花费 O(m*n) 的内存空间。
重点
看了评论,其实根本不需要重新申请空间!只需要利用取模的操作,将一维的下标转换为二维即可!
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m = len(matrix)
n = len(matrix[0])
total = m*n
left = 0
right = total-1
while left <= right :
middle = left + (right-left)//2
i = middle // n
j = middle % n
if matrix[i][j] > target :
right = middle-1
elif matrix[i][j] < target :
left = middle+1
else :
return True
return False
题解的代码
日后复习重新编写
34 在排序数组中查找元素的第一个和最后一个位置
未看解答自己编写的青春版
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
leftboard = 0
rightboard = 0
left = 0
right = n-1
while left <= right :
middle = left + (right-left)//2
if nums[middle] > target :
right = middle - 1
elif nums[middle] < target :
left = middle + 1
else :
left = middle + 1
rightboard = left - 1
left = 0
right = n-1
while left <= right :
middle = left + (right-left)//2
if nums[middle] > target :
right = middle - 1
elif nums[middle] < target :
left = middle + 1
else :
right = middle - 1
leftboard = right + 1
# 在这版代码中,不需要处理很多边界情况,和卡哥的代码不一样
# 卡哥的代码,找不到会不进行赋值,而我的代码一定会赋值
# 所以 leftboard rightboard 的初始值无所谓。
# 最后只需要比较,是否左边界大于右边界,大于的话,说明找不到
if leftboard > rightboard :
return [-1,-1]
return [leftboard,rightboard]
重点
题解的代码
日后复习重新编写
33 搜索旋转排序数组
未看解答自己编写的青春版
没思路,一开始以为是一个循环数组的二分搜索,但是想了很久,觉得二分法不能用在循环数组里面。
重点
看了评论中的方法,大致可以分为两类。
方法一:
如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了。
这道题可以先主要学习方法一。
class Solution {
public int search(int[] nums, int target) {
int len = nums.length;
int left = 0, right = len-1;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] == target)
return mid;
else if(nums[mid] < nums[right]){
if(nums[mid] < target && target <= nums[right])
left = mid+1;
else
right = mid-1;
}
else{
if(nums[left] <= target && target < nums[mid])
right = mid-1;
else
left = mid+1;
}
}
return -1;
}
}
方法二:
两次二分查找,第一次找到旋转中心,第二次在两段中选中一个进行二分搜索。(不好理解,用二分法查找旋转中心)
class Solution {
public:
int search(vector<int>& nums, int target) {
int t = nums[0];
int l = 0, r = nums.size() - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (t > nums[mid]) r = mid - 1;
else l = mid + 1;
}
if (target >= t) l = 0;
else r = nums.size() - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (target > nums[mid]) l = mid + 1;
else if (target < nums[mid]) r = mid - 1;
else return mid;
}
return -1;
}
};
题解的代码
日后复习重新编写
方法一复写:(方法一比方法二好理解)
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n-1
while left <= right :
mid = left + (right-left)//2
if nums[mid]==target:
return mid
# 说明右半段是有序的
if nums[mid] < nums[right]:
# 利用有序的右半段的两个端点值,判断出目标值在有序的右半段中
if nums[mid] < target and target <= nums[right] :
left = mid + 1
else :
# 目标值不在有序的右半段中
right = mid -1
# 中间数大于最右边的数,说明左半段是有序的
else :
if nums[mid] > target and target >= nums[left] :
right = mid -1
else :
left = mid + 1
return -1
153 寻找旋转排序数组中的最小值
未看解答自己编写的青春版
说着不要不要,结果下一题直接被迫让你学会,利用二分法求解旋转中心的题目。其实就是上一题的方法二。定义切割点,在 left 和 right 的左闭右闭区间中,所以当 nums[0] > nums[mid] 时,切割点不可能为 mid ,切割点我这里定义为:原始数组中的最后一个元素(即最大元素),[ 4 5 1 2 3 ] , 切割点为 5 。
class Solution:
def findMin(self, nums: List[int]) -> int:
n = len(nums)
left = 0
right = n-1
tar = nums[0]
while left <= right :
mid = left + (right-left)//2
if tar > nums[mid] :
right = mid-1
else :
left = mid+1
# 旋转n次的情况,要特殊处理,因为旋转n次后,序列又变成有序了
if left >= n :
left = 0
return nums[left]
重点
题解的代码
日后复习重新编写
4 寻找两个正序数组的中位数
未看解答自己编写的青春版
不会,没思路。两个数组直接拼接,不是有序的啊。
重点
评论中最牛逼的思路:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int left = (m + n + 1) / 2;
int right = (m + n + 2) / 2;
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
//i: nums1的起始位置 j: nums2的起始位置
public int findKth(int[] nums1, int i, int[] nums2, int j, int k){
if( i >= nums1.length) return nums2[j + k - 1];//nums1为空数组
if( j >= nums2.length) return nums1[i + k - 1];//nums2为空数组
if(k == 1){
return Math.min(nums1[i], nums2[j]);
}
int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
if(midVal1 < midVal2){
return findKth(nums1, i + k / 2, nums2, j , k - k / 2);
}else{
return findKth(nums1, i, nums2, j + k / 2 , k - k / 2);
}
}
}
提问:为什么赋予最大值 ?
答:赋予最大值的意思只是说如果第一个数组的K/2不存在,则说明这个数组的长度小于K/2,那么另外一个数组的前K/2个我们是肯定不要的。给你举个例子,加入第一个数组长度是2,第二个数组长度是12,则K为7,K/2为3,因为第一个数组长度小于3,则无法判断中位数是否在其中,而第二个数组的前3个肯定不是中位数!故当K/2不存在时,将其置为整数型最大值,这样就可以继续下一次循环。
这道题太牛逼了,一定要多复习。
题解的代码
日后复习重新编写
加了一点自己的逻辑,感觉更清晰了一点。
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m = len(nums1)
n = len(nums2)
left = (m+n+1)//2
right = (m+n+2)//2
return (self.findK(nums1,nums2,0,0,left)+self.findK(nums1,nums2,0,0,right))/2
def findK(self,nums1,nums2,i,j,k):
if i >= len(nums1) :
return nums2[j+k-1]
if j >= len(nums2) :
return nums1[i+k-1]
if k == 1 :
return min(nums1[i],nums2[j])
# 评论代码中,没说清楚是不是向下取整,不过首先:在二分法的题目中,一般都是向下取整
# 其次,其实向下取整也没关系,只要贯穿始终就好了,计算第 i+k//2-1 的数值,丢弃掉的时候
# 也是丢弃 k//2 个数
if i+k//2-1 < len(nums1):
value1 = nums1[i+k//2-1]
else :
value1 = inf
if j+k//2-1 < len(nums2):
value2 = nums2[j+k//2-1]
else :
value2 = inf
if value1 < value2 :
return self.findK(nums1,nums2,i+k//2,j,k-k//2)
elif value1 > value2 :
return self.findK(nums1,nums2,i,j+k//2,k-k//2)
# 自己加了一个相等的逻辑,也AC了,证明我对方法的理解是正确的
else :
if k % 2 == 0 :
return value1
else :
return self.findK(nums1,nums2,i+k//2,j+k//2,1)
121 买卖股票的最佳时机
未看解答自己编写的青春版
贪心,拆分利润。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices)==1:
return 0
n = len(prices)
diff = [0]*(n-1)
for i in range(1,n):
diff[i-1] = prices[i]-prices[i-1]
maxi = 0
count = 0
for i in diff :
if count + i < 0 :
count = 0
else :
count += i
maxi = max(maxi,count)
return maxi
重点
题解的代码
日后复习重新编写
55 跳跃游戏
未看解答自己编写的青春版
贪心,迭代更新 cover 的思想。另外循环中的判断很重要,保证当前下标要是当前cover可以到达的。
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
n = len(nums)
for i in range(n-1) :
# 这句判断很重要,当前下标要是当前cover可以到达的
if i <= cover :
cover = max(cover,i+nums[i])
if cover < n-1 :
return False
else :
return True
重点
题解的代码
日后复习重新编写
45 跳跃游戏 II
未看解答自己编写的青春版
class Solution:
def jump(self, nums: List[int]) -> int:
if len(nums)==1 :
return 0
cover = 0
maxdis = 0
step = 0
n = len(nums)
for i in range(n-1):
# 这道题这句判断反倒可以省略,因为题目确保了一定可以到达终点
if i <= cover :
maxdis = max(maxdis,i+nums[i])
if i == cover :
cover = maxdis
step += 1
if cover >= n-1 :
return step
重点
上面两道有关跳跃游戏的题目,如果想复习思路,可以重读卡哥的解答。
跳跃游戏
跳跃游戏 II
题解的代码
日后复习重新编写
763 划分字母区间
未看解答自己编写的青春版
用上哈希结构就可以了。
class Solution:
def partitionLabels(self, s: str) -> List[int]:
# 这道题的哈希结构,用数组和字典都可,字典更普适一些,数组因为这道题
# 说明了只包含小写字母,所以能用
table = {}
n = len(s)
for i in range(n):
table[s[i]] = i
res = []
start = 0
end = 0
for i in range(n):
if i <= end :
end = max(end,table[s[i]])
if i == end :
res.append(end-start+1)
start = end+1
end = start
return res
重点
题解的代码
日后复习重新编写
20 有效的括号
未看解答自己编写的青春版
class Solution:
def isValid(self, s: str) -> bool:
stack = []
n = len(s)
i = 0
while i < n :
if s[i]=='(' :
stack.append(')')
elif s[i]=='[' :
stack.append(']')
elif s[i]=='{' :
stack.append('}')
else :
if stack == [] or stack.pop() != s[i]:
return False
i += 1
if len(stack)!=0 :
return False
else :
return True
重点
题解的代码
日后复习重新编写
155 最小栈
未看解答自己编写的青春版
没思路。
重点
看了官方的题解,原来是用一个辅助栈,我以为只能用一个栈!还是对题意的理解不到位。
题解的代码
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = [math.inf]
def push(self, x: int) -> None:
self.stack.append(x)
self.min_stack.append(min(x, self.min_stack[-1]))
def pop(self) -> None:
self.stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
日后复习重新编写
394 字符串解码
未看解答自己编写的青春版
模拟就完事了,但是内存空间占用有点高,只打败5%,我觉得是因为我让一定条件下的解码字符入栈的原因。
class Solution:
def decodeString(self, s: str) -> str:
st = []
nums = ['0','1','2','3','4','5','6','7','8','9']
res = ''
for i in s :
if i == ']':
temp = ''
while st[-1]!='[' :
temp += st.pop()
st.pop()
number = ''
while st != [] and st[-1] in nums :
number += st.pop()
number = int(number[::-1])
temp = temp[::-1]
tt = temp*number
if st == []:
res = res + tt
else :
for j in tt :
st.append(j)
else :
st.append(i)
temp = ''
while st != []:
temp += st.pop()
temp = temp[::-1]
res += temp
return res
重点
评论里看了一个人的解法,觉得很简洁:
class Solution:
def decodeString(self, s: str) -> str:
stack = [] # (str, int) 记录左括号之前的字符串和左括号外的上一个数字
num = 0
res = "" # 实时记录当前可以提取出来的字符串
for c in s:
if c.isdigit():
num = num * 10 + int(c)
elif c == "[":
stack.append((res, num))
res, num = "", 0
elif c == "]":
top = stack.pop()
res = top[0] + res * top[1]
else:
res += c
return res
学习了,这种压栈弹栈的操作,确实比我的要少操作一些。之前做过的栈的题目,都是将元素压入,第一次遇见这种,压入某种信息的,思路拓宽!
题解的代码
日后复习重新编写
739 每日温度
未看解答自己编写的青春版
单调栈基础题目。
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
res = [0]*n
stack = [0]
for i in range(1,n):
while stack!=[] and temperatures[stack[-1]] < temperatures[i]:
idx = stack.pop()
res[idx] = i-idx
stack.append(i)
return res
重点
想明白单调栈的运作方式和结果数组之间的关系。
想明白要用while一直弹出。
栈里应该存储下标。
题解的代码
日后复习重新编写
84 柱状图中最大的矩形
未看解答自己编写的青春版
单调递减栈,找最临近的小值。本题需要注意的有两点:
1、前后加0,保证每个矩形都有左右边界,都有左右最小,不然计算会出问题
2、是通过当前矩形的高度,以及这个高度所能蔓延的最大宽度来计算面积的,所以要找寻当前高度下的,左右最临近小值的index,这样跨度就是 right-left-1 。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
# 前后加0,保证每个矩形都有左右边界,都有左右最小,不然计算会出问题
heights = [0] + heights + [0]
n = len(heights)
maxi = 0
stack = [0,1]
for i in range(2,n):
while stack!=[] and heights[i] < heights[stack[-1]]:
middle = stack.pop()
# 当前高度
h = heights[middle]
left = stack[-1]
right = i
# 当前高度对应的宽度
w = right-left-1
maxi = max(maxi,h*w)
stack.append(i)
return maxi