238. Product of Array Except Self

練習日期:2026-01-15

難度:Medium

類型:Array、Prefix Sum

📘 題目敘述

給定一個整數陣列 nums,請回傳一個陣列 answer,其中 answer[i] 代表:

  • nums 中除了 nums[i] 以外所有元素的乘積

題目要求:

  • 不能使用除法(題目原本的進階要求)
  • 並且演算法需在 O(n) 時間內完成

(我的寫法有用除法,但我先把題目需求完整寫在這裡)

條件限制

  • 2 ≤ nums.length ≤ 10^5
  • -30 ≤ nums[i] ≤ 30
  • 保證任意前綴或後綴的乘積都在 32-bit 整數範圍內

🧠 解題思路(一)

我這份程式的核心想法是:

  • 先把「所有非 0 的數字」乘起來,得到一個總乘積 pd
  • 然後用 pd 去算每一個位置「除了自己以外的乘積」

但是這題最麻煩的地方其實是 0,因為:

  • 只要陣列中有 0,乘積會整個變成 0
  • 如果直接做除法 pd / nums[i],遇到 nums[i] = 0 也會出錯

所以我的邏輯主要是在分情況處理「0 的數量」。

所有變數

  • n:陣列 nums 的長度
  • pd:把所有「非 0 元素」乘起來的乘積
  • zero:是否已經遇到過 0(用來判斷 0 的數量)
  • zpos:若只有一個 0,記錄那個 0 出現的位置
  • answer:答案陣列(我先初始化為全 0,方便處理多個 0 的情況)

🪜 主要流程步驟

第一階段:掃描整個陣列,統計 0 的情況並計算乘積

我用一次迴圈掃過 nums

情況一:遇到 nums[i] == 0

  • 如果我之前已經遇過 0(zero == true),代表陣列裡至少有兩個 0

這種情況下,不管你排除哪一個元素,剩下的乘積裡一定還有 0,所以答案一定是:

  • 每一格都是 0

因此我直接回傳已經是全 0 的 answer


情況二:第一次遇到 0

  • zero 設成 true
  • zpos 記錄成目前位置

同時我不把 0 乘進 pd(避免整個乘積變成 0)。


情況三:遇到非 0 的數字

  • 我把它乘進 pd
  • 因為 pd 代表的是「所有非 0 的總乘積」

第二階段:根據 0 的數量決定怎麼填答案

情況一:陣列中剛好只有一個 0(zero == true

這時候只有 zpos 那個位置的答案會是非 0:

  • answer[zpos] = 「所有非 0 的乘積」= pd

原因是:

  • 對於 zpos 位置:排除掉 0 之後,剩下全都是非 0,所以乘積是 pd
  • 對於其他位置:你排除的是非 0,但剩下仍包含那個 0,所以乘積一定是 0

而我一開始把 answer 初始化成全 0,所以只要補 zpos 這格就好。


情況二:陣列中完全沒有 0(zero == false

這時候我就可以用最直覺的方式:

  • answer[j] = pd / nums[j]

因為:

  • pd 是所有元素的乘積
  • 除掉 nums[j] 就得到「除了自己以外的乘積」

💡 整體邏輯總結

  1. 兩個以上的 0 → 全部答案都是 0
  2. 剛好一個 0 → 只有 0 的位置是 pd,其他都 0
  3. 沒有 0 → 用總乘積 pd 直接除掉自己

這樣可以在 O(n) 時間內完成,而且邏輯非常直觀。

💻 程式碼實作

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        int pd = 1, zpos;
        bool zero = false;
        vector<int> answer(n, 0);
        for (int i = 0; i < n; i++) {
            if (nums[i] == 0) {
                if (zero) {
                    return answer;
                } else {
                    zero = true;
                    zpos = i;
                }
            } else {
                pd = pd * nums[i];
            }
        }
        if (zero) {
            answer[zpos] = pd;
            return answer;
        }
        for (int j = 0; j < n; j++) {
            answer[j] = pd / nums[j];
        }
        return answer;
    }
};

🧠 解題思路(二)(但會TLE)

我後來想到的做法是:

  • 先把 answer 初始化成全部都是 1
  • 然後對每個位置 j,把「所有不是 j 的元素」都乘進去

我的寫法是用兩層迴圈去完成:

  • 外層固定某個元素 nums[i]
  • 內層跑過所有位置 j,只要 i != j,就把 nums[i] 乘到 answer[j]

這樣做的效果就是:

  • 對於每個 j,我會把除了 nums[j] 以外的所有數字都乘進 answer[j]

所有變數

  • n:陣列 nums 的長度
  • answer:答案陣列,一開始全部設成 1
  • i:外層迴圈正在看的元素索引
  • j:內層迴圈正在更新的答案位置

🪜 主要流程步驟

第一步:初始化答案陣列

  • 建立 answer,長度為 n
  • 每一格都先放 1,因為乘法的單位元素是 1

第二步:雙迴圈把元素乘進去

外層:走過每個 nums[i]

我把 nums[i] 當成「等一下要乘進去的某一個因數」。

內層:走過每個答案位置 answer[j]

  • 如果 j != i,代表 answer[j] 的乘積應該包含 nums[i]
  • 所以執行 answer[j] *= nums[i]

為什麼這樣會 TLE

因為我用了兩層迴圈:

  • 外層跑 n
  • 內層也跑 n

所以總操作次數大約是:n * n = O(n^2)

而題目給的 n 最大可以到 10^510^5^2 = 10^10 次操作,這個數量級一定會超時。

因此這個方法雖然直觀、也很好理解,但不符合題目要求的 O(n)

💻 程式碼實作

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> answer(n, 1);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    answer[j] *= nums[i];
                }
            }
        }
        return answer;
    }
};

https://leetcode.com/problems/product-of-array-except-self/

作者: scottnick
撰寫日期: 2026-01-15
類別:
原文連結: https://scottnick.github.io/cpp-notes/leetcode75/238-product-of-array-except-self.html