代码随想录训练营 Day2打卡 数组part02
一、 力扣977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
题目建议: 本题关键在于理解双指针思想
版本一 双指针法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l, r, i = 0, len(nums)-1, len(nums)-1 # 初始化左(l)右(r)指针以及结果列表的填充指针(i)
res = [float('inf')] * len(nums) # 预先定义与输入列表等长的结果列表,初始化为正无穷大,便于后续更新最小值
while l <= r: # 当左指针不大于右指针时循环
if nums[l] ** 2 < nums[r] ** 2: # 比较左右指针对应元素平方的大小
res[i] = nums[r] ** 2 # 将较大的平方值放入结果列表的当前尾部位置
r -= 1 # 移动右指针向左,探索更小的元素
else:
res[i] = nums[l] ** 2 # 否则,将左侧元素平方放入结果列表
l += 1 # 移动左指针向右
i -= 1 # 移动结果列表的填充指针向前,准备放置下一个更大的平方值
return res # 返回排序后的平方数列表
思路讲解:此版本采用双指针技巧,在原数组上同时从两端开始遍历。每次比较两端元素的平方值,将较大者放入结果列表的末尾(由于是倒序填充,实际是头部),并相应地移动指针。这种方法确保了结果列表始终以降序填充,最终得到升序的平方数列表。
版本二 暴力排序法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
for i in range(len(nums)):
nums[i] *= nums[i] # 先计算每个元素的平方
nums.sort() # 直接对整个列表进行排序
return nums
思路讲解:该版本简化了问题处理方式,首先通过列表推导式或循环计算每个元素的平方,随后直接调用列表的排序方法。这种方法直观且易于理解,但时间复杂度较高,为O(nlogn),其中n为列表长度。
版本三 暴力排序法 + 列表推导法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(x*x for x in nums)
思路讲解:此版本进一步简化,使用一行代码完成任务。首先利用列表推导式计算每个元素的平方,然后直接通过sorted()函数对新生成的列表进行排序。这是一种简洁高效的实现方式,同样基于O(nlogn)的排序算法。
版本四 双指针 + 反转列表
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
new_list = [] # 初始化一个新的空列表用于存放平方值
left, right = 0 , len(nums) -1 # 定义左右指针
while left <= right: # 当左右指针相遇前循环
if abs(nums[left]) <= abs(nums[right]): # 比较绝对值确定平方后较大的数
new_list.append(nums[right] ** 2) # 将较大的数平方后加入结果列表
right -= 1 # 右指针向左移动
else:
new_list.append(nums[left] ** 2) # 否则将左指针对应的数平方后加入
left += 1 # 左指针向右移动
return new_list[::-1] # 计算完毕后,反向输出结果列表以获得升序排列
思路讲解:这一版本同样是双指针策略,但它的独特之处在于它直接构建了一个降序的平方数列表,之后通过列表的切片操作[::-1]反转列表,达到升序排序的目的。这种方式避免了额外的排序步骤,时间复杂度仍为O(n),空间复杂度也为O(n)。
二、 力扣209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
版本一 滑动窗口法
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 # 初始化当前窗口内的和为0
while right < l: # 当右指针未超过数组长度时循环
cur_sum += nums[right] # 将右指针指向的元素加入当前和
while cur_sum >= s: # 如果当前和大于等于目标值s
# 更新最小长度为当前窗口长度(右-左+1)和之前记录的最小长度中的较小值
min_len = min(min_len, right - left + 1)
# 缩小窗口,从窗口中移除左边界的元素值
cur_sum -= nums[left]
left += 1
# 移动右指针,扩大窗口
right += 1
# 如果找到了符合条件的子数组,返回其最短长度;否则返回0
return min_len if min_len != float('inf') else 0
思路讲解:滑动窗口法的关键在于维护一个可以滑动的窗口(由左右指针界定),窗口内元素之和与目标值s做比较。当窗口内的和首次满足条件时,尝试缩小窗口寻找更短的符合条件的子数组,通过不断移动左右指针实现窗口的滑动与调整。
版本二 暴力法
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums) # 获取数组长度
min_len = float('inf') # 初始化最小长度为正无穷大
# 双重循环,遍历所有可能的子数组
for i in range(l): # i为子数组起始位置
cur_sum = 0 # 为每次新的起始位置重置当前和
for j in range(i, l): # j为子数组结束位置
cur_sum += nums[j] # 累加当前子数组的和
# 当累加和大于等于目标值时,更新最小长度并跳出内层循环
if cur_sum >= s:
min_len = min(min_len, j - i + 1)
break
# 如果找到了符合条件的子数组,返回其最短长度;否则返回0
return min_len if min_len != float('inf') else 0
思路讲解:暴力法通过两层循环枚举所有可能的子数组,对每个子数组求和并与目标值s比较。一旦找到一个和大于等于s的子数组,就计算其长度并考虑是否更新最小长度。这种方法简单直接,但效率较低,尤其是当数组很大时。
三、 力扣59. 螺旋矩阵II
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
在解决编程问题时,代码的一致性和清晰性至关重要。一个常见的问题是如何在循环和条件语句中处理区间边界,尤其是在处理数组和矩阵时。这种边界定义的不一致往往会导致代码变得混乱和难以维护。这通常是因为在定义和处理矩阵的边界时使用了不同的开闭原则,如左开右闭、左闭右闭和左闭右开,导致逻辑混乱。
为了解决这一问题,采用统一的区间处理原则,即“左闭右开”,可以显著提高代码的整洁性和可读性。
版本一 按圈层填充
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
nums = [[0] * n for _ in range(n)] # 初始化n×n的矩阵,全部填充为0
startx, starty = 0, 0 # 定义填充起始点
loop, mid = n // 2, n // 2 # 计算迭代次数(圈数)和当n为奇数时的中心点
count = 1 # 初始化计数器
# 外层循环控制每一圈的偏移量
for offset in range(1, loop + 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 # 更新起始点x坐标
starty += 1 # 更新起始点y坐标
# 如果n为奇数,单独处理中间元素
if n % 2 != 0:
nums[mid][mid] = count
return nums
思路讲解:这个版本的代码通过外部循环控制“圈”的迭代,每一轮迭代中分别填充上、右、下、左边界的元素。随着每轮的迭代,起始点逐步右移和下移,形成螺旋填充的模式。对于奇数n的情况,还需要单独处理中心点。
版本二 定义四个边界
class Solution:
def generateMatrix(self, n):
if n <= 0:
return []
# 初始化 n × n 的零矩阵
matrix = [[0]*n for _ in range(n)]
# 定义四个边界
top, bottom, left, right = 0, n-1, 0, n-1
num = 1 # 当前要填充的数字
# 当上下边界没有交错且左右边界没有交错时继续填充
while top <= bottom and left <= right:
# 从左到右填充上边界
for i in range(left, right + 1):
matrix[top][i] = num
num += 1
top += 1
# 从上到下填充右边界
for i in range(top, bottom + 1):
matrix[i][right] = num
num += 1
right -= 1
# 如果此时上边界还没有越过下边界,需要继续填充(防止偶数n时提前退出)
if top <= bottom:
# 从右到左填充下边界
for i in range(right, left - 1, -1):
matrix[bottom][i] = num
num += 1
bottom -= 1
# 从下到上填充左边界
for i in range(bottom, top - 1, -1):
matrix[i][left] = num
num += 1
left += 1
return matrix
思路讲解:此版本同样是为了生成螺旋矩阵,但采用了更加清晰的边界控制方式。定义了四个变量top、bottom、left、right来表示当前填充范围的上、下、左、右边界的索引,通过循环更新这些边界,并在每一圈内部按照边界顺序填充数字,直至上下左右边界交汇停止。这种方式使得代码结构更为直观,逻辑清晰。