[33]除自身以外的数组的乘积 & 螺旋矩阵

# 除自身以外数组的乘积

题目要求

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。

请不要使用除法,且在 O(n) 时间复杂度内完成此题

示例 1:

输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:

输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
 

提示:

2 <= nums.length <= 105
-30 <= nums[i] <= 30
保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内
 

进阶:你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

思路

最为简单的思路就是依次把每个数组中元素自身以外数组的乘积都算出来,依次遍历数组,然后二次循环来计算每个元素除自身以外的乘积。这样的时间复杂度就是O(n^2),显然是不符合题目要求。

方法一:左右乘积列表

我们不必将所有数字的乘积除以给定索引处的数字得到相应的答案,而是利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀与后缀)相乘得到答案。

对于给定索引 i,我们将使用它左边所有数字的乘积乘以右边所有数字的乘积。下面让我们更加具体的描述这个算法。

然而如果直接计算前缀和后缀,依然面临两个循环的问题。这里一个优化的地方是当前数前缀的乘积等于上一个数*上一个数前缀的乘积。

L[i] = L[i-1] * nums[i-1]

后缀同理

这样就把得到前缀和后缀的过程进行了简化。

方法二:空间复杂度 O(1)O(1)O(1) 的方法

尽管上面的方法已经能够很好的解决这个问题,但是空间复杂度并不为常数。

由于输出数组不算在空间复杂度内,那么我们可以将 L 或 R 数组用输出数组来计算。先把输出数组当作 L 数组来计算,然后再动态构造 R 数组得到结果。让我们来看看基于这个思想的算法。

1.初始化 answer 数组,对于给定索引 i,answer[i] 代表的是 i 左侧所有数字的乘积。
2.构造方式与之前相同,只是我们试图节省空间,先把 answer 作为方法一的 L 数组。
3.这种方法的唯一变化就是我们没有构造 R 数组。而是用一个遍历来跟踪右边元素的乘积。并更新 数组 answer[i]=answer[i]∗R。然后 R 更新为 R=R∗nums[i],其中变量 R 表示的就是索引右侧数字的乘积。

代码如下

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        left = [1]
        right = [0 for i in range(len(nums)-1)]
        right.append(1)
        ans = []

        for i in range(len(nums)-1):
            left.append(nums[i] * left[i])

        for i in range(len(nums)-2, -1, -1):
            right[i] = nums[i+1] * right[i+1]
        for i in range(len(nums)):
            ans.append(left[i] * right[i])
        return ans

# 螺旋矩阵

题目要求

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:


输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

思路

自己的办法

将这个遍历的过程划分为两个部分,一是坐标x,y增加的过程,二是x,y减小的过程。也就是先是X变大,然后y变大,再x减小,y减小的重复过程。这个过程的变化就在于每次增大和减小的边界的变化。在x,y增大的部分完成后,y的下界上升1,x的上界应当减少了1。同样的,在减小的部分完成后,x的下界加1,y的上界减1。
但是比较蛇皮的地方是会多遍历最后的几个格子,简单的解决就是返回的时候就直接返回应该返回的长度就完了。

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        maxX = len(matrix[0])
        maxY = len(matrix)

        minX = 0
        minY = 0
        ans = []
        x, y = 0, 0
        flag = 1
        print(len(matrix[0]) * len(matrix))

        while len(matrix[0]) * len(matrix) > len(ans):
            
            for x in range(minX, maxX):
                ans.append(matrix[y][x])
            for y in range(minY + 1, maxY):
                ans.append(matrix[y][x])
            minY = minY + 1
            maxX = maxX - 1
            
            for x in reversed(range(minX, maxX)):
                ans.append(matrix[y][x])
            print(ans)

            for y in reversed(range(minY, maxY - 1)):
                ans.append(matrix[y][x])
            minX = minX + 1
            maxY = maxY - 1

        return ans[0:len(matrix[0]) * len(matrix)]
方法一:模拟

可以模拟螺旋矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。

判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 visited 中的对应位置的元素设为已访问。

如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。

这个方法的不同之处在于使用了一个数组来确定方向和用一个标记数组来标记访问过的位置。这样可以避免重复访问。

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        col = len(matrix[0])
        row = len(matrix)

        visited = [[False] * col for _ in range(row) ]
        directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]
        locX = 0
        locY = 0
        ans = []
        directionIndex = 0

        for i in range(col * row):
            
            ans.append(matrix[locY][locX])
            visited[locY][locX] = True
            
            nextX = directions[directionIndex][0] + locX
            nextY = directions[directionIndex][1] + locY

            if not (0 <= nextX < col and 0 <= nextY < row and not visited[nextY][nextX]):
                directionIndex = (directionIndex+1) % 4
            locX = directions[directionIndex][0] + locX
            locY = directions[directionIndex][1] + locY
        return ans

方法二:按层模拟

这个方法跟我自己写的思路差不多,不过叙述的不一样

可以将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素。

定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,下图矩阵最外层元素都是第1 层,次外层元素都是第 2层,剩下的元素都是第 3 层。

[[1, 1, 1, 1, 1, 1, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 2, 3, 3, 3, 2, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 1, 1, 1, 1, 1, 1]]

对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top,left),右下角位于 (bottom,right),按照如下顺序遍历当前层的元素。

从左到右遍历上侧元素,依次为 (top,left) 到 (top,right)。

从上到下遍历右侧元素,依次为 (top+1,right) 到 (bottom,right)。

如果 left<right 且 top<bottom,则从右到左遍历下侧元素,依次为 (bottom,right−1) 到 (bottom,left+1),以及从下到上遍历左侧元素,依次为 (bottom,left) 到 (top+1,left)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值