46. Permutations

練習日期:2026-04-24

難度:Medium

類型:Array、Backtracking

📘 題目敘述

給定一個由互不相同整數組成的陣列 nums,請回傳它的所有排列。

答案可以用任意順序回傳。

條件限制

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中所有整數都互不相同

🧠 解題思路(一)直接遞迴

我這題是直接寫遞迴函數。

我的想法是先假設:

  • n - 1 個數字的所有排列我都已經知道了

那我現在只要把第 n 個數字,插進這些排列的每一個可能位置,就能得到前 n 個數字的所有排列。

所以這題的遞迴核心就是:

  • 先遞迴求出 n - 1 個數字的所有排列
  • 再把第 n 個數字插入每個排列的每個位置

這樣一路往上組回來,就能得到全部答案。

所有變數

  • ans:目前這一層遞迴要回傳的所有排列
  • v:遞迴求出來的 n - 1 個數字的所有排列
  • now:目前這一層要插入進去的新數字
  • x:某一個較小規模的排列
  • y:把 now 插入某個位置之後形成的新排列

🪜 主要流程步驟(一)

1. 先定義遞迴函式的意義

我把遞迴函式 f(n, nums) 定義成:

  • 回傳 numsn 個元素所形成的所有排列

也就是說:

  • f(1, nums) 會回傳只有一個數字時的排列
  • f(2, nums) 會回傳前兩個數字的所有排列
  • ...
  • f(n, nums) 就是我要的完整答案

這樣整個遞迴結構會很清楚。


2. 先處理遞迴的 base case

如果 n == 1,那就代表目前只剩一個數字。

只有一個數字時,排列就只有一種,所以我直接回傳:

return {{nums[0]}};

這就是整個遞迴往上展開時的最基本起點。


3. 先遞迴求出前 n - 1 個數字的所有排列

如果 n 不等於 1,我就先遞迴去算:

v = f(n - 1, nums);

這代表我先拿到前 n - 1 個數字的所有排列結果。

接著我再把第 n 個數字加進去。

我這裡用:

int now = nums[n - 1];

把這一層要新加入的數字記下來。


4. 把 now 插進每個舊排列的每一個位置

這題最核心的地方就是這一步。

對於 v 裡面的每一個排列 x,我都把 now 插進去所有可能的位置:

  • 插在最前面
  • 插在中間
  • 插在最後面

這段意思是:

  • x 是某個已經排好的 n - 1 長度排列
  • 我複製一份成 y
  • 再把 now 插入第 i 個位置
  • 這樣就會得到一個新的長度 n 排列
  • 最後把它放進 ans

因為一個長度 n - 1 的排列,總共有 n 個位置可以插入新數字, 所以每個舊排列都能產生 n 個新排列。


5. 把這一層組好的所有排列回傳

當我把 now 插進所有舊排列的所有位置之後,ans 裡面就會放著前 n 個數字的全部排列。

這樣就能把結果回傳給上一層遞迴。


6. 主函式只要呼叫完整規模的遞迴

在主函式 permute 裡,我先取出陣列長度 n,然後直接回傳:

return f(n, nums);

因為 f(n, nums) 的定義本來就是「前 n 個數字的所有排列」, 所以這裡就剛好是整題答案。

⏱️ 複雜度

n = nums.length

  • 時間複雜度:O(n \cdot n!)
    共有 n! 個排列,而每次建立新排列時還會有插入與複製成本,所以整體可以記成 O(n \cdot n!)
  • 空間複雜度:O(n \cdot n!)
    需要儲存所有排列結果。

💻 程式碼實作(一)

class Solution {
public:
    vector<vector<int>> f(int n, vector<int>& nums) {
        if (n == 1) {
            return {{nums[0]}};
        }
        vector<vector<int>> ans;
        vector<vector<int>> v = f(n - 1, nums);
        int now = nums[n - 1];
        for (auto& x : v) {
            for (int i = 0; i < n; i++) {
                auto y = x;
                y.insert(y.begin() + i, now);
                ans.push_back(y);
            }
        }
        return ans;
    }

    vector<vector<int>> permute(vector<int>& nums) {
        int n = nums.size();
        return f(n, nums);
    }
};

🧠 解題思路(二)回溯寫法

我這份寫法是用標準 backtracking。

我的想法是從空的排列開始,一個位置一個位置往下填。 每次都去找目前還沒用過的數字,把它加進 now,然後遞迴處理下一層。

now 的長度已經等於 n 時,就代表已經湊出一個完整排列了,直接放進答案。

這種寫法的重點就是:

  • now 記目前正在組的排列
  • check[i]nums[i] 有沒有用過
  • 每次遞迴結束後要把狀態還原,讓下一個選擇可以繼續試

所有變數

  • ans:最後要回傳的所有排列
  • now:目前正在組的排列
  • check:記錄每個位置的數字是否已經被使用過
  • n:陣列長度

🪜 主要流程步驟(二)

1. 先把遞迴要用的狀態準備好

我把幾個會在整個 backtracking 過程中一直用到的東西直接寫成成員變數:

  • ans:存所有答案
  • now:存目前路徑
  • check:存使用狀態
  • n:存陣列長度

這樣遞迴函式 f 裡面就不用每次都一直傳很多參數,寫起來會比較直接。


2. 遞迴函式的目標是把目前排列一路填滿

我寫的 f(nums),它的意思是:

  • 以目前 now 的狀態為基礎
  • 繼續往下填還沒選過的數字
  • 直到形成一個完整排列

所以每一層遞迴,其實都在做同一件事:

  • 枚舉還沒用過的數字
  • 選一個放進 now
  • 然後繼續遞迴

3. 如果 now.size() == n,代表已經得到一組完整排列

這是遞迴的終止條件:

if(now.size()==n){
    ans.push_back(now);
    return;
}

now 的長度已經和 n 一樣時,就表示:

  • 每個位置都填好了
  • 而且每個數字都只用了一次

所以這時候 now 就是一組合法排列,直接放進 ans 裡即可。


4. 每一層都去枚舉所有還沒被使用的數字

如果目前排列還沒填滿,我就用一個迴圈把所有數字都試一遍:

  • 如果 check[i] 已經是 true,代表 nums[i] 之前已經被放進 now
  • 那這一層就不能再用它,所以直接跳過
  • 只有還沒用過的數字,才可以拿來當目前這一格的候選答案

5. 選一個數字放進去,遞迴完再還原狀態

當我決定要選 nums[i] 時,就先做三件事:

check[i]=true;
now.push_back(nums[i]);
f(nums);

也就是:

  • 先標記這個數字已經使用過
  • 把它加到目前排列 now 的尾端
  • 然後遞迴去處理下一層

等遞迴回來之後,還要把剛剛的選擇取消掉:

now.pop_back();
check[i]=false;

這一步很重要,因為 backtracking 的核心就是:

  • 試一個選擇
  • 做到底
  • 回來之後把現場恢復
  • 再試下一個選擇

如果不把狀態還原,後面的分支就會被前面做過的選擇污染。


6. 主函式先初始化,再呼叫遞迴

permute 裡,我先把 n 記下來,然後把 check 開成長度 n、初始值全是 false

n=nums.size();
check.resize(n,false);

這表示一開始所有數字都還沒被用過。

接著直接呼叫:

f(nums);

讓遞迴從空排列開始慢慢展開。

最後回傳 ans,就是所有排列。

⏱️ 複雜度

n = nums.length

  • 時間複雜度:O(n \cdot n!)
    總共有 n! 種排列,而每一組排列建立過程都和長度 n 有關。
  • 空間複雜度:O(n \cdot n!)
    需要儲存所有排列結果;另外遞迴深度和暫存路徑是 O(n)

💻 程式碼實作(二)

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> now;
    vector<bool> check;
    int n;
    
    void f(vector<int>& nums) {
        if(now.size()==n){
            ans.push_back(now);
            return;
        }
        for(int i=0;i<n;i++){
            if(check[i]){
                continue;
            }
            check[i]=true;
            now.push_back(nums[i]);
            f(nums);
            
            now.pop_back();
            check[i]=false;
        }
    }
    
    vector<vector<int>> permute(vector<int>& nums) {
        n=nums.size();
        check.resize(n,false);
        f(nums);
        return ans;
    }
};

🔗 題目連結

https://leetcode.com/problems/permutations/

作者: scottnick
撰寫日期: 2026-04-24
類別:
原文連結: https://scottnick.github.io/cpp-notes/all%20problems/46-permutations.html