每日一题——除自身以外数组的乘积

简介: 每日一题——除自身以外数组的乘积

除自身以外数组的乘积

题目链接


这一题乍一看好像十分简单,先用一趟循环遍历所有数据,得到数据所有元素的乘积,再用一趟循环将这个乘积除以每个元素,这样不就得到了除自身以外数组的乘积吗?我们先来看看代码:

int* productExceptSelf(int* nums, int numsSize, int* returnSize){
    int *ret = (int *)malloc(sizeof(int) * numsSize);
    *returnSize = numsSize;
    int sub = 1;
    for(int i = 0; i < numsSize; i++)
        sub *= nums[i];
    for(int i = 0; i < numsSize; i++)
        ret[i] = sub / nums[i];
    return ret;
}

得到了这样的结果:

提示我们不能进行除0操作

我们来看看它给的错误用例:

这下我们就清楚了,当数组元素存在0时,由于不能进行除0操作,那我们这个最容易想到的方法就失效了,我们必须另寻出路。


方法一:构造左右乘积列表

假设我们要求下标为i的元素外数组的乘积,那我们可以分别求出它左边所有元素的乘积和右边所有元素的乘积,再相乘即可。而为了方便得到这两个乘积,我们可以构造两个数组,分别存储下标为i(0 <= i < numsSize)左边所有元素的乘积和右边所有元素的乘积

具体构造步骤如下:

  1. 定义两个数组leftright其大小和给定数组的大小一致
  2. left数组用来存放下标为i(0 <= i < numsSize)左边所有元素的乘积。当i为0(数组最左边的元素)时,其左边没有数据,因此left[0]为1。对于i > 0的情况,left[i] = left[i - 1] * nums[i - 1](从左向右计算)
  3. 同理对于right数组,i = numsSize - 1时(数组最右边的元素),其右边没有元素,因此right[numsSize - 1]为1。对于i < numsSize - 1的情况,right[i] = right[i + 1] * nums[i + 1](从右向左计算)

构造动图:

得到了leftright两个数组,我们就可以通过索引i直接得到最后结果了

这个方法的时间复杂度和空间复杂度都为O(N)

int* productExceptSelf(int* nums, int numsSize, int* returnSize){
     //为返回数组申请内存
     int *ret = (int *)malloc(sizeof(int) * numsSize);
     *returnSize = numsSize;
     //申请左右乘积列表的内存
     int *left = (int *)malloc(sizeof(int) * numsSize);
     int *right = (int *)malloc(sizeof(int) * numsSize);
     //得到左右乘积列表
     left[0] = 1;
     right[numsSize - 1] = 1;
     for(int i = 1; i < numsSize; i++)
         left[i] = left[i - 1] * nums[i - 1];
     for(int i = numsSize - 2; i >= 0; i--)
         right[i] = right[i + 1] * nums[i + 1];
     //最后的结果就是数据左边所有元素的乘积乘以右边所有元素的乘积
     for(int i = 0; i < numsSize; i++)
         ret[i] = left[i] * right[i];
     //返回得到的结果
     return ret;
 }

方法一的优化

尽管方法一已经可以较好地解决问题,但是由于方法一除了返回数组外,还额外申请了两个数组,因此空间复杂度为O(N)(返回的数组不计入空间复杂度),我们可以对方法一进行优化,从而使空间复杂度为O(1)

由于返回的数组和左右乘积列表left、right是相同的大小,因此我们可以直接在返回数组ret的基础上直接先求出left或者right,然后再通过一次循环来更新右边所有元素的乘积,就可以得到最后结果了。

以下我们以先在ret的基础上求出left为例:

int* productExceptSelf(int* nums, int numsSize, int* returnSize){
      //为返回数组申请内存
      int *ret = (int *)malloc(sizeof(int) * numsSize);
      *returnSize = numsSize;
      //在ret的基础上求出left(左边所有数的乘积)
      ret[0] = 1;
      for(int i = 1; i < numsSize; i++)
          ret[i] = ret[i - 1] * nums[i - 1];
      //从右向左遍历数组,不断更新sub(右边所有元素的乘积),最后得到结果
      int sub = 1;
      for(int i = numsSize - 1; i >= 0; i--)
      {
          ret[i] = ret[i] * sub;
          sub *= nums[i];
      }
      return ret;
  }

这样,我们就不需要申请额外的空间,从而使空间复杂度为O(1)

(推荐)方法二:左右指针

我们可以维护两个指针leftSubrightSub,这两个指针分别用来计算左边所有数的乘积和右边所有数的乘积

具体步骤如下:

  1. 和上面的方法类似,先用leftSub,利用一趟循环从左向右遍历,得到每个位置左边所有数据的乘积(也可以先用right计算右边所有数据的乘积)
  2. 然后再用rightSub,用一趟循环从右向左遍历,利用ret[i] *= rightSub就可以得到最后的结果
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
    //为返回数组申请内存    
    int *ret = (int *)malloc(sizeof(int) * numsSize);
    *returnSize = numsSize;
    //求出左边所有数据的乘积
    int leftSub = 1;
    for(int i = 0; i < numsSize; i++)
    {
        ret[i] = leftSub;
        leftSub *= nums[i];
    }
    //在已经得到左边所有数据的乘积的基础上,在乘以右边所有数据的乘积,得到最后结果
    int rightSub = 1;
    for(int i = numsSize - 1; i >= 0; i--)
    {
        ret[i] *= rightSub;
        rightSub *= nums[i];
    }
    return ret;
}

方法二优化

方法二我们用了两次循环来解决问题,实际上,我们可以将这两个循环合并到一起(整体逻辑一样)

int* productExceptSelf(int* nums, int numsSize, int* returnSize){
    //为返回数组申请内存        
     int *ret = (int *)malloc(sizeof(int) * numsSize);
     *returnSize = numsSize;
     //将返回数组初始化为1
     for(int i = 0; i < numsSize; i++)
         ret[i] = 1;
     /*
      leftSub从左边开始,不断更新左边所有数据的乘积
      rightSub从右边开始,不断更新右边所有数据的乘积
     */
     int leftSub = 1;
     int rightSub = 1;
     for(int i = 0, j = numsSize - 1; i < numsSize; i++,j--)
     {
         ret[i] *= leftSub;
         ret[j] *= rightSub;
         leftSub *= nums[i];
         rightSub *= nums[j];
     }
     return ret;
 }
相关文章
|
算法 计算机视觉
使用积分图的自适应二值化算法
使用积分图的自适应二值化算法
|
9月前
|
算法 决策智能
基于遗传优化的货柜货物摆放优化问题求解matlab仿真
本项目采用MATLAB2022A实现基于遗传算法的货柜货物摆放优化,初始随机放置货物后通过适应度选择、交叉、变异及逆转操作迭代求解,最终输出优化后的货物分布图与目标函数变化曲线,展示进化过程中的最优解和平均解的变化趋势。该方法模仿生物进化,适用于复杂空间利用问题,有效提高货柜装载效率。
181 24
|
7月前
|
传感器 定位技术
会议通知 | 第13届国际移动测量技术大会(MMT2025)二号通知
2025年6月20-22日,第13届国际移动测量技术大会(MMT2025)将在福建厦门举行,由厦门大学空间感知与计算实验室与ISPRS等联合承办。作为全球移动测量技术领域最大国际会议之一,MMT为相关研究、系统及应用提供交流平台。大会主席为王程教授,优秀论文将推荐至《PE&RS》和《The Photogrammetric Record》期刊发表。摘要投稿截止日期为2025年4月1日,详情见官网:https://mmt2025.xmu.edu.cn/2025/。
401 4
|
SQL 关系型数据库 MySQL
【Go语言专栏】使用Go语言连接MySQL数据库
【4月更文挑战第30天】本文介绍了如何使用Go语言连接和操作MySQL数据库,包括选择`go-sql-driver/mysql`驱动、安装导入、建立连接、执行SQL查询、插入/更新/删除操作、事务处理以及性能优化和最佳实践。通过示例代码,展示了连接数据库、使用连接池、事务管理和性能调优的方法,帮助开发者构建高效、稳定的Web应用。
1978 0
|
算法 API 计算机视觉
OpenCV(图像处理)-基于Python-形态学处理-开运算、闭运算、顶帽、黑帽运算
1. 形态学 OpenCV形态学是一种基于OpenCV库的数字图像处理技术,主要用于处理图像的形状、结构和空间关系。它包括一系列图像处理工具和算法,包括膨胀、腐蚀、开运算、闭运算、形态学梯度、顶帽、黑帽等。
342 0
|
11月前
|
人工智能 测试技术 Windows
Windows 竞技场:面向下一代AI Agent的测试集
【10月更文挑战第25天】随着人工智能的发展,大型语言模型(LLMs)在多模态任务中展现出巨大潜力。为解决传统基准测试的局限性,研究人员提出了Windows Agent Arena,一个在真实Windows操作系统中评估AI代理性能的通用环境。该环境包含150多个多样化任务,支持快速并行化评估。研究团队还推出了多模态代理Navi,在Windows领域测试中成功率达到19.5%。尽管存在局限性,Windows Agent Arena仍为AI代理的评估和研究提供了新机遇。
233 3
|
关系型数据库 C语言 PostgreSQL
PostgreSQL服务端开发学习 -- fmgr.h
fmgr按官方的解释就是Postgres函数管理器和函数调用接口,在使用C语言开发PostgreSQL后端应用时,所以与backend交互时必须遵循fmgr.h中定义的一些规范。
|
JavaScript 前端开发 API
深入解析JavaScript Generator 生成器的概念及应用场景
本文讲解了JS生成器的概念和应用场景。生成器是一个可以暂停和恢复执行的函数。利用生成器我们可以很方便地实现自定义的可迭代对象、状态机、惰性计算等,并且还能用它来简化我们的异步操作代码。
880 0
|
机器学习/深度学习 人工智能 自然语言处理
【机器学习】机器学习:人工智能中实现自动化决策与精细优化的核心驱动力
【机器学习】机器学习:人工智能中实现自动化决策与精细优化的核心驱动力
|
异构计算
FPGA入门(7):IP核调用(一)
FPGA入门(7):IP核调用(一)
287 0