前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >俺也一样。。。

俺也一样。。。

作者头像
五分钟学算法
发布2022-02-21 08:35:49
2290
发布2022-02-21 08:35:49
举报
文章被收录于专栏:五分钟学算法

大家好,我是吴师兄。

今天继续来学习《剑指Offer》系列的一道经典题目,依旧给出了非常详细的题解和精美的配图与动画。

封面图来源于本题的评论区,LeetCode 评论区人才挺多的。

一、题目描述

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。

不能使用除法。

示例:

代码语言:javascript
复制
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

提示:

  • 所有元素乘积之和不会溢出 32 位整数
  • a.length <= 100000

二、题目解析

按照正常的思路,既然 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1],那么想要计算出 B[i] 来那每次都去遍历数组 A,把这些数字相乘就行了。

而数组 B 的长度为 n,并且计算数组 B 中的每个元素时都需要完整的遍历一遍数组 A,而数组 A 的长度为 n,那么整体的时间复杂度就达到了 O(N2) 级别,按照这个逻辑写出的代码提交会超时。

那么哪里可以优化呢?

上面的暴力解法中充斥着大量重复的计算

比如数组 A 为 [1,2,3,4,5] 。

1、想要计算除了 2 以外的结果时,需要计算 1 * 3 * 4 * 5

2、想要计算除了 3 以外的结果时,需要计算 1 * 2 * 4 * 5

注意到,这两个计算过程都计算了 14 * 5

所以,我们优化的方向就是去保存好计算的结果,避免重复计算。

1 出现在 2 和 3 的左侧,4 * 5 出现在 2 和 3 的右侧,它们分别可以使用数组提前计算保存下来。

在公式 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1] 中,实际上可以划分为两个部分,从 0 到 i - 1 和从 i + 1 到 n - 1。

因此,想要构建乘积数组后某下标对应元素的值,只需要求出该下标对应原数组中其左边的元素的乘积和其右边的元素的乘积,最后将两个乘积再相乘即可。

具体操作如下:

数组 A 为 [1,2,3,4,5] 。

1、数组 left[i] 表示在数组 A 中下标为 i 的所有左边元素的乘积,如果左边没有元素,默认为 1。

2、数组 right[i] 表示在数组 A 中下标为 i 的所有右边元素的乘积,如果右边没有元素,默认为 1。

3、B[i] = left[i] * right[i] 。

为了帮助你更好的理解整个过程,我特意做了一组动画,点开可以查看:http://mpvideo.qpic.cn/0bc3siabqaaa7yadxupk3nqvbewddcjaagaa.f10002.mp4?dis_k=2fe2ab47cf7fbc3cc4f95bba6012003d&dis_t=1645403694&vid=wxv_2233755004778102797&format_id=10002&support_redirect=0&mmversion=false

三、参考代码

代码语言:javascript
复制
// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 剑指 Offer 66. 构建乘积数组:https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
class Solution {
    public int[] constructArr(int[] a) {

        // 边界判断
        if( a == null || a.length == 0 ) return a;

        // 获取数组 a 的长度
        int length = a.length;

        // 数组 leftA 表示数组 a 中每个元素左边所有元素的乘积
        // 比如 left[5] 表示数组 a 中下标为 5 的元素的左边所有元素的乘积
        // 即 left[5] = a[0] * a[1] * a[2] * a[3] * a[4]
        int[] leftA = new int[length];

        // 数组 rightA 表示数组 a 中每个元素右边边所有元素的乘积
        // 比如 rightA[3] 表示数组 a 中下标为 3 的元素的右边边所有元素的乘积
        // 即 rightA[3] =  a[4] * a[5]
        int[] rightA = new int[length];

        // 由于任何数与 1 相乘都是它本身,因此,如果发现元素左边没有元素时,默认值为 1
        // 这样就不会影响到后面的计算
        leftA[0] = 1;

        // 由于任何数与 1 相乘都是它本身,因此,如果发现元素右边没有元素时,默认值为 1
        // 这样就不会影响到后面的计算
        rightA[length - 1] = 1;

        // 开始不断填充 leftA
        for( int i = 1 ; i < length ; i++ ){
            leftA[i] = leftA[ i - 1 ] * a[ i - 1 ];
        }

        // 开始不断填充 rightA
        for( int j = length - 2 ; j >= 0 ; j-- ){
            rightA[j] = rightA[ j + 1 ] * a[ j + 1 ];
        }

        // 数组 B 存放结果
        int[] B = new int[length];

        // 只求出该下标对应原数组中其左边的元素的乘积和其右边的元素的乘积
        // 最后将两个乘积再相乘即可
        for( int k = 0 ; k < length ; k++){
            B[k] = leftA[k] * rightA[k];
        }

        // 返回数组 B
        return B;

    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 五分钟学算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、题目描述
  • 二、题目解析
  • 三、参考代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档