Leetcode 162 除了自身以外数组的乘积 | 接雨水

1 题目

238. 除了自身以外数组的乘积

给你一个整数数组 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
  • 输入 保证 数组 answer[i]32 位 整数范围内

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

2 代码实现

c++

cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size() ;
        vector<int> prefix(n ,1 );
        vector<int> suffix(n ,1 );
        vector<int> answer(n ,1 );

        for (int i = 1 ; i < n ; ++i){
            prefix[i] = prefix[i - 1 ] * nums[i -1 ];
        }
        for (int i = n -2 ; i >= 0 ; i --){
            suffix[i] = suffix[i + 1 ] * nums[i + 1];
        }
        for (int i = 0 ; i < n ; ++i){
            answer[i] = prefix[i] * suffix[i] ;
        }
        return answer ;
    }
};

js

javascript 复制代码
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
    const n = nums.length ;
    const prefix = new Array(n).fill(1);
    const suffix = new Array(n).fill(1);
    const answer = new Array(n).fill(1);

    for ( let i = 1 ; i < n ; i++){
        prefix[i] = prefix [i -1 ] * nums[i -1 ];
    }

    for (let i = n - 2 ; i >= 0 ; i --){
        suffix[i] = suffix[i + 1] * nums[i + 1];
    }

    for (let  i = 0 ; i < n  ; i++){
        answer[i] = prefix[i] * suffix[i];
    }

    return answer ;
};

思考

不让用除法?蛤,用什么,那我不知道了。

看了题解是

  • 前缀乘积prefix[i] = nums [0] × nums [1] × ... × nums [i-1](i 左边所有数的乘积)
  • 后缀乘积suffix[i] = nums [i+1] × nums [i+2] × ... × nums [n-1](i 右边所有数的乘积)
  • 最终结果:answer[i] = prefix[i] × suffix[i]

很直接的想法啊。

题解

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<int> productExceptSelf(vector<int>& nums) {
    int n = nums.size();
    // 定义三个数组:前缀、后缀、答案
    vector<int> prefix(n, 1);
    vector<int> suffix(n, 1);
    vector<int> answer(n, 1);

    // 1. 计算前缀乘积:从左往右遍历
    // prefix[i] = 左边所有数的乘积
    for (int i = 1; i < n; ++i) {
        prefix[i] = prefix[i-1] * nums[i-1];
    }

    // 2. 计算后缀乘积:从右往左遍历
    // suffix[i] = 右边所有数的乘积
    for (int i = n-2; i >= 0; --i) {
        suffix[i] = suffix[i+1] * nums[i+1];
    }

    // 3. 结果 = 前缀 × 后缀
    for (int i = 0; i < n; ++i) {
        answer[i] = prefix[i] * suffix[i];
    }

    return answer;
}

// 测试代码
int main() {
    vector<int> nums = {1,2,3,4};
    vector<int> res = productExceptSelf(nums);
    for (int x : res) cout << x << " "; // 输出 24 12 8 6
    return 0;
}

这里有些注意点:

  • 前缀的 i 从1 才开始 ,因为从下标 1 才是有 " 前 " 的; 同理 i = n - 2 开始一样理解, n - 2 以后才是有" 后 "的。
  • 实际上这里比较好的做法,有一个递归的思想,不需要每次都往前 / 后重新计算一下,用数组存好之前的前缀 / 后缀 。

3 题目

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

复制代码
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

复制代码
输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

4 代码实现

c++

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if ( n == 0 ) return 0 ;

        vector<int> leftMax(n);
        leftMax[0] = height[0];
        for(int i = 1 ; i < n ; i++){
            leftMax[i] = max(leftMax[i -1 ], height[i]);
        }

        vector<int> rightMax(n);
        rightMax[n -1] = height[ n -1];
        for (int i = n - 2 ; i >= 0 ; i--){
            rightMax[i] = max(rightMax[i + 1] , height[i]);
        }

        int ans = 0 ;
        for (int i  = 0 ; i < n ; i++){
            ans += min(leftMax[i] , rightMax[i]) - height[i] ;
        }

        return ans ;
    }
};

js

javascript 复制代码
/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    const n = height.length ;
    if (n === 0 ) return 0 ;
    let answer = 0 ;

    const leftMax = new Array(n).fill(0);
    leftMax[0] = height[0];
    for (let i = 1 ;  i < n ; i ++){
        leftMax[i] = Math.max(leftMax[i - 1] , height[i]);
    }

    const rightMax = new Array(n).fill(0);
    rightMax[n -1] =height[ n-1] ;
    for (let i = n -2 ; i >=0 ; i --){
        rightMax[i] = Math.max(rightMax[ i + 1 ] , height[i]);
    }

    for (let i = 0 ; i < n ; i++){
        answer += Math.min(leftMax[i] , rightMax[i]) - height[i];
    }

    return answer ;
};

思考

其实这个题目看到我都!吓!

困难 hard , 常常说出这个题目就是面试不想要你的意思了。

题目的表述是很简单,可是代码要怎么实现呢,不知怎么写。


看看题解的思路

每个位置 i 能存多少水?

只看 3 个东西:

  1. i 左边最高的柱子 leftMax
  2. i 右边最高的柱子 rightMax
  3. i 自己的柱子高度 height[i]

公式:

当前存水量 = min (左边最高,右边最高) - 当前柱子高度

如果结果 > 0,就有水;否则没水

题解

  • 前缀:leftMax[i] = i 左边最高柱子
  • 后缀:rightMax[i] = i 右边最高柱子
  • 最后遍历一遍算总雨水量
cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n == 0) return 0;

        // 1. 左边最大值数组
        vector<int> leftMax(n);
        leftMax[0] = height[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = max(leftMax[i-1], height[i]);
        }

        // 2. 右边最大值数组
        vector<int> rightMax(n);
        rightMax[n-1] = height[n-1];
        for (int i = n-2; i >= 0; i--) {
            rightMax[i] = max(rightMax[i+1], height[i]);
        }

        // 3. 计算总雨水
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans += min(leftMax[i], rightMax[i]) - height[i];
        }

        return ans;
    }
};

神奇耶,有了前面的铺垫,这一题也不是那么难了。

但是我原先有个误解蛤,我以为这里可能是负的,但其实不是,因为left 、 right的 max 已经包括自己的,也就是如果左右都比自己小,自己是max 那么这里ans 加上的是 自己 - 自己 = 0 ;

并且还要注意为什么我们是这样用数组维护的left 、right 的max ,因为不能单单只看自己最近的左和右。

复制代码
height = [4,2,0,3,2,5]

按照这个例子走一遍,会发现其实兜住雨水最高的板子是4。

cpp 复制代码
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans += min(leftMax[i], rightMax[i]) - height[i];
        }

自己写漏了

javascript 复制代码
/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    const n = height.length ;
    if (n === 0 ) return 0 ;
    let answer = 0 ;

    const leftMax = new Array(n).fill(0);
    for (let i = 1 ;  i < n ; i ++){
        leftMax[i] = Math.max(leftMax[i - 1] , height[i]);
    }

    const rightMax = new Array(n).fill(0);
    for (let i = n -2 ; i >=0 ; i --){
        rightMax[i] = Math.max(rightMax[ i + 1 ] , height[i]);
    }

    for (let i = 0 ; i < n ; i++){
        answer += Math.min(leftMax[i] , rightMax[i]) - height[i];
    }

    return answer ;
};

发现问题了吧,一头一尾没有加入数组初始化。

5 小结

哇哇哇!

感觉这样收获挺大的!不会写,看题解思路,看明白思路以后一行一行边敲边想。

第二遍用js写的时候默一遍,会发现自己的漏洞。

这个学习办法是对的!!!

按照这个SOP,接下来我打算刷 hot 100 了。150的模块已经差不多了。

然后按照模块提取出一个共性的思路,二刷的时候反思为主。

相关推荐
自我意识的多元宇宙1 小时前
数据结构----插入排序
数据结构·算法·排序算法
Westward-sun.1 小时前
YOLO目标检测算法与mAP评估指标详解(附示例)
算法·yolo·目标检测
是个西兰花1 小时前
C++:异常
开发语言·c++·异常
cpp_25011 小时前
P1873 [COCI 2011/2012 #5] EKO / 砍树
数据结构·c++·算法·题解·二分答案·洛谷·csp
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第18题:HashMap底层是如何扩容的
java·开发语言·面试·散列表·hash-index·hash
啊哦呃咦唔鱼1 小时前
leetcodehot100-347. 前 K 个高频元素
数据结构·算法·leetcode
玛丽莲茼蒿1 小时前
Leetcode hot100 多数元素【简单】
算法·leetcode·职场和发展
AbandonForce1 小时前
Map类:pair键值对|map的基本操作|operator[]
开发语言·c++·算法·leetcode
澈2071 小时前
C++核心:封装与static静态成员实战指南
开发语言·c++·算法