全排列是經典算法問題:從 n 個不同元素中取出所有元素,按順序排列,共 n! 種結果。本文提供兩種 PHP 實現——遞歸回溯(易理解)與字典序迭代(無遞歸、常用於下一個排列),並附帶效能對比與實戰避坑指南。
['A','B','C','D'](n=4)4! = 24ABCD, ABDC, ACBD, ACDB, ADBC, ADCB, BACD, ...<?php
function permuteRecursive(array $elements): array {
$result = [];
$n = count($elements);
$used = array_fill(0, $n, false);
$path = [];
function backtrack(array &$elements, array &$used, array &$path, array &$result) {
if (count($path) === count($elements)) {
$result[] = implode('', $path); // 或保存陣列副本
return;
}
foreach ($elements as $i => $val) {
if ($used[$i]) continue;
$used[$i] = true;
$path[] = $val;
backtrack($elements, $used, $path, $result);
array_pop($path);
$used[$i] = false;
}
}
backtrack($elements, $used, $path, $result);
return $result;
}
$arr = ['A','B','C','D'];
$perms = permuteRecursive($arr);
echo "總共 " . count($perms) . " 種排列<br>";
foreach ($perms as $p) echo $p . "<br>";
關鍵點:
$used 陣列標記元素是否已進入當前路徑,避免重複使用。$path 存儲當前排列,滿長度時加入結果集。O(n! * n),空間複雜度 O(n)(遞歸棧 + 路徑)。next_permutation 思想)核心步驟:
a[i] < a[i+1] → 在右側找到比 a[i] 大的最小元素 a[j] → 交換 a[i] ↔ a[j] → 反轉 i+1 到末尾 → 得到下一排列。function permuteNext(array $elements): Generator {
sort($elements); // 確保起始為最小字典序
yield implode('', $elements);
while (true) {
$n = count($elements);
// 1. 從右向左找第一個 a[i] < a[i+1]
$i = $n - 2;
while ($i >= 0 && $elements[$i] >= $elements[$i+1]) $i--;
if ($i < 0) break; // 已是最大排列
// 2. 從右向左找第一個 a[j] > a[i]
$j = $n - 1;
while ($elements[$j] <= $elements[$i]) $j--;
// 3. 交換
[$elements[$i], $elements[$j]] = [$elements[$j], $elements[$i]];
// 4. 反轉 i+1 到末尾
$left = $i + 1;
$right = $n - 1;
while ($left < $right) {
[$elements[$left], $elements[$right]] = [$elements[$right], $elements[$left]];
$left++; $right--;
}
yield implode('', $elements);
}
}
// 使用 Generator 惰性輸出,記憶體 O(1)
foreach (permuteNext(['A','B','C','D']) as $p) {
echo $p . "<br>";
}
優勢:
| 實現 | 時間 | 峰值記憶體 | 備註 |
|---|---|---|---|
| 遞歸回溯 | ~1.2s | ~12 MB | 結果存陣列 |
| 字典序 + Generator | ~0.9s | ~2 MB | 逐個 yield |
| 字典序 + 陣列收集 | ~1.0s | ~12 MB | 同遞歸 |
結論:生產環境推薦字典序 + Generator,既快又省記憶體。
function permuteUnique(array $nums): array {
sort($nums);
$result = [];
$used = array_fill(0, count($nums), false);
$path = [];
function backtrackUnique(array &$nums, array &$used, array &$path, array &$result) {
if (count($path) === count($nums)) {
$result[] = $path;
return;
}
for ($i = 0; $i < count($nums); $i++) {
if ($used[$i]) continue;
// 剪枝:同層級相同值跳過
if ($i > 0 && $nums[$i] === $nums[$i-1] && !$used[$i-1]) continue;
$used[$i] = true;
$path[] = $nums[$i];
backtrackUnique($nums, $used, $path, $result);
array_pop($path);
$used[$i] = false;
}
}
backtrackUnique($nums, $used, $path, $result);
return $result;
}
// 輸入 [1,1,2] → [[1,1,2],[1,2,1],[2,1,1]]
全排列算法核心在於「有序選擇 + 狀態回溯」。PHP 中:
$i>0 && $a[$i]===$a[$i-1] && !$used[$i-1])。掌握兩種核心模板,即可應對面試與實際業務中的所有排列組合變體。