java学习 leetcode31 下一个排列

1.排列方法(按照全排列,数组,整数来回转换的思路)

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

 2.测试用例


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

最后一个测试用例超时了,其他都是对的,就是思路好理解些

1. 溢出

✅ 问题原因:
  • 当排列中的数字较大或排列长度较长时,直接使用整数类型(如 intlong)来表示排列可能会导致数值溢出。

  • 例如,在某些情况下,排列可能需要非常大的数值范围,而标准的整数类型无法容纳。

✅ 修复方式:
  • 改用 String 表示排列:字符串可以表示任意长度和大小的排列,不会受到数值类型的限制。通过字符串处理,你可以避免数值溢出的问题。


2. 前导零丢失(这个确实有)

✅ 问题原因:
  • 在使用整数类型表示排列时,前导零会被自动忽略。例如,排列 [0, 0, 4, 2, 1, 0] 转换为整数后会变成 4210,丢失了前导零。

  • 这会导致排列信息不完整,影响后续的排列生成和比较。

✅ 修复方式:
  • 使用字符串处理,保留完整信息:字符串可以保留排列中的所有数字,包括前导零。通过字符串操作,你可以确保排列的完整性,避免前导零丢失的问题。


3. 效率低

✅ 问题原因:
  • 使用回溯法或其他复杂算法生成全排列时,时间复杂度较高,尤其是在大规模输入的情况下。

  • 回溯法的时间复杂度通常是指数级的,对于较大的输入规模,计算量会急剧增加,导致效率低下。

✅ 修复方式:
  • 仅用于理解逻辑,不适用于大规模输入:在学习和理解排列生成的逻辑时,可以使用回溯法等方法。但在实际应用中,特别是面对大规模输入时,应选择更高效的算法或数据结构。

  • 可以考虑使用迭代法或其他优化算法来提高效率。


4. 全排列写回错误(补上前导0,数组排序就有问题了)

✅ 问题原因:
  • 在将全排列结果写回数组时,可能出现错误,导致排列顺序或内容不正确。

  • 这可能是由于字符串和数组之间的转换不正确,或者在处理过程中出现了逻辑错误。

✅ 修复方式:
  • 使用字符串逐位写回数组:在将字符串形式的排列写回数组时,逐位进行转换和赋值,确保每个字符都能正确地对应到数组中的相应位置。

  • 例如,可以通过遍历字符串,将每个字符转换为整数,并依次写入数组中。

3.原地算法



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}。我们的目标是找到这个排列的下一个字典序更大的排列。

步骤详解
  1. 寻找第一个降序对

    • 我们从后向前遍历数组,寻找第一个不符合升序的元素。

    • 遍历顺序:2 -> 4 -> 5(直到遇到 3)。

    • 在位置 i=1 处发现 3 < 5,这是我们要找的第一个降序点。

  2. 寻找大于 nums[i] 的最小元素

    • 从数组末尾开始向前查找,寻找第一个比 nums[1]=3 大的元素。

    • 查找顺序:2 -> 4(不满足条件),然后是 4,它比 3 大,且是最接近的一个。

    • 找到索引 j=3 处的元素 4

  3. 交换 nums[i]nums[j]

    • 交换 nums[1]nums[3],得到新的数组 {1, 4, 5, 3, 2}

  4. 反转 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},这正是它的下一个字典序排列。

关键点总结

  • 寻找降序对:从后向前找到第一个降序的位置,确保能找到一个比当前位置大的元素进行交换。

  • 寻找交换元素:在降序部分中找到刚好大于当前元素的值进行交换,保证新排列是下一个更大的排列。

  • 反转操作:将降序部分反转成升序,使得这部分成为最小的排列,从而保证整个数组是下一个字典序排列。

你可能感兴趣的:(java,学习,leetcode)