代码随想录算法训练营第二天| 977.有序数组的平方 、209.长度最小的子数组、 59.螺旋矩阵II|纯小白python

977.有序数组的平方

题目

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序排序。

原始思路

class Solution(object):
    def sortedSquares(self, nums):
        for i in range(len(nums)):
            nums[i]=nums[i]**2
        nums.sort()
        return nums

双指针法

由代码随想录可知,整体流程如下:

数组其实是有序的, 只不过负数平方之后可能成为最大数了。

那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。

如果A[i] * A[i] < A[j] * A[j] 那么result[k--] = A[j] * A[j]; 。

如果A[i] * A[i] >= A[j] * A[j] 那么result[k--] = A[i] * A[i]; 。

尝试使用双指针法

class Solution(object):
    def sortedSquares(self, nums):
        l=0
        r=len(nums)-1
        k=len(nums)
        nums_1=[1]*len(nums)
        while 0<=l<len(nums) and 0<=r<len(nums) and l<=r and 0<k<=len(nums):
            if nums[l]**2<nums[r]**2:
                k-=1
                nums_1[k]=nums[r]**2
                r-=1
            else:
                k-=1
                nums_1[k]=nums[l]**2
                l+=1
        return nums_1

在这里我一开始令nums_1=nums,结果一直出错。我就debug发现,在将nums[r]**2或nums[l]**2赋给nums_1[k]时,nums的值也会改变。这我才想到nums_1=nums,是将nums_1指向nums的引用了,改变一个另一个也会改变。所以我只能再创建一个不是指向和nums同一个地址的数组才使得运算正确。

代码随想录的代码

双指针法

代码
class Solution(object):
    def sortedSquares(self, nums):
        l, r, i = 0, len(nums)-1, len(nums)-1
        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
学习

1.我在写while进行判断时,害怕有条件漏了,所以把能写的都写上了。但其实再看有些条件是不需要的,只要让l小于等于r就行。这样不会使i减到小于0。

2.它将i-=1放到了最后,确实可以减少代码的冗余。在之后写完代码后,要检查一下看哪些步是可以合并写在一起。

3.其他的基本思路是一样的。

除了双指针法和暴力排序法(代码同原始思路里的代码),代码随想录还提供了暴力排序法+列表推导法和双指针法+反转列表

暴力排序法+列表推导法

代码
class Solution(object):
    def sortedSquares(self, nums):
        return sorted(x*x for x in nums)
学习

列表推导法的基本语法如下:

[expression for item in iterable if condition]
  • expression:用于生成新列表元素的表达式,通常是对原始元素的操作或转换。
  • item:表示从iterable中取出的每个元素。
  • iterable:一个可迭代对象,如列表、元组、集合、字符串等。
  • condition:(可选)一个过滤条件,只有满足条件的元素才会被包含在新列表中。

 双指针+反转列表

代码
class Solution(object):
    def sortedSquares(self, nums):
        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.比较除了直接平方比较外,还可以比较绝对值

2.append是直接在数组最后添加元素,其实我一开始也是想建立一个空列表,但是在赋值的时候报错了,因为没有保证空列表有足够的内存空间。应该用append,但是当时没想到

3.以上生成的列表是一个倒叙的,所以利用切片,步长为-1,进行反转列表

209.长度最小的子数组

题目

给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的子数组[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

原始思路

class Solution(object):
    def minSubArrayLen(self, target, nums):
        j,m=0,0
        nums_1=[]
        for i in range(len(nums)):
            s=0
            for j in range(i,len(nums)):
                s=s+nums[j]
                if s>=target and (s-nums[j])<target:
                    nums_1.append(j+1-i)
                    m=min(nums_1)
        return m

显示超出时间限制

滑动窗口

由ChatGPT得到: 

滑动窗口是一种用于高效解决数组或字符串问题的算法技巧。它主要用于连续子数组或子字符串问题。其核心是在数组或字符串上维护一个固定大小或动态调整大小的窗口,通过左右指针来控制窗口的范围。窗口的数据不断变化,以满足特定的条件。

具体实现步骤

1.初始化:

  • 定义两个指针,通常称为left和right,分别指向窗口的左边界和右边界。
  • 根据问题需要,初始化一个变量来存储窗口内的值(如窗口内元素的和、最大值等)

2.扩展窗口:

  • 移动right指针来扩展窗口,并更新窗口内的值
  • 检查是否满足特定条件 

3.收缩窗口: 

  • 当满足条件时,尝试收缩窗口,即移动left指针,并更新窗口内的值
  • 更新结果(如最小长度、最大和等)

4.重复:

  • 重复扩展和收缩窗口的过程,直到遍历完数组或字符串 

尝试使用滑动窗口

class Solution(object):
    def minSubArrayLen(self, target, nums):
        left,right,m,s=0,len(nums)-1,0,0
        nums_1=[]
        while right>=0:
            for left in range(right+1):
                s=sum(nums[left:right+1])
                if s>=target:
                    left+=1
                    nums_1.append(right + 2 - left)
                    m=min(nums_1)
            right-=1
        return m

结果还是显示超出时间,说明写得不是滑动窗口

主要问题:

1.嵌套循环:使用嵌套的while和for循环,遍历所有可能的子数组,再加上求和操作本身也是O(n),所以时间复杂度为O(n^3)。

2.求和操作:在for循环中,每次都计算子数组的和,导致了额外的时间复杂度。

3.不动态调整窗口:没有动态地调整窗口,而是通过固定范围来求和。

代码随想录的代码 

滑动窗口法

代码
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
学习

我从ChatGPT中得到的滑动窗口的定义来写代码,但是我没有真正理解滑动窗口的意思。以下是代码随想录中给出的解释:

target=7, 数组是 2,3,1,2,4,3,查找的过程如下:

209.长度最小的子数组

本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的连续子数组。

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于窗口的起始位置如何移动。滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

所以我对滑动窗口理解错误,一开始想着是只要两个指针都移动把所有数组中的元素遍历一遍就行。但是正如上面尝试代码中那样,这样做并未降低时间复杂度 。如同上述所说,我一开始写的代码没有做到根据子序列和大小的情况来调整窗口大小。只是一味地遍历每一个子数组。

我一开始不知道怎么算最小的长度,所以在暴力法中,我找那个索引,加上这个索引对应的值大于等于目标值,减去后又小于目标值。此时就是子数组的长度,然后再把这个索引放到一个数组里面,最后求这个数组里的最小值。在滑动窗口法中,它找最小长度是先找到满足条件的窗口长度,然后逐渐缩小这个窗口,看是否满足条件。如果不满足,就改变窗口的位置,往前移一位看是否满足条件。位置满足后便改变大小,便记录满足条件的窗口大小。总之就是先根据条件找窗口位置,再找窗口大小,最后记录大小。

还有就是min_len=float('inf')。这个是我之前没有见到的,意思是使min_len赋值为无穷大,也是一种初始化。这样最开始比大小的时候,肯定谁都比这个值小,这样就能把第一个值记录起来了,之后将所有符合条件的窗口大小一一比较,就能得出最小长度。

最后就是最终的输出return min_len if min_len != float('inf') else 0,也是要学会这个写法。

暴力法 

代码
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
学习

整理了前面的滑动窗口法后,再看这个代码就明白了。包括min_len=float('inf'),return min_len if min_len != float('inf') else 0这些语句。而其中的逻辑就是两个嵌套循环,一个一个改变位置,遍历所有的子数组,找满足条件的。

类似题目

904.水果成篮
题目

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i]是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

思路

题目可以转换为找一个子数组,里面只能有两种元素。再求这个子数组的最大长度。这个和209不同是找最大。所以我想让指针都指向结尾,然后左移指针找到最大的窗口长度。但是我想不出如何判断子数组中只有两个元素。看了题解之后,发现大家说要用哈希表,但是我还没学哈希表,所以就先放在这里,等学到之后,再来看这道题。

76.覆盖最小子串
题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""。

思路

这个应该和上面一样应该也是要用到哈希表,等学了吧。。。。

59.螺旋矩阵II

题目

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

原始思路

没有思路

代码随想录

代码1
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
代码2
class Solution(object):
    def generateMatrix(self, n):
        if n <= 0:
            return []
        
        # 初始化 n x 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

            # 从右到左填充下边界

            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
学习

螺旋矩阵的基本思想就是按照顺时针或者逆时针(本题是顺时针)的顺序,从内到外逐层遍历一个二维矩阵。螺旋矩阵的典型遍历顺序通常是:从上行向右,右列向下,下行向左,左列向上,然后重复该过程,逐步缩小遍历的范围,直至遍历完整个矩阵。

遍历顺序:

1.从左到右遍历顶行

2.从上到下遍历右列

3.从右到左遍历底行

4.从下到上遍历左列

5.缩小范围,重复上述步骤,直至所有元素都被遍历

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值