Leetcode46:全排列Ⅰ(java)
題目描述
思路圖解
1.遞歸回溯法
首先介紹“回溯”算法的應用。“回溯”算法也叫“回溯搜索”算法,主要用於在一個龐大的空間裏搜索我們所需要的問題的解。我們每天使用的“搜索引擎”就是幫助我們在龐大的互聯網上搜索我們需要的信息。“搜索”引擎的“搜索”和“回溯搜索”算法的“搜索”意思是一樣的。
“回溯”指的是“狀態重置”,可以理解爲“回到過去”、“恢復現場”,是在編碼的過程中,是爲了節約空間而使用的一種技巧。而回溯其實是“深度優先遍歷”特有的一種現象。之所以是“深度優先遍歷”,是因爲我們要解決的問題通常是在一棵樹上完成的,在這棵樹上搜索需要的答案,一般使用深度優先遍歷。
“全排列”就是一個非常經典的“回溯”算法的應用。我們知道,N 個數字的全排列一共有 N!N!N! 這麼多個。
大家可以嘗試一下在紙上寫 3 個數字、4 個數字、5 個數字的全排列,相信不難找到這樣的方法。
以數組 [1, 2, 3] 的全排列爲例。
我們先寫以 1 開頭的全排列,它們是:[1, 2, 3], [1, 3, 2];
再寫以 2 開頭的全排列,它們是:[2, 1, 3], [2, 3, 1];
最後寫以 3 開頭的全排列,它們是:[3, 1, 2], [3, 2, 1]。
我們只需要按順序枚舉每一位可能出現的情況,已經選擇的數字在接下來要確定的數字中不能出現。按照這種策略選取就能夠做到不重不漏,把可能的全排列都枚舉出來。
在枚舉第一位的時候,有 3 種情況。
在枚舉第二位的時候,前面已經出現過的數字就不能再被選取了;
在枚舉第三位的時候,前面 2 個已經選擇過的數字就不能再被選取了。
這樣的思路,我們可以用一個樹形結構表示。看到這裏的朋友,建議自己先嚐試畫一下“全排列”問題的樹形結構。
使用編程的方法得到全排列,就是在這樣的一個樹形結構中進行編程,具體來說,就是執行一次深度優先遍歷,從樹的根結點到葉子結點形成的路徑就是一個全排列。
舉個例子
從 [1, 2, 3] 到 [1, 3, 2] ,深度優先遍歷是這樣做的,從 [1, 2, 3] 回到 [1, 2] 的時候,需要撤銷剛剛已經選擇的數 3,因爲在這一層只有一個數 3 我們已經嘗試過了,因此程序回到上一層,需要撤銷對 2 的選擇,好讓後面的程序知道,選擇 3 了以後還能夠選擇 2。
這種在遍歷的過程中,從深層結點回到淺層結點的過程中所做的操作就叫“回溯”。
2.迭代法
代碼實現
package LeetCode;
import java.util.ArrayList;
import java.util.List;
public class Permute
{
public static void main(String[] args)
{
int nums[]={1,2,3};
System.out.println(permute2(nums));
}
//方法一:遞歸回溯法
public static List<List<Integer>> permute(int[] nums)
{
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (nums == null || nums.length == 0)
return res;
helper(res, new ArrayList<Integer>(), nums);
return res;
}
public static void helper(List<List<Integer>> res, List<Integer> list,
int[] nums)
{
if (list.size() == nums.length)
{
// ArrayList是一個引用,記錄的是指向位置,如果對應位置上的數據被修改,結果就不是想要的了。
res.add(new ArrayList<Integer>(list));
// 如果是這樣,無法拷貝integers裏面的值
// res.add(list);
return;
}
for (int i = 0; i < nums.length; i++)
{
if (list.contains(nums[i]))
continue;
list.add(nums[i]);
helper(res, list, nums);
list.remove(list.size() - 1);
}
}
//方法二
public static List<List<Integer>> permute2(int[] nums)
{
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (nums == null || nums.length == 0)
return res;
helper2(res, 0, nums);
return res;
}
public static void helper2(List<List<Integer>> res, int first, int[] nums)
{
if (first==nums.length)
{
List<Integer> list=new ArrayList<Integer>();
for(int num:nums) {
list.add(num);
}
res.add(new ArrayList<Integer>(list));
return;
}
for (int i = first; i < nums.length; i++)
{
swap(nums, first, i);
helper2(res, first+1, nums);
swap(nums, first, i);//換回去
}
}
public static void swap(int[]nums,int l,int r)
{
int temp=nums[l];
nums[l]=nums[r];
nums[r]=temp;
}
}
運行結果
Leetcode47:全排列Ⅱ(java)
題目描述
思路圖解
在一定會產生重複結果集的地方剪枝
例題:對於數組 [1, 1’, 1’’, 2],回溯的過程如下:
得到的全排列是:[[1, 1’, 1’’, 2], [1, 1’, 2, 1’’], [1, 2, 1’, 1’’], [2, 1, 1’, 1’’]]。特點是:1、1’、1’’ 出現的順序只能是 1、1’、1’’
代碼實現
package LeetCode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Permute2
{
public static void main(String[] args)
{
int nums[] =
{
1, 1, 1, 3
};
System.out.println("方案一:" + permuteUnique(nums));
System.out.println("方案二:" + permuteUnique2(nums));
}
public static List<List<Integer>> permuteUnique(int[] nums)
{
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (nums == null || nums.length == 0)
{
return res;
}
// 修改 1:排序(升序或者降序都可以),爲了剪枝方便
Arrays.sort(nums);
helper(res, new ArrayList<Integer>(), nums, new boolean[nums.length]);
return res;
}
public static void helper(List<List<Integer>> res, List<Integer> list,
int[] nums, boolean[] used)
{
if (list.size() == nums.length)
{
res.add(new ArrayList<Integer>(list));
return;
}
for (int i = 0; i < nums.length; i++)
{
// 修改 2:在 used[i - 1] 剛剛被撤銷的時候剪枝,說明接下來會被選擇,搜索一定會重複,故"剪枝"
if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1])
continue;
used[i] = true;
list.add(nums[i]);
helper(res, list, nums, used);
// 回溯,撤銷選擇
used[i] = false;
list.remove(list.size() - 1);
}
}
//方法二:
public static List<List<Integer>> permuteUnique2(int[] nums)
{
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (nums == null || nums.length == 0)
{
return res;
}
// 修改 1:排序(升序或者降序都可以),爲了剪枝方便
Arrays.sort(nums);
helper2(res, nums, 0);
return res;
}
public static void helper2(List<List<Integer>> res, int[] nums, int start)
{
if (start == nums.length)
{
List<Integer> list = new ArrayList<Integer>();
for (int num : nums)
{
list.add(num);
}
res.add(list);
return;
}
for (int i = start; i < nums.length; i++)
{
if (isUsed(nums, start, i))
continue;
swap(nums, start, i);
helper2(res, nums, start + 1);
swap(nums, start, i);
}
}
public static void swap(int[] nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public static boolean isUsed(int[] nums, int i, int j)
{
for (int x = i; x < j; x++)
{
if (nums[x] == nums[j])
return true;
}
return false;
}
}