1 题目
给你一个整数数组 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 题目
给定 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.length1 <= n <= 2 * 1040 <= 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 个东西:
- i 左边最高的柱子
leftMax - i 右边最高的柱子
rightMax - 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的模块已经差不多了。
然后按照模块提取出一个共性的思路,二刷的时候反思为主。