面试经典算法150题系列-除自身以外数组的乘积

news/2024/12/23 1:09:25/

除自身以外数组的乘积

给你一个整数数组 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]

实现思路:

这个问题可以通过预先计算数组的前缀和后缀乘积来解决。由于不能使用除法,我们可以创建两个数组,一个用于存储每个元素之前所有元素的乘积(前缀乘积),另一个用于存储每个元素之后所有元素的乘积(后缀乘积)。然后,对于数组中的每个元素,我们可以将其对应的前缀乘积和后缀乘积相乘,得到除了该元素之外其他所有元素的乘积(不知道前缀乘积和后缀乘积的朋友别害怕,后面有补充)。

实现代码:

java">    // 这是一个公共方法,用于返回一个新数组,其中每个元素// 是原数组中除了该元素之外其他所有元素的乘积。public  int[] productExceptSelf(int[] nums) {// 获取输入数组的长度。int length = nums.length;// 初始化一个长度与输入数组相同的答案数组,初始值设为1。int[] answer = new int[length];// 初始化前缀乘积数组,所有元素初始值设为1。int[] prefix = new int[length];// 初始化后缀乘积数组,所有元素初始值也设为1。int[] suffix = new int[length];// 计算前缀乘积数组。// 从第二个元素开始,每个元素是前一个元素与当前元素的乘积。prefix[0] = 1;for (int i = 1; i < length; i++) {prefix[i] = prefix[i - 1] * nums[i - 1];}// 计算后缀乘积数组。// 从最后一个元素开始,每个元素是后一个元素与当前元素的乘积。suffix[length - 1] = 1;for (int i = length - 2; i >= 0; i--) {suffix[i] = suffix[i + 1] * nums[i + 1];}// 计算答案数组。// 对于每个索引i,答案数组的元素是前缀数组的元素与后缀数组的元素的乘积。for (int i = 0; i < length; i++) {answer[i] = prefix[i] * suffix[i];}// 返回计算好的答案数组。return answer;}}

好的,让我们通过一个具体的例子来演绎上述Java代码的执行过程。假设我们有以下输入数组:

int[] nums = {1, 2, 3, 4};

下面是代码执行的步骤:

  1. 初始化数组

    • answerprefix 和 suffix 数组都被初始化为长度与 nums 相同的数组,所有元素初始值为1。
  2. 计算前缀乘积 (prefix 数组):

    • 从索引1开始(因为索引0已经初始化为1),每个元素是前一个元素与 nums 中相应元素的乘积。
                  prefix[0] = 1,prefix[1]=prefix[0] * nums[0]=1 * 1= 1, prefix[2]=prefix[1] * nums[1]=1 * 2= 2,prefix[3]=prefix[2]  * nums[2]=2 * 3=6
    • 执行完循环后,prefix 数组为:[1, 1, 2, 6] 
  3. 计算后缀乘积 (suffix 数组):

    • 从索引 length - 2 开始逆序遍历(因为索引 length - 1 已经初始化为1),每个元素是后一个元素与 nums 中相应元素的乘积。
      suffix[3]=1,
      suffix[2]=suffix[3]*nums[3] = 1 *4 =4,
      suffix[1]=suffix[2]*nums[2] = 4 *3 =12,
      suffix[0]=suffix[1]*nums[1] = 12 *2 =24
    • 执行完循环后,suffix 数组为:[24, 12, 4, 1]
  4. 计算答案数组 (answer 数组):

    • 对于 nums 中的每个元素,answer 数组的元素是 prefix 数组与 suffix 数组对应元素的乘积。
    • 以 nums[1] 为例,prefix[1] 是 1(因为 nums[0] 是1),suffix[1] 是 12,所以 answer[1] 是 1 * 12 = 12
    • 执行完循环后,answer 数组为:[24, 12, 8, 6]
  5. 返回结果

    answer 数组包含了输入数组 nums 中每个元素对应的乘积,不包含它们自己。

通过这个演绎过程,你们熟悉了吗?这个方法在O(n)时间复杂度内解决了问题,并且只使用了O(n)的额外空间

知识补充:前缀数组(前缀乘积)与后缀数组(后缀乘积)

前缀数组和后缀数组的原理基于累积乘积的概念,它们用于快速计算数组中元素的连续子集的乘积。下面是这两个概念的详细解释:

前缀数组(Prefix Array)

前缀数组是一个与原数组等长的数组,其中第 i 个元素包含从数组开始到第 i-1 个元素的乘积(如果存在的话)。换句话说,前缀数组的第 i 个元素是原数组从索引 0i-1 的所有元素的乘积。

例如: 假设有一个数组 nums = [a1, a2, a3, ..., an],前缀数组 prefix 将是这样的:

  • prefix[0] = 1(因为没有元素乘以 a1
  • prefix[1] = a1
  • prefix[2] = a1 * a2
  • ...
  • prefix[i] = a1 * a2 * ... * ai-1

后缀数组(Suffix Array)

与前缀数组相对应,后缀数组包含从数组末尾开始到第 i+1 个元素的乘积。后缀数组的第 i 个元素是原数组从索引 i+1 到末尾的所有元素的乘积。

例如: 对于相同的数组 nums = [a1, a2, a3, ..., an],后缀数组 suffix 将是这样的:

  • suffix[n-1] = 1(因为没有元素乘以 an
  • suffix[n-2] = an
  • suffix[n-3] = an * an-1
  • ...
  • suffix[i] = an * an-1 * ... * ai+1

原理

前缀和后缀数组的原理基于这样一个事实:对于数组中的任意元素 nums[i],要计算除了 nums[i] 之外所有元素的乘积,可以将其前缀乘积(到 i-1 的乘积)和后缀乘积(从 i+1 到末尾的乘积)相乘。这样,nums[i] 项就不会出现在乘积中,因为它既不会被前缀也不会被后缀数组包含。

计算示例: 假设 nums = [3, 2, 4],我们有:

  • prefix = [1, 3, 6]
  • suffix = [8, 4, 1]

对于 nums[1](值为2),除了它之外的乘积是 prefix[1] * suffix[1] = 3 * 4 = 12

这种方法的优势在于,它允许我们在O(1)时间内计算出任意元素的非自身元素乘积,而不需要对数组进行多次遍历或使用除法。这在处理大数据集或需要避免除法操作的场景中特别有用。


http://www.ppmy.cn/news/1506360.html

相关文章

【实现100个unity特效之12】Unity中的冲击波 ——如何使用ShaderGraph制作一个冲击波着色器

最终效果 文章目录 最终效果新增LitShaderGraph圆环扭曲效果优化冲击波效果屏幕全屏冲击波圆形冲击波最终连线图代码控制补充源码完结 新增LitShaderGraph 圆环扭曲效果 让我们从一个UV节点开始 创建一个Vector2变量RingSpawnPosition表示冲击波生成位置,在X和Y上将其默认值…

element时间段选择器或时间选择器 只设置默认起始时间或者结束时间,不显示问题

element时间段选择器或时间选择器 只设置默认起始时间或者结束时间&#xff0c;不显示问题 <div v-for"(item,index) in [a,b]":key"item"><el-date-pickerv-if"b"v-model"value1[item]"type"datetimerange"value-…

eclipse启动springboot项目指定启动配置文件-spring.profiles.active

sh命令启动项目&#xff1a; nohup java -Dloader.path"lib/" -jar springboot.jar --spring.profiles.activedev >>out.log 2>&1 & eclipse启动springboot项目指定启动配置文件 --spring.profiles.activezj

OpenCV 读取 MP4 视频

在 C 中结合 OpenCV 库来读取 MP4 视频文件是一个常见的任务。以下是一个简单的示例程序&#xff0c;说明了如何使用 OpenCV 的 VideoCapture 类来打开一个 MP4 文件并逐帧显示每一帧。 VideoCapture::VideoCapture(const string& filename)&#xff1b;参数&#xff1a;f…

从人工智能神经网络出发

今天与豆包聊天&#xff0c;聊了当前深度学习中的神经网络。 1、跟他多次沟通后&#xff0c;对GPT中的神经网络总结了如下几个关键要点&#xff1a; &#xff08;1&#xff09;GPT是一种基于深度学习的语言模型&#xff0c;它使用线性代数和神经网络来模拟人工神经元。 &…

Java面试题--JVM大厂篇之破解 JVM 性能瓶颈:实战优化策略大全

目录 引言: 正文: 1. 常见的JVM性能问题 频繁的GC导致应用暂停 内存泄漏导致的内存不足 线程争用导致的CPU利用率过高 类加载问题导致的启动时间过长 2. 优化策略大全 2.1 代码层面的优化 2.1.1 避免不必要的对象创建 2.1.2 优化数据结构的选择 2.1.3 使用并发工具…

GPT-5:未来已来,你准备好了吗?

GPT-5 一年半后发布&#xff1f;对此你有何期待&#xff1f; IT之家6月22日消息&#xff0c;在美国达特茅斯工程学院周四公布的采访中&#xff0c;OpenAI首席技术官米拉穆拉蒂被问及GPT-5是否会在明年发布&#xff0c;给出了肯定答案并表示将在一年半后发布。此外&#xff0c;穆…

Linux 文件系统、动静态库

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a; Linux 目录 一、文件系统 1、了解磁盘的存储结构 1.基本知识 2.磁盘中盘片为什么高速旋转&#xff1f; 3.磁头为什么要左右摇摆&#xff1f; 4.如何找到一个指定位置的扇区&#xff1f; 5.文件在磁盘…