PAYATHON 2026

回溯中如何判断下一次递归应传 ind + 1 还是 i + 1?

数据小许

下一次递归传什么,取决于当前选择后还剩什么需要处理。排列每次填好一个位置,所以传 ind + 1;回文分割每次选取区间 [ind, i],所以传 i + 1。

原问题最后一句把两者写反了,下面以代码中的调用为准。

排列:ind 是待填位置,i 是候选元素的位置

在这段交换式排列代码中,进入 set(ind, nums, ans) 时:

  • [0, ind) 中的位置已经确定,在当前递归分支内保持不变。
  • [ind, nums.length) 中的元素还没有安排好。
  • 本层递归负责确定位置 ind 应该放哪个元素。

循环变量 i 用来枚举候选元素。执行:

swap(i, ind, nums);

原本位于 i 的候选元素就被放到了位置 ind。这一步确定了一个位置,所以下一个待填位置始终是 ind + 1:

for (int i = ind; i < nums.length; i++) {
    swap(i, ind, nums);
    set(ind + 1, nums, ans);
    swap(i, ind, nums);
}

例如,nums = [1, 2, 3],当前 ind = 0,选择 i = 2,交换后的数组是:

[3, 2, 1]

此时只确定了位置 0 放 3,位置 1、2 还需要排列,所以应调用 set(1, nums, ans)。

如果改为 set(i + 1, nums, ans),这里就会调用 set(3, nums, ans),直接触发终止条件。程序会把尚未处理的位置当作已经完成,从而漏掉排列。

递归返回后的第二次 swap 用来恢复现场,让循环继续尝试其他候选元素。

回文分割:ind 是未处理后缀的起点,i 是当前片段的终点

进入 partition(ind, ans, ds, s) 时,ds 中的片段已经连续覆盖了前缀 [0, ind)。接下来要分割的是从 ind 开始的后缀。

本层循环尝试不同的结束位置 i,检查区间 [ind, i] 是否为回文:

if (ispalindrome(s, ind, i)) {
    ds.add(s.substring(ind, i + 1));
    partition(i + 1, ans, ds, s);
    ds.remove(ds.size() - 1);
}

Java 的 substring(ind, i + 1) 包含起点、不包含终点,因此选中的字符下标是 ind 到 i。这些字符已经使用过,下一次递归就从 i + 1 开始。

例如,s = "aab",当前 ind = 0,选择 i = 1:

已选片段:"aa"
已覆盖下标:0、1
剩余部分:"b"
下一次起点:2

此时应调用 partition(2, ans, ds, s)。如果传入 ind + 1,起点会变成 1,下标 1 的字符就会被重复使用,产生重叠的分割。

当选择单个字符,即 i == ind 时,i + 1 和 ind + 1 恰好相同;选取更长的片段时,两者就不同了。

如何自己推导递归参数?

写递归调用前,可以先回答三个问题:

  1. 函数参数表示什么?是待填的位置、剩余区间的起点,还是其他状态?
  2. 本次选择完成了多少工作?是填好一个位置,还是消耗了一段连续区间?
  3. 下一层还需要处理什么?根据剩余任务更新参数。

这两段代码的状态变化如下:

问题当前参数的含义本次选择完成的工作下一次参数
交换式排列ind 是待填位置确定位置 ind 的元素ind + 1
回文分割ind 是未处理后缀的起点使用字符区间 [ind, i]i + 1

常见的子集枚举也会使用 i + 1。如果当前选择了下标 i 的元素,下一层只考虑它后面的元素,就能按下标递增的顺序构造子集,避免重复枚举同一组下标。允许重复使用元素的问题则可能继续从 i 开始,具体取决于题目约束和状态定义。

代码使用时需要注意什么?

这些片段省略了结果收集和辅助函数。原始回文分割片段末尾还多了一个右花括号,整理为可运行代码时需要修正。到达终止条件后,应把当前结果的副本加入 ans;例如回文分割使用:

if (ind == s.length()) {
    ans.add(new ArrayList<>(ds));
    return;
}

保存副本可以避免后续回溯修改已经收集的结果。排列也需要在叶子节点复制当前数组内容。另外,如果输入数组含有重复值,这段交换式排列代码可能生成重复排列,需要另行处理去重。

备注:内容仅供参考。