977.有序数组的平方
思路
题目要求处理的是按 非递减顺序 排序的整数数组,那么根据此排序特性,如果数组中存在正负数,那么该数组内元素平方的最大值,一定是在left和right这两个位置上。因为这个数组的绝对值变化呈先减后增趋势。那么我们就可以从两边开始,寻找最大值,一直探索到中间,直到所有的数字平方都记录在新的数组内。
下列两种代码都是运用了库函数,方便了算法过程。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(x**2 for x in nums)
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
for x in range(len(nums)):
nums[x]*=nums[x]
nums.sort()
return nums
双指针法
由于我们确定了两边开始探索的方法,那么可以采用双指针法的双向指针来解决这道题。比较两边的数字的平方大小,大的一边,指针前进一格,并将数字记录在新数组内。简单来说,两个人相向而行,谁投的数大,谁就往前走一步,直到一个人超过另一个人(left>right,跳出循环)。
新数组需要我们重新创建一个,做不到在原有数组上既更新数字,又比较大小。新数组从大到小开始记录,且记入数字后,新数组内的存放指标往前移动(index-=1),等待下一个数。
代码
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l=len(nums)
result=[0]*l # 创造一个和nums等长的全是0的result,等会儿往里面填数
left,right,index=0,l-1,l-1 # result从末尾开始往前填数字
while left<=right: # 不把left=right也算作循环的条件的话,会导致left=right这个位置上的数字不被记入result
if nums[left]**2>=nums[right]**2:
# 当最左边数字平方大于等于最右边数字平方
result[index]=nums[left]**2
left+=1 # 左指针往右移动
# 填入result最右边,left+1以排除已经记录的数字
else:
# 当最左边数字平方比最右边数字平方小
result[index]=nums[right]**2
right-=1 # 右指针往左移动
# 把大的那个填入result最右边,right-1以排除已经记录的数字
index-=1
# if else判定完后,因为索引index的位置已经填入元素,index-1使存放结果指针往前移一位
return result
209.长度最小的子数组
思路
如果是暴力解法的话,那就是两层循环,找出所有符合要求的子数组,最后得到长度最小的那一个。不过,因为暴力解法在leetcode运行会直接超时,所以我也不知道我自己的对不对…(bushi)
简便快捷的办法还是双指针法!
采用双指针法,right往前探索直到满足total>=target的条件,然后记录当前长度,固定right,left开始向前探索直到total<target,期间发现更小的区间则长度需要更新;最后right继续前进探索,直到到达边界,固定right,left持续前进,直到不再满足条件,同理,期间发现更小的区间则长度需要更新。
right作为主导的指针,每当其停止,left就要开始行动,直到right到达终止位置,left才会进行最后一次的探索。
滑动窗口
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。起始位置和终止位置可以理解为窗口的两边位置,而不是数组的起始和终止。起始位置是窗口开始向前移动的起点,而终止位置其实就是探索当前索引的数字是否能满足条件。
窗口就是 满足其和 ≥ target 的长度最小的连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)。在这道题,我们的起始位置就是窗口的left。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。在这道题,我们的起始位置就是不断探索的right。
解题的关键在于 窗口的起始位置如何移动,如图所示:
相比于暴力解法,滑动窗口方法会在发现第一个满足条件的子数组时,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
代码
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
l=len(nums)
left,right,total=0,0,0
minlen=float('inf') # 设置无穷大是为了确保minlen在后续min()会被更新
while right<l: # 当right=l,,会越界
total+=nums[right] # 此时会把当前right的数加进去,无论left之后是否行动,right再+1
while total>=target: # 右边界移动直到满足条件停止,这时候移动左边界判断当前区间能否更小
minlen=min(minlen,right-left+1) # 比较当前连续子数组长度,最小就记录,不是最小就保持
total-=nums[left] # 减去当前左区间的数字,下一步缩小区间
left+=1 # 左边界右移一格,缩小区间
right+=1 # 右边界右移一格进行下一轮循环判定
return minlen if minlen != float('inf') else 0
# 跳出循环就返回minlen
# 如果minlen是初始设置的无限大inf,则说明没有符合条件的子数组
59.螺旋矩阵II
注意点
这类模拟行为不涉及到什么算法,就是单纯的模拟,但是掌握不好代码,还是容易被自己的循环绕晕,甚至想多了就不想动脑了。而对于循环,最需要注意的,无疑是一开始定义的区间规则,需要一直遵守下去
思路
螺旋矩阵的循环体现在转圈上。
一圈的过程是:
填充上行从左到右 --> 填充右列从上到下 --> 填充下行从右到左 --> 填充左列从下到上。在此基础上循环,由外向内填入数字。
下方两种,方法一致,但是上下左右区间设置不同,都写了以防以后弄乱了。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
matrix=[[0]*n for _ in range(n)]
l,r,t,b=0,n,0,n # 设置上下左右区间,此时r,b为考虑下方range设置为n
num,end=1,n**2
while num<=end: # 所赋的值来作为循环条件
for i in range(l,r): # 填充上行,从左到右
matrix[t][i]=num # 固定在top行,列随着循环增加,
num+=1 # 记录数字逐渐增大
t+=1 # 到达一圈的右上角,top+=1,进入到下一行
for i in range(t,b): # 填充右列,从上到下
matrix[i][r-1]=num # 由于r=n且这一for循环要固定右列,r-1避免越界
num+=1
r-=1 # 到达一圈的右下角,right-=1,往左移动一列
for i in range(r-1,l-1,-1): # 填充下行,从右到左
# r-1才开能取到,又因为range右为开区间,所以用取不到的l-1,-1表示逆向
matrix[b-1][i]=num # 由于b=n且这一for循环要固定下行,b-1避免越界
num+=1
b-=1 # 到达一圈的左下角,bottom-=1,往上移动一行
for i in range(b-1,t-1,-1): # 填充左列,从下到上
# b-1才开能取到,又因为range右为开区间,所以用取不到的t-1,-1表示逆向
matrix[i][l]=num # 固定左列,填充数字
num+=1
l+=1 # 到达一圈的左上角,left+=1,往右移动一行
# 然后进入到下一圈内的起始点
return matrix
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
matrix=[[0]*n for _ in range(n)]
l,r,t,b=0,n-1,0,n-1
num,end=1,n**2
while num<=end:
for i in range(l,r+1): # r+1确保取不到
matrix[t][i]=num
num+=1
t+=1
for i in range(t,b+1): # b+1确保取不到
matrix[i][r]=num
num+=1
r-=1
for i in range(r,l-1,-1):
matrix[b][i]=num
num+=1
b-=1
for i in range(b,t-1,-1):
matrix[i][l]=num
num+=1
l+=1
return matrix