回溯——46.全排列 && 47.全排列 II
阿新 • • 發佈:2021-12-10
給定一個 沒有重複 數字的序列,返回其所有可能的全排列。
示例:
- 輸入: [1,2,3]
- 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
全排列,顧名思義就是:Amn,結果剛開始的我想都沒想就寫出了空間O(n2)的演算法。。。。
//錯誤示範
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
int[][] ans = new int[n][n];
for (int i = 0; i < n; ++i) {
int number = nums[i];
int index = i;
for (int j = 0; j < n; ++j) {
int[] arr = ans[j];
arr[index] = number;
index = (index + 1) % n;
}
}
for (int[] arr : ans) {
List<Integer> list = new ArrayList<>();
for (int number : arr) {
list.add(number);
}
result.add(list);
}
return result;
}
根據Anm,本來想用迴圈,填滿一個int[Anm][Anm]的矩陣,再轉化為二維List。
後來用手寫模擬全排列列舉過程的時候,發現了規律,可見手寫模擬的重要性:
對於當前的path,只要確定了當前需要新增的數字,那麼這條path只要將nums裡剩下的數字的全排列,依次拼接到path中,就完成的單層的回溯任務。
所以在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將刪除了這個數字的子陣列再傳入下一層回溯樹中,這就確定了參
由此回溯分析完成:
- 返回值:void——不需要,由外層的result收集結果
- 引數:List<Integer>nums——在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將刪除了這個數字的子陣列
- 終止:path.size()== 初始陣列的長度,則result.add();
- 單層邏輯:在當前回溯步驟中,對於拿到的陣列nums,我們要依次挑選(遍歷)所有的數字,並將數字加入path中,然後將刪除了這個數字的子陣列再傳入下一層回溯樹中
因此程式碼如下:
class Solution {
List<List<Integer>> result = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
//原始陣列的長度
int n = 0;
public List<List<Integer>> permute(int[] arr) {
n = arr.length;
//將原始陣列處理成List,方便回溯時刪除元素
List<Integer> nums = new ArrayList<>(n);
for (int i : arr) {
nums.add(i);
}
backtracking(nums);
return result;
}
private void backtracking(List<Integer> nums) {
if (path.size() == n) {
result.add((ArrayList) path.clone());
return ;
}
int i = 0;
for (Integer number : nums) {
path.add(number);
backtracking(delete(nums, i++));
path.remove(path.size()-1);
}
}
//用以刪除List中具體索引的數,並返回一個新的list
private List<Integer> delete(List<Integer> nums, int index) {
List<Integer> ans = new ArrayList<>();
int i = 0;
for (Integer number : nums) {
if (i++ == index) {
continue;
}
ans.add(number);
}
return ans;
}
}
由此做 47.全排列 II 時,自然先用筆模擬了一下,發現了以下規律:
在全排列回溯時,當前回溯樹層已經加入path的數不要再次加入,否則會產生重複
程式碼如下:
private void backtracking(List<Integer> nums){ if (path.size() == n) { result.add((ArrayList) path.clone()); return ; } Set<Integer> set = new HashSet<>(); int i = -1; for (Integer number : nums) { ++i; //如果重複了,則直接跳過 if (set.contains(number)) { continue; } else { set.add(number); path.add(number); backtracking(delete(nums, i)); path.remove(path.size()-1); } } }