LeetCode 2245. 转角路径的乘积中最多能有几个尾随零

2245. 转角路径的乘积中最多能有几个尾随零

给你一个二维整数数组 grid ,大小为 m x n,其中每个单元格都含一个正整数。

转角路径 定义为:包含至多一个弯的一组相邻单元。具体而言,路径应该完全 向水平方向 或者 向竖直方向 移动过弯(如果存在弯),而不能访问之前访问过的单元格。在过弯之后,路径应当完全朝 另一个 方向行进:如果之前是向水平方向,那么就应该变为向竖直方向;反之亦然。当然,同样不能访问之前已经访问过的单元格。

一条路径的 乘积 定义为:路径上所有值的乘积。

请你从 grid 中找出一条乘积中尾随零数目最多的转角路径,并返回该路径中尾随零的数目。

注意:

  • 水平 移动是指向左或右移动。
  • 竖直 移动是指向上或下移动。

示例 1:

输入:grid = [[23,17,15,3,20],[8,1,20,27,11],[9,4,6,2,21],[40,9,1,10,6],[22,7,4,5,3]]
输出:3
解释:左侧的图展示了一条有效的转角路径。
其乘积为 15 * 20 * 6 * 1 * 10 = 18000 ,共计 3 个尾随零。
可以证明在这条转角路径的乘积中尾随零数目最多。

中间的图不是一条有效的转角路径,因为它有不止一个弯。
右侧的图也不是一条有效的转角路径,因为它需要重复访问已经访问过的单元格。

示例 2:

输入:grid = [[4,3,2],[7,6,1],[8,8,8]]
输出:0
解释:网格如上图所示。
不存在乘积含尾随零的转角路径。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10^5
  • 1 <= m * n <= 10^5
  • 1 <= grid[i][j] <= 1000

提示 1

What actually tells us the trailing zeros of the product of a path?


提示 2

It is the sum of the exponents of 2 and sum of the exponents of 5 of the prime factorizations of the numbers on that path. The smaller of the two is the answer for that path.


提示 3

We can then treat each cell as the elbow point and calculate the largest minimum (sum of 2 exponents, sum of 5 exponents) from the combination of top-left, top-right, bottom-left and bottom-right.


提示 4

To do this efficiently, we should use the prefix sum technique.

 

解法:维护各个方向的前缀和

提示 1

一定存在至少一个两端位于数组边界的路径,它的乘积的尾随零数量最多。

我们可以用反证法证明。假设存在一条至少有一端不位于数组边界的路径,它的乘积尾随零数量大于任何两端位于数组边界的路径。

那么我们将该路径的两个端点延长至数组边界,此时乘积尾随零数量不可能减少,这就产生了矛盾。

提示 2

对于长宽不为 1 的数组 grid,一定存在至少一个带有一个转角的路径,它的乘积的尾随零数量最多。

我们可以用反证法证明。假设存在一条不带转角的路径,它的乘积尾随零数量大于任何两端带有一个转角的路径。

那么我们将该路径的一个端点沿着任意一个互相垂直的方向延伸,此时乘积尾随零数量不可能减少,这就产生了矛盾。

提示 3

一条路径中乘积尾随零的数量等于该乘积质因数分解中因子 2 的数目与因子 5 的数目的较小值。

思路与算法

根据 提示 1 与 提示 2,对于任意一个确定的转角点,对应的乘积尾随零数量最大的路径一定是该点沿着某一水平方向以及某一竖直方向延伸至数组边界的四条路径之一。

因此,我们可以遍历 grid 中的所有单元格,并维护每个单元格作为转角点,对应路径乘积尾随零最大数量的最大值。

如果我们对于每一个单元格,都暴力计算每条可能路径的对应值,那么我们遍历每个单元格都需要 O(m+n) 的时间复杂度,总时间复杂度 O(mn(m+n)) 显然不符合题目要求。因此我们需要对算法的时间复杂度进行优化。

我们可以将每一条路径拆成 3 个互不相交的部分,即转角点,水平方向延伸部分,竖直方向延伸部分。同时,根据 提示 3,我们用 8 个二维数组 (l∣r∣u∣d) (2∣5) 表示每个点对应方向(左右上下)延伸部分乘积中 2 或 5 的因子个数。同时,我们用 c2 (i,j) 与 c5 (i,j) 表示 (i,j) 坐标数值中 2 或 5 的因子个数。因此以 (i,j) 转角点,对应的最大乘积尾随零数量一定是下列四个数值中的最大值:

而对于这 8 个二维数组,以数组 u 2 ​ 为例,我们有:

因此我们可以通过按照对应方向遍历数组,并以类似计算前缀和的方式,预处理更新这 8 个二维数组。这样,我们可以将预处理与遍历的时间复杂度都优化至 O(mn)。

综上,我们首先通过计算前缀和的方式按照不同方向遍历数组预处理出上文的 8 个二维数组,随后枚举转角点,计算对应路径乘积尾随零的最大数量,并维护这些数量的最大值。最终,我们返回该最大值作为答案。

Java版:

class Solution {
    public int maxTrailingZeros(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] l2 = new int[m][n];
        int[][] l5 = new int[m][n];
        int[][] r2 = new int[m][n];
        int[][] r5 = new int[m][n];
        int[][] u2 = new int[m][n];
        int[][] u5 = new int[m][n];
        int[][] d2 = new int[m][n];
        int[][] d5 = new int[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i != 0) {
                    u2[i][j] = u2[i - 1][j] + cnt(grid[i - 1][j], 2);
                    u5[i][j] = u5[i - 1][j] + cnt(grid[i - 1][j], 5);
                }
                if (j != 0) {
                    l2[i][j] = l2[i][j - 1] + cnt(grid[i][j - 1], 2);
                    l5[i][j] = l5[i][j - 1] + cnt(grid[i][j - 1], 5);
                }
            }
        }
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                if (i != m - 1) {
                    d2[i][j] = d2[i + 1][j] + cnt(grid[i + 1][j], 2);
                    d5[i][j] = d5[i + 1][j] + cnt(grid[i + 1][j], 5);
                }
                if (j != n - 1) {
                    r2[i][j] = r2[i][j + 1] + cnt(grid[i][j + 1], 2);
                    r5[i][j] = r5[i][j + 1] + cnt(grid[i][j + 1], 5);
                }
            }
        }

        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int c2 = cnt(grid[i][j], 2);
                int c5 = cnt(grid[i][j], 5);
                ans = Math.max(ans, Math.min(u2[i][j] + l2[i][j] + c2, u5[i][j] + l5[i][j] + c5));
                ans = Math.max(ans, Math.min(u2[i][j] + r2[i][j] + c2, u5[i][j] + r5[i][j] + c5));
                ans = Math.max(ans, Math.min(d2[i][j] + l2[i][j] + c2, d5[i][j] + l5[i][j] + c5));
                ans = Math.max(ans, Math.min(d2[i][j] + r2[i][j] + c2, d5[i][j] + r5[i][j] + c5));
            }
        }
        return ans;
    }

    private int cnt(int num, int x) {
        int res = 0;
        while (num % x == 0) {
            res++;
            num /= x;
        }
        return res;
    }
}

Python3版:

class Solution:
    def maxTrailingZeros(self, grid: List[List[int]]) -> int:
        # 计算一个整数质因数分解中 2 的数量
        def cnt2(num: int) -> int:
            res = 0
            while num % 2 == 0:
                res += 1
                num //= 2
            return res
        
        # 计算一个整数质因数分解中 5 的数量
        def cnt5(num: int) -> int:
            res = 0 
            while num % 5 == 0:
                res += 1
                num //= 5
            return res
        
        m = len(grid)
        n = len(grid[0])
        # 每个方向延伸部分乘积中因子 2 or 5 的数量
        l2 = [[0] * n for _ in range(m)]
        l5 = [[0] * n for _ in range(m)]
        r2 = [[0] * n for _ in range(m)]
        r5 = [[0] * n for _ in range(m)]
        u2 = [[0] * n for _ in range(m)]
        u5 = [[0] * n for _ in range(m)]
        d2 = [[0] * n for _ in range(m)]
        d5 = [[0] * n for _ in range(m)]

        for i in range(m):
            for j in range(n):
                if i != 0:
                    u2[i][j] = u2[i - 1][j] + cnt2(grid[i - 1][j])
                    u5[i][j] = u5[i - 1][j] + cnt5(grid[i - 1][j])
                if j != 0:
                    l2[i][j] = l2[i][j - 1] + cnt2(grid[i][j - 1])
                    l5[i][j] = l5[i][j - 1] + cnt5(grid[i][j - 1]) 
        
        for i in range(m - 1, -1, -1):
            for j in range(n - 1, -1, -1):
                if i != m - 1:
                    d2[i][j] = d2[i + 1][j] + cnt2(grid[i + 1][j])
                    d5[i][j] = d5[i + 1][j] + cnt5(grid[i + 1][j])
                if j != n - 1:
                    r2[i][j] = r2[i][j + 1] + cnt2(grid[i][j + 1])
                    r5[i][j] = r5[i][j + 1] + cnt5(grid[i][j + 1]) 

        ans = 0
        for i in range(m):
            for j in range(n):
                # 枚举转角点
                c2 = cnt2(grid[i][j])
                c5 = cnt5(grid[i][j])
                ans = max(ans, min(u2[i][j] + l2[i][j] + c2, u5[i][j] + l5[i][j] + c5))
                ans = max(ans, min(u2[i][j] + r2[i][j] + c2, u5[i][j] + r5[i][j] + c5))
                ans = max(ans, min(d2[i][j] + l2[i][j] + c2, d5[i][j] + l5[i][j] + c5))
                ans = max(ans, min(d2[i][j] + r2[i][j] + c2, d5[i][j] + r5[i][j] + c5))
                
        return ans

复杂度分析

  • 时间复杂度:O(mn),其中 m,n 分别为二维数组 grid 的长与宽。即为计算各方向前后缀和以及遍历转角对应点的时间复杂度。
  • 空间复杂度:O(mn),即为前缀和辅助数组的空间开销。
  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值