package com.hmdp.leetcode;
import java.util.*;
public class backtracking31 {
public void nextPermutation(int[] nums) {
// 1. 将当前数组转为字符串表示
StringBuilder sb = new StringBuilder();
for (int num : nums) {
sb.append(num);
}
String value = sb.toString();
// 2. 生成所有全排列,并存储为字符串
Set result = new HashSet<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, new StringBuilder(), used, result);
// 3. 排序所有排列
List sortedList = new ArrayList<>(result);
Collections.sort(sortedList);
// 4. 找到当前 value 的下一个排列
for (int j = 0; j < sortedList.size(); j++) {
if (sortedList.get(j).equals(value)) {
int nextIndex = (j == sortedList.size() - 1) ? 0 : j + 1;
String nextValue = sortedList.get(nextIndex);
// 5. 写回原数组
Arrays.fill(nums, 0);
for (int k = 0; k < nextValue.length(); k++) {
nums[k] = nextValue.charAt(k) - '0';
}
break;
}
}
}
// 回溯生成全排列(字符串形式)
private void backtrack(int[] nums, StringBuilder path, boolean[] used, Set result) {
if (path.length() == nums.length) {
result.add(path.toString());
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
used[i] = true;
path.append(nums[i]);
backtrack(nums, path, used, result);
path.deleteCharAt(path.length() - 1);
used[i] = false;
}
}
}
import com.hmdp.leetcode.backtracking31;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SolutionTest {
@Test
public void testNextPermutation() {
backtracking31 solution = new backtracking31();
// Existing test cases
int[] nums1 = {1, 2, 3};
solution.nextPermutation(nums1);
assertArrayEquals(new int[]{1, 3, 2}, nums1);
int[] nums2 = {3, 2, 1};
solution.nextPermutation(nums2);
assertArrayEquals(new int[]{1, 2, 3}, nums2);
int[] nums3 = {1, 1, 5};
solution.nextPermutation(nums3);
assertArrayEquals(new int[]{1, 5, 1}, nums3);
int[] nums4 = {0, 0};
solution.nextPermutation(nums4);
assertArrayEquals(new int[]{0, 0}, nums4);
int[] nums5 = {2, 2, 2};
solution.nextPermutation(nums5);
assertArrayEquals(new int[]{2, 2, 2}, nums5);
int[] nums6 = {1, 3, 4, 2};
solution.nextPermutation(nums6);
assertArrayEquals(new int[]{1, 4, 2, 3}, nums6);
int[] nums7 = {4, 3, 2, 1};
solution.nextPermutation(nums7);
assertArrayEquals(new int[]{1, 2, 3, 4}, nums7);
int[] nums8 = {1, 1, 1, 1};
solution.nextPermutation(nums8);
assertArrayEquals(new int[]{1, 1, 1, 1}, nums8);
int[] nums9 = {1, 2, 4, 3};
solution.nextPermutation(nums9);
assertArrayEquals(new int[]{1, 3, 2, 4}, nums9);
// New test cases
int[] nums10 = {0, 0, 4, 2, 1, 0};
solution.nextPermutation(nums10);
assertArrayEquals(new int[]{0, 1, 0, 0, 2, 4}, nums10);
int[] nums11 = {6, 7, 5, 3, 5, 6, 2, 9, 1, 2, 7, 0, 9};
solution.nextPermutation(nums11);
assertArrayEquals(new int[]{6, 7, 5, 3, 5, 6, 2, 9, 1, 2, 9, 0, 7}, nums11);
}
}
最后一个测试用例超时了,其他都是对的,就是思路好理解些
当排列中的数字较大或排列长度较长时,直接使用整数类型(如 int
或 long
)来表示排列可能会导致数值溢出。
例如,在某些情况下,排列可能需要非常大的数值范围,而标准的整数类型无法容纳。
改用 String
表示排列:字符串可以表示任意长度和大小的排列,不会受到数值类型的限制。通过字符串处理,你可以避免数值溢出的问题。
在使用整数类型表示排列时,前导零会被自动忽略。例如,排列 [0, 0, 4, 2, 1, 0]
转换为整数后会变成 4210
,丢失了前导零。
这会导致排列信息不完整,影响后续的排列生成和比较。
使用字符串处理,保留完整信息:字符串可以保留排列中的所有数字,包括前导零。通过字符串操作,你可以确保排列的完整性,避免前导零丢失的问题。
使用回溯法或其他复杂算法生成全排列时,时间复杂度较高,尤其是在大规模输入的情况下。
回溯法的时间复杂度通常是指数级的,对于较大的输入规模,计算量会急剧增加,导致效率低下。
仅用于理解逻辑,不适用于大规模输入:在学习和理解排列生成的逻辑时,可以使用回溯法等方法。但在实际应用中,特别是面对大规模输入时,应选择更高效的算法或数据结构。
可以考虑使用迭代法或其他优化算法来提高效率。
在将全排列结果写回数组时,可能出现错误,导致排列顺序或内容不正确。
这可能是由于字符串和数组之间的转换不正确,或者在处理过程中出现了逻辑错误。
使用字符串逐位写回数组:在将字符串形式的排列写回数组时,逐位进行转换和赋值,确保每个字符都能正确地对应到数组中的相应位置。
例如,可以通过遍历字符串,将每个字符转换为整数,并依次写入数组中。
public class Solution {
public static void nextPermutation(int[] nums) {
if (nums == null || nums.length <= 1) {
return;
}
// Step 1: Find the first decreasing element from the end.
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// If such an element is found, find the element to swap with.
if (i >= 0) {
int j = nums.length - 1;
while (nums[j] <= nums[i]) {
j--;
}
// Step 2: Swap the elements.
swap(nums, i, j);
}
// Step 3: Reverse the sequence after the position i.
reverse(nums, i + 1);
}
private static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private static void reverse(int[] nums, int start) {
int end = nums.length - 1;
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
}
假设我们有一个数组 nums = {1, 3, 5, 4, 2}
。我们的目标是找到这个排列的下一个字典序更大的排列。
寻找第一个降序对:
我们从后向前遍历数组,寻找第一个不符合升序的元素。
遍历顺序:2 -> 4 -> 5
(直到遇到 3
)。
在位置 i=1
处发现 3 < 5
,这是我们要找的第一个降序点。
寻找大于 nums[i]
的最小元素:
从数组末尾开始向前查找,寻找第一个比 nums[1]=3
大的元素。
查找顺序:2 -> 4
(不满足条件),然后是 4
,它比 3
大,且是最接近的一个。
找到索引 j=3
处的元素 4
。
交换 nums[i]
和 nums[j]
:
交换 nums[1]
和 nums[3]
,得到新的数组 {1, 4, 5, 3, 2}
。
反转 i+1
到数组末尾的部分:
反转 nums[2]
到 nums[4]
,即 {5, 3, 2}
。
反转后的结果为 {2, 3, 5}
。
最终数组变为 {1, 4, 2, 3, 5}
。
原始数组 {1, 3, 5, 4, 2}
经过上述步骤后变成了 {1, 4, 2, 3, 5}
,这正是它的下一个字典序排列。
寻找降序对:从后向前找到第一个降序的位置,确保能找到一个比当前位置大的元素进行交换。
寻找交换元素:在降序部分中找到刚好大于当前元素的值进行交换,保证新排列是下一个更大的排列。
反转操作:将降序部分反转成升序,使得这部分成为最小的排列,从而保证整个数组是下一个字典序排列。