加班到晚上十点钟回来实在刷不动题了,第二天早上起来摸鱼刷题,否则要追不上了!
977.有序数组的平方
题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/
一开始没看懂题,直接无脑写成了这样。但是这里有个低级问题,就是range是左闭右开,所以这里应该写的是len(nums)不用减一。
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
for i in range(len(nums)-1):
nums[i] = nums[i] * nums[i]
return nums
然后果然运行不通过,发现原来还要排序。考虑到我现在还没写过排序题,之前学的冒泡排序也忘差不多了, 时间又有限,决定先直接sort一个,居然通过了。
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
for i in range(len(nums)):
nums[i] = nums[i] * nums[i]
list.sort(nums)
return nums
但是正经解法还是要学的,直接看讲解。
讲解链接:https://www.bilibili.com/video/BV1QB4y1D7ep
一眼看到双指针这个关键词,前天才刷过,感觉之后应该要总结一下双指针的思路是什么了。
1.重要的数据特征:因为有负数,同时是非递减数组,最大元素肯定在两边。
2.双指针可以从两边往中间走,也就是从大到小取。然后对于我们新建的数组,下标从右向左开始更新就行了。
看完第一遍写成了这样,运行直接爆炸。检查了之后发现问题出在vec的定义上,只更新了后两位。因为有两个指针,所以while的轮数是len(nums)/2,这就是问题。
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
vec = nums
i = 0
j = len(nums) - 1
k = len(nums) - 1
while i <= j:
if nums[i]*nums[i] > nums[j]*nums[j]:
vec[k] = nums[i]*nums[i]
k-=1
i+=1
else:
vec[k] = nums[j]*nums[j]
k-=1
j-=1
return vec
把vec换成空列表的定义,运行通过。
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
vec = [float('inf')] * len(nums)
i = 0
j = len(nums) - 1
k = len(nums) - 1
while i <= j:
if nums[i]*nums[i] > nums[j]*nums[j]:
vec[k] = nums[i]*nums[i]
k-=1
i+=1
else:
vec[k] = nums[j]*nums[j]
k-=1
j-=1
return vec
209.长度最小的子数组
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
训练营提示让直接看题解,于是照做了,题解链接:https://www.bilibili.com/video/BV1tZ4y1q7XE
关键词是滑动窗口和双指针,又是双指针……我现在把它理解为是在一个循环中同时进行两个循环的过程,可以并列、对冲之类的。。两个指针中间的部分就是滑动窗口了。
for循环里面的j表示的是终止位置,然后起始位置i在里面动态移动。看完有点似懂非懂,于是直接看答案,又让GPT给我讲了一遍:
(版本一)滑动窗口法
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
left = 0
right = 0
min_len = float('inf')
cur_sum = 0 #当前的累加值
while right < l:
cur_sum += nums[right]
while cur_sum >= s: # 当前累加值大于目标值
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]
left += 1
right += 1
return min_len if min_len != float('inf') else 0
下面是代码的解读:
初始化变量:
l
表示整数列表nums的长度。left
和right
表示滑动窗口的左右指针,初始都指向列表的第一个元素。min_len
用于记录最短子数组的长度,初始值设为正无穷。cur_sum
表示当前的累加值,初始值为0。进入
while
循环,条件是right
小于列表的长度。在循环中,首先将当前元素
nums[right]
添加到累加值cur_sum
中。进入第二个
while
循环,条件是当前累加值cur_sum
大于等于目标值s
。
- 在这个循环中,我们更新最短子数组的长度
min_len
,通过将当前窗口的长度right - left + 1
与min_len
比较并取较小值。- 然后,从当前累加值中减去左指针指向的元素
nums[left]
,并将左指针向右移动一位left += 1
,以缩小窗口的大小。无论是否进入第二个
while
循环,都将右指针向右移动一位right += 1
,扩大窗口的大小。循环结束后,返回最短子数组的长度
min_len
,如果min_len
仍然是正无穷,则返回0,表示没有找到满足条件的子数组。这段代码的核心思想是使用两个指针来表示滑动窗口的左右边界,通过不断调整窗口的大小,找到满足条件的最短子数组。
用同样的方法过了一遍暴力解法。还是头一次看到这样写for循环的,内层循环的开头是外层循环的索引……
(版本二)暴力法
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
min_len = float('inf')
for i in range(l):
cur_sum = 0
for j in range(i, l):
cur_sum += nums[j]
if cur_sum >= s:
min_len = min(min_len, j - i + 1)
break
return min_len if min_len != float('inf') else 0
下面是代码的解读:
初始化变量:
l
表示整数列表nums的长度。min_len
用于记录最短子数组的长度,初始值设为正无穷。进入外层循环,遍历整数列表nums中的每个元素,使用变量
i
表示当前元素的索引。在外层循环中,初始化当前累加值
cur_sum
为0。进入内层循环,从当前索引
i
开始遍历到列表的末尾。
- 在内层循环中,将当前元素
nums[j]
添加到累加值cur_sum
中。- 如果当前累加值
cur_sum
大于等于目标值s
,则满足条件,更新最短子数组的长度min_len
,通过将当前子数组的长度j - i + 1
与min_len
比较并取较小值。- 然后跳出内层循环,因为我们已经找到了满足条件的最短子数组。
外层循环继续进行,遍历下一个元素。
循环结束后,返回最短子数组的长度
min_len
,如果min_len
仍然是正无穷,则返回0,表示没有找到满足条件的子数组
59. 螺旋矩阵Ⅱ
一眼看过去还是不知道怎么解,于是直接看视频讲解,链接:https://www.bilibili.com/video/BV1SL4y1N7mV/?vd_source=40781b3117fa72cc67da258629107b62
真的是一圈一圈写的,原理理解了,代码不理解,于是直接看答案:
讲解中写的是,这个题和二分法一样,并不涉及到什么算法,就是模拟过程,需要坚持循环不变量原则。看代码是直接看懂了,但是没有动手写,时间来不及了。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
nums = [[0] * n for _ in range(n)]
startx, starty = 0, 0 # 起始点
loop, mid = n // 2, n // 2 # 迭代次数、n为奇数时,矩阵的中心点
count = 1 # 计数
for offset in range(1, loop + 1) : # 每循环一层偏移量加1,偏移量从1开始
for i in range(starty, n - offset) : # 从左至右,左闭右开
nums[startx][i] = count
count += 1
for i in range(startx, n - offset) : # 从上至下
nums[i][n - offset] = count
count += 1
for i in range(n - offset, starty, -1) : # 从右至左
nums[n - offset][i] = count
count += 1
for i in range(n - offset, startx, -1) : # 从下至上
nums[i][starty] = count
count += 1
startx += 1 # 更新起始点
starty += 1
if n % 2 != 0 : # n为奇数时,填充中心点
nums[mid][mid] = count
return nums
训练营提示说让做个数组总结,说实在的我实在是太菜了不会总结,于是去看了训练营的总结:https://programmercarl.com/%E6%95%B0%E7%BB%84%E6%80%BB%E7%BB%93%E7%AF%87.html#%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80
二分法:循环不变量原则,需要在循环中坚持对区间的定义,是算法面试中的常考题。
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。
滑动窗口:精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
模拟行为:真正解决题目的代码都是简洁的,或者有原则性的