977.有序数组的平方
文章讲解:代码随想录
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
思路:
暴力法:阅读完题目的第一想法就是先对数组中每个数值进行平方,然后排序。题目强调了非递减顺序,因此应该是从小到大排序。我先对数组中的数值循环平方后,使用了冒泡排序法进行排序。以下是我的代码:
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
for i in range(0, len(nums)):
nums[i] *= nums[i]
# 冒泡排序
for m in range(len(nums) - 1):
for n in range(len(nums) - m - 1):
if nums[n+1] < nums[n]:
nums[n], nums[n+1] = nums[n+1],nums[n]
return nums
很明显这种方法的时间复杂度是很高的,为O(n + nlogn),在查看了文章解析后,学习到了双指针法,根据昨天的学习经验,双指针法在解决数组这类问题中非常重要,要好好掌握。
双指针法:原数组本身是有序的,在平方后,数组中数值的位置会发生改变。不难理解,数组平方后的最大值一定在原数组的两端,此时就可以考虑双指针法。用两个指针来比较原数组左右两端平方后的数值,创建一个和原数组一样大的新数组,从最末端开始存放最大值,以此类推。代码如下:
左右指针:原数组的左右两端
i 指针:新数组的最右端即最末端
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
left, right, i = 0, len(nums) - 1, len(nums) - 1 # 定义三个指针,left和right指针分别在原数组的左右两端,i指针在新数组的末端
new_nums = [float('inf')] * len(nums) # 定义一个新数组用来存放平方后的数值,float('inf')表示正无穷大的浮点数
while left <= right:
if nums[left] ** 2 < nums[right] ** 2: # 如果右边指针所指数值平方后大于左边数值的平方,则将右边指针所指数值的平方值放在新数组的最末端
new_nums[i] = nums[right] ** 2
right -= 1 # 此时右指针需要往前一位,看下一个数值的平方
else:
new_nums[i] = nums[left] ** 2 # 同理,左边指针所指数值的平方大于右边指针所指数值的平方
left += 1
i -= 1 # 从最末端开始添加数值,添加一个后往前移一位
return new_nums
双指针的时间复杂度为O(n) ,与暴力法比提高了很多。
209.长度最小的子数组
文章讲解:代码随想录
视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili
思路:拿到题目后最直接的想法就是通过两层循环遍历,把所有数值和得出然后取最小长度,但是显然这种方法的时间复杂度很高为O(n^2)。训练营提示了这道题应该用滑动窗口的思路来做。
滑动窗口:简单来说,有两个指针分别称作左指针和右指针。左右指针之间的范围即为窗口。两个指针都是从数组的0位置起,右指针最先往右移动,左指针保持在原地,当窗口内的值的和大于等于目标值即符合题意时,右指针停止移动。此时通过移动左指针来找到窗口内符合题意的最小长度。代码中详细写了注释如下:
左右指针:区间内表示窗口
class Solution(object):
def minSubArrayLen(self, target, nums):
"""
:type target: int
:type nums: List[int]
:rtype: int
"""
left = 0 # 代表窗口的起始位置,初始化为0
right = 0 #代表窗口的终止位置,初始化为0
sum = 0 #当前窗口中的所有数值的累加值
min_length = float('inf') # 最小长度
sub_length = 0 # 右指针停止移动时的子序列长度
while right < len(nums): # 当右指针移动范围没有超过总长度时,右指针从0一直向右移动
sum += nums[right] # 计算右指针移动范围内值的总和
while sum >= target: # 如果该值大于等于目标值,右指针停止移动
sub_length = right - left + 1 # 此时左指针向右移动,计算左右指针之间范围内的长度
min_length = min(min_length, sub_length) # 取最小值即题目所求
sum -= nums[left] # 此时总和应该减去左指针移动过的值才是窗口范围内的总和
left += 1 # 左指针移动一次加一
right += 1 # 如果一直没有满足右指针移动范围内值的总和大于等于目标值,右指针一直向右移动加一
return min_length if min_length != float('inf') else 0 # 最终如果找到最小值返回最小长度,如果没有满足题目要求则返回0
滑动窗口法的时间复杂度为O(n),和暴力法相比大大提高了运行效率。
59.螺旋矩阵II
文章讲解:代码随想录
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
思路:读完题目后一脸懵,直接就看了视频讲解。这道题的关键就是明确好转一圈各边的边界,通过遵循左闭右开的原则,能够使每一边遵循同样的原则将这一圈走完。代码的核心部分就是定义好矩阵螺旋排序的起始位置和终止位置。代码以及详细的注释如下:
nums :n X n 矩阵里应该存放的数值
startX, startY:起始点的位置
class Solution(object):
def generateMatrix(self, n):
"""
:type n: int
:rtype: 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): # offset代表偏移量,为了保证每一边的遍历都是左闭右开,第一圈开始偏移一,逐圈增加
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
时间复杂度为O(n^2)。
今日心得:
首先关于有序数组的平方,学习到了使用双指针来解决,左右指针从数组两端开始计算数值的平方,依次将最大值放入新的数组中,这道题让我认识到了双指针的重要性。
其次,第二道题的滑动窗口的解决办法其实也是双指针进行操作,由此可见,双指针在解决数组问题中非常重要。滑动窗口的核心是先移动右指针,当达到某一条件后,再移动左指针从而得到最小值。
最后一道题在我看来是今天最复杂的一道题,需要非常细心,要明确好矩阵在螺旋排序时,每一条边的边界,明确好起始和终止位置。明天需要再巩固一遍!