代码随想录训练营 Day2打卡 数组part02 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

代码随想录训练营 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来表示当前填充范围的上、下、左、右边界的索引,通过循环更新这些边界,并在每一圈内部按照边界顺序填充数字,直至上下左右边界交汇停止。这种方式使得代码结构更为直观,逻辑清晰。

力扣题目链接
题目文章讲解
题目视频讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值