目录
第一题
题目来源
题目内容
解决方法
方法一:回溯算法
方法二:permute方法
方法三:交换法
第二题
题目来源
题目内容
解决方法
方法一:回溯算法
方法二:递归和交换
方法三:二维列表
第三题
题目来源
题目内容
解决方法
方法一:旋转90度
方法二:使用辅助数组
方法三:先转置后反转
方法四:坐标变换
46. 全排列 - 力扣(LeetCode)
这道题可以使用回溯算法来解决。具体思路如下:
1、创建一个结果集列表 result 来存储所有可能的全排列。
2、创建一个临时列表 tempList 来存储当前正在生成的排列。
3、创建一个布尔数组 used,用于标记数字是否已经被使用过。
4、调用回溯函数 backtrack,传入初始参数:nums 数组、used 数组、tempList 列表和 result 结果集。
5、在回溯函数中,首先判断当前排列的长度是否等于 nums 数组的长度,如果是,则说明已经完成了一种排列,将其加入结果集 result 中。
6、否则,遍历 nums 数组中的每个数字:
7、回溯函数返回后,所有的排列都已经生成完毕,将结果集 result 返回。
这样,通过回溯的递归过程,可以生成所有可能的全排列。
import java.util.ArrayList;
import java.util.List;
class Solution {
public List> permute(int[] nums) {
List> result = new ArrayList<>();
List tempList = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, used, tempList, result);
return result;
}
private void backtrack(int[] nums, boolean[] used, List tempList, List> result) {
if (tempList.size() == nums.length) {
result.add(new ArrayList<>(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue; // 如果该数已经被使用过,则跳过
tempList.add(nums[i]);
used[i] = true;
backtrack(nums, used, tempList, result);
tempList.remove(tempList.size() - 1);
used[i] = false;
}
}
}
复杂度分析:
综上所述,该解法的时间复杂度为O(N!),空间复杂度为O(N)。
LeetCode运行结果:
除了回溯算法外,还可以使用Java的库函数Collections中的permute方法来生成全排列。具体步骤如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Solution {
public List> permute(int[] nums) {
List> result = new ArrayList<>();
// 转换为List类型
List list = new ArrayList<>();
for (int num : nums) {
list.add(num);
}
// 使用库函数生成全排列
permuteHelper(list, 0, result);
return result;
}
private void permuteHelper(List list, int start, List> result) {
if (start == list.size()) {
result.add(new ArrayList<>(list));
} else {
for (int i = start; i < list.size(); i++) {
// 交换元素
Collections.swap(list, start, i);
// 继续生成后面的排列
permuteHelper(list, start + 1, result);
// 恢复原始顺序
Collections.swap(list, start, i);
}
}
}
}
复杂度分析:
综上所述,使用库函数Collections.permute的解法的时间复杂度为O(N!),空间复杂度为O(N!)。
LeetCode运行结果:
除了回溯算法和库函数Collections,还可以使用交换法来生成全排列。
交换法的思路是:
import java.util.ArrayList;
import java.util.List;
class Solution {
public List> permute(int[] nums) {
List> result = new ArrayList<>();
// 转换为List类型
List list = new ArrayList<>();
for (int num : nums) {
list.add(num);
}
permuteHelper(list, 0, result);
return result;
}
private void permuteHelper(List list, int start, List> result) {
if (start == list.size() - 1) {
result.add(new ArrayList<>(list));
} else {
for (int i = start; i < list.size(); i++) {
Collections.swap(list, start, i); // 交换元素
permuteHelper(list, start + 1, result); // 递归处理下一个位置
Collections.swap(list, start, i); // 恢复原始顺序
}
}
}
}
复杂度分析:
综上所述,使用交换法的解法的时间复杂度为O(N * N!),空间复杂度为O(N!)。
LeetCode运行结果:
47. 全排列 II - 力扣(LeetCode)
可以使用回溯算法来解决这个问题。回溯算法通过遍历所有可能的解空间来找到问题的解。
具体步骤如下:
首先将给定的序列 nums 进行排序,以便于后续处理。
创建一个空列表 result 来存储所有的全排列。
创建一个空列表 path 来存储当前正在构建的排列。
创建一个与 nums 长度相等的布尔数组 used,用于记录每个元素是否已经被使用过。
调用回溯函数 backtrack(0) 开始生成全排列。
在回溯函数 backtrack 中,首先判断如果 path 的长度等于 nums 的长度,则将 path 加入到 result 中,并返回。
否则,遍历 nums 数组,对于每个元素 nums[i],进行以下操作:
如果当前元素 nums[i] 已经被使用过(used[i] = true),则跳过该元素。
如果当前元素 nums[i] 与前一个元素相同,并且前一个元素还没有被使用过(used[i-1] = false),则跳过该元素。这是为了避免生成重复的排列。
将当前元素 nums[i] 添加到 path 中,并将 used[i] 设置为 true,表示已经使用过。
递归调用 backtrack(i+1) 继续生成下一个位置的元素。
回溯:将当前元素从 path 中移除,并将 used[i] 设置为 false,表示回溯到上一层。
最后返回 result。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List> permuteUnique(int[] nums) {
List> result = new ArrayList<>();
List path = new ArrayList<>();
boolean[] used = new boolean[nums.length];
Arrays.sort(nums); // 对nums进行排序
backtrack(nums, used, path, result);
return result;
}
private void backtrack(int[] nums, boolean[] used, List path, List> result) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
path.add(nums[i]);
used[i] = true;
backtrack(nums, used, path, result);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
复杂度分析:
对于给定长度为 n 的序列 nums,我们来分析一下算法的时间复杂度和空间复杂度。
时间复杂度:
空间复杂度:
需要注意的是,由于全排列的个数可以很大(n!),因此在实际应用中,当 n 较大时,算法可能会耗费较多的时间和空间资源。
LeetCode运行结果:
除了回溯算法,我们还可以使用其他方法来解决这个问题。
一种常用的方法是使用递归和交换元素的方式来生成全排列。具体步骤如下:
首先创建一个空列表 result 来存储所有的全排列。
调用递归函数 backtrack(nums, 0, result) 开始生成全排列。
在递归函数 backtrack 中,首先判断如果当前位置 index 等于 nums 数组的长度,则将当前排列加入到 result 中,并返回。
否则,遍历从 index 到 nums 数组末尾的所有位置,对于每个位置 i,进行以下操作:
如果当前位置 index 和位置 i 的元素相同,则跳过该位置,以避免生成重复的排列。
将当前位置 index 和位置 i 的元素交换。
递归调用 backtrack(nums, index + 1, result) 继续生成下一个位置的元素。
回溯:将当前位置 index 和位置 i 的元素交换回来。
最后返回 result。
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List> permuteUnique(int[] nums) {
List> result = new ArrayList<>();
backtrack(nums, 0, result);
return result;
}
private void backtrack(int[] nums, int index, List> result) {
if (index == nums.length) {
List permutation = new ArrayList<>();
for (int num : nums) {
permutation.add(num);
}
result.add(permutation);
return;
}
for (int i = index; i < nums.length; i++) {
if (isDuplicate(nums, index, i)) {
continue;
}
swap(nums, index, i);
backtrack(nums, index + 1, result);
swap(nums, index, i); // 回溯
}
}
private boolean isDuplicate(int[] nums, int start, int end) {
for (int i = start; i < end; i++) {
if (nums[i] == nums[end]) {
return true;
}
}
return false;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
这样就可以通过调用 permuteUnique 方法来获取给定序列的所有不重复全排列。这种方法在实现上更加简洁,但是在效率上可能略逊于回溯算法。
复杂度分析:
时间复杂度:
空间复杂度:
需要注意的是,全排列的个数可以很大(n!),因此在实际应用中,当 n 较大时,算法可能会耗费较多的时间和空间资源。
LeetCode运行结果:
除了回溯算法和递归交换元素,我们还可以使用其他方法来生成全排列。以下是另一种常用的方法:
首先,将数组 nums 转换为一个 ArrayList 来方便后续操作。
创建一个空的二维列表 result 来存储全排列。
调用递归函数 permuteUniqueHelper(ArrayList> result) 来生成全排列。
在递归函数 permuteUniqueHelper 中,首先判断如果当前位置 start 等于 nums 列表的长度,则将当前排列加入到 result 中,并返回。
否则,遍历从 start 到 nums 列表末尾的所有位置,对于每个位置 i,进行以下操作:
如果当前位置 start 和位置 i 的元素相同,则跳过该位置,以避免生成重复的排列。
交换当前位置 start 和位置 i 的元素。
将当前位置 start 的元素固定,继续递归调用 permuteUniqueHelper(nums, start + 1, result) 来生成下一个位置的元素。
回溯:恢复位置 start 和位置 i 的元素的原始顺序。
最后返回 result。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List> permuteUnique(int[] nums) {
List> result = new ArrayList<>();
ArrayList numsList = new ArrayList<>();
for (int num : nums) {
numsList.add(num);
}
permuteUniqueHelper(numsList, 0, result);
return result;
}
private void permuteUniqueHelper(ArrayList nums, int start, List> result) {
if (start == nums.size()) {
result.add(new ArrayList<>(nums));
return;
}
for (int i = start; i < nums.size(); i++) {
if (isDuplicate(nums, start, i)) {
continue;
}
// 交换位置 start 和位置 i 的元素
swap(nums, start, i);
// 固定位置 start 的元素,递归生成下一个位置的元素
permuteUniqueHelper(nums, start + 1, result);
// 恢复位置 start 和位置 i 的元素的原始顺序,进行回溯
swap(nums, start, i);
}
}
private boolean isDuplicate(ArrayList nums, int start, int end) {
for (int i = start; i < end; i++) {
if (nums.get(i).equals(nums.get(end))) {
return true;
}
}
return false;
}
private void swap(ArrayList nums, int i, int j) {
Integer temp = nums.get(i);
nums.set(i, nums.get(j));
nums.set(j, temp);
}
}
这种方法与之前的方法相比稍微简洁一些,并且在效率上也具有一定优势。同样地,需要注意全排列的个数可能很大,因此在处理较大规模的输入时仍然需要谨慎考虑算法的时间和空间复杂度。
复杂度分析:
时间复杂度:
空间复杂度:
综上所述,该方法的时间复杂度是 O(n!),空间复杂度是 O(n!),其中 n 是数组的长度。需要注意的是,由于全排列的数量是非常大的,因此在处理较大规模的输入时,可能会受到时间和空间复杂度的限制。
LeetCode运行结果:
48. 旋转图像 - 力扣(LeetCode)
题目要求将给定的二维矩阵顺时针旋转90度,并且需要在原地修改输入的矩阵。
解题思路:
通过以上步骤,就可以将矩阵顺时针旋转90度。注意,这里的翻转操作是在原矩阵上进行的,因此不需要使用额外的空间。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 先将矩阵沿副对角线翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][n - i - 1];
matrix[n - j - 1][n - i - 1] = temp;
}
}
// 再将矩阵沿水平中线翻转
for (int i = 0; i < n / 2; i++) {
for (int j = 0; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
}
}
复杂度分析:
对于给定的 n × n 矩阵,我们需要沿副对角线翻转矩阵并沿水平中线翻转矩阵,来完成顺时针旋转90度的操作。
时间复杂度分析:
空间复杂度分析:
综上所述,该算法的时间复杂度为 O(n^2),空间复杂度为 O(1)。
LeetCode运行结果:
该方法通过创建一个辅助数组来存储旋转后的矩阵元素,然后将辅助数组中的元素重新赋值给原矩阵。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
int[][] temp = new int[n][n];
// 将矩阵元素赋值给辅助数组
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
temp[i][j] = matrix[i][j];
}
}
// 将辅助数组中的元素按规则赋值给原矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[j][n - i - 1] = temp[i][j];
}
}
}
}
复杂度分析:
时间复杂度分析:
空间复杂度分析:
LeetCode运行结果:
该方法先将矩阵进行转置操作(行列互换),然后再对每一行进行反转操作。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 转置矩阵
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 反转每一行
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - j - 1];
matrix[i][n - j - 1] = temp;
}
}
}
}
复杂度分析:
时间复杂度分析:
空间复杂度分析:
LeetCode运行结果:
还有一种基于坐标变换的方法可以实现矩阵的旋转。
该方法的思想是通过找到旋转前后对应位置的关系,直接将旋转后的元素赋值给旋转前的位置。这种方法通过循环遍历矩阵中的四个角落的元素,逐步交换它们的值,实现矩阵的旋转。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 对每个元素进行旋转
for (int i = 0; i < n / 2; i++) {
for (int j = i; j < n - i - 1; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
}
复杂度分析:
时间复杂度分析:
空间复杂度分析:
综上所述,该方法的时间复杂度为 O(n^2),空间复杂度为 O(1)。这是一种高效的解决方案。
LeetCode运行结果: