You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于回溯的句子单词全排列算法内存溢出问题及替代方案咨询

单词全排列算法的内存溢出问题分析与优化

我正在测试一款基于回溯法的句子单词全排列生成算法,输入“orange is sweet”可生成“orange is sweet, is orange sweet, sweet is orange, orange sweet is”等排列。

算法实现

public static void calculatePermutations(String sentence)
{
    // 拆分句子为单词数组
    String[] lis = sentence.split(" ");

    // 存储所有排列结果
    List<String[]> permute = new ArrayList<>();

    // 生成全排列
    generatePermutations(lis, 0, permute);

    // 遍历打印所有排列
    for (String[] i : permute) {
        System.out.println(String.join(" ", i));
    }
}

// 回溯法生成全排列
public static void generatePermutations(String[] arr, int index, List<String[]> permute)                        
{
    // 递归终止:已处理完所有单词,保存当前排列
    if (index == arr.length) {
        permute.add(Arrays.copyOf(arr, arr.length));
        return;
    }

    // 遍历剩余单词,生成排列
    for (int i = index; i < arr.length; i++) {
        // 交换当前位置与i位置的单词
        String temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;

        // 递归处理下一个位置
        generatePermutations(arr, index + 1, permute);

        // 回溯:交换回原位置
        temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;
    }
}

// 测试入口
public static void main(String[] args)
{
    String sentence = "sky is blue";
    calculatePermutations(sentence);
}

该算法在小数据量下正常运行,但处理大量数据时会抛出内存溢出异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3481)

我注意到Arrays.copyOf每次都会生成新的数组实例,其核心源码如下:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object) newType == (Object) Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);

    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));

    return copy;
}

提问

  1. 当需要生成超100万条排列短语时,该算法的最大问题是什么?
  2. 递归是否是导致问题的诱因之一?
  3. 有哪些可行的替代优化方案?

解答

1. 核心问题:全量存储排列结果导致内存爆炸

当前算法最致命的问题是将所有排列结果预先存储在List<String[]>中。n个单词的全排列总数是n!,当n≥10时,10! = 3628800,已经超过360万条记录;n=12时,12! = 479001600,接近5亿条。每条排列都是独立的字符串数组,即使字符串本身是共享引用,数组对象的内存开销也会迅速累积,加上Arrays.copyOf不断创建新数组,最终耗尽堆内存触发OutOfMemoryError。

2. 递归不是核心诱因

递归本身不会直接导致内存溢出。回溯法的递归深度等于单词数量n,即使n=20,递归深度也只有20,远低于Java栈的默认阈值(通常是1000以上)。递归栈的内存开销和存储所有排列的内存相比可以忽略不计,真正的问题还是结果的全量存储。

3. 优化方案

方案1:生成即处理,不存储全量结果

直接在递归终止阶段(index == arr.length)处理当前排列(比如打印、写入文件、传递给下游逻辑),而不是存入列表。这样内存中始终只保留当前正在构建的数组,内存复杂度从O(n!)降到O(n)。

修改后的核心代码:

public static void calculatePermutations(String sentence)
{
    String[] lis = sentence.split(" ");
    generatePermutations(lis, 0);
}

public static void generatePermutations(String[] arr, int index)                        
{
    if (index == arr.length) {
        // 直接处理当前排列,无需存储
        System.out.println(String.join(" ", arr));
        return;
    }

    for (int i = index; i < arr.length; i++) {
        // 交换
        String temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;

        generatePermutations(arr, index + 1);

        // 回溯交换
        temp = arr[i];
        arr[i] = arr[index];
        arr[index] = temp;
    }
}

方案2:迭代式回溯(替代递归)

如果担心极端场景下递归栈的微小开销,可以用迭代方式模拟回溯逻辑,本质还是生成即处理,只是用循环和栈结构替代递归调用。

方案3:分批次处理(需存储部分结果时)

如果业务必须存储部分结果,可以分批次生成、处理、清理:比如每生成1万条排列就写入文件并清空临时存储,再继续生成下一批。但这种方式复杂度较高,优先推荐生成即处理。

方案4:Java Stream惰性生成

利用Java 8+的Stream API实现惰性求值,避免一次性加载所有排列到内存:

public static Stream<String> permutationStream(String[] words) {
    return permuteStream(words, 0);
}

private static Stream<String> permuteStream(String[] words, int index) {
    if (index == words.length - 1) {
        return Stream.of(String.join(" ", words));
    }
    Stream<String> result = Stream.empty();
    for (int i = index; i < words.length; i++) {
        swap(words, index, i);
        result = Stream.concat(result, permuteStream(words, index + 1));
        swap(words, index, i);
    }
    return result;
}

private static void swap(String[] arr, int i, int j) {
    String temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// 使用示例
public static void main(String[] args) {
    String[] words = "sky is blue".split(" ");
    permutationStream(words).forEach(System.out::println);
}

Stream会按需生成排列,不会一次性把所有结果加载到内存。


内容的提问来源于stack exchange,提问作者Ger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:59:57