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;
}
}