回溯时current.remove被执行了两次

编程语言 2026-07-09

我对编程中的回溯概念有很多问题,但主要是想弄清为什么current.remove会执行两次。于是我在练习一个LeetCode的题目,需要找出数组 {1,2,3} 的所有可能子集。看起来最直接、也最常用的方法就是回溯。

下面是LeetCode上某人给出的解法

package CodingPractice;
import java.util.*;

public class BackTrackingExample {

    /**
     * Backtracking function to find all subsets of an array.
     */
    private static void backtrack(int[] nums, int start, List<Integer> current, List<List<Integer>> result) {
        // Add the current path to our list of results
        result.add(new ArrayList<>(current));

        // Iterate through the options starting from the current index
        for (int i = start; i < nums.length; i++) {
            // 1. Make a choice: add element to the current subset
            current.add(nums[i]);

            // 2. Explore: move deeper into the decision tree
            backtrack(nums, i + 1, current, result);

            // 3. Backtrack: remove the last choice to explore other possibilities
            current.remove(current.size() - 1);
        }
    }

    /**
     * Entry point for the Java Virtual Machine (JVM).
     */
    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        List<List<Integer>> result = new ArrayList<>();

        // Start the backtracking process
        backtrack(nums, 0, new ArrayList<>(), result);

        // Print the findings
        System.out.println("All possible subsets:");
        for (List<Integer> subset : result) {
            System.out.println(subset);
        }
    }
}

我运行了代码,它确实给出了所有正确的子集。

但在调试过程中有些事情我不太理解。我只是想确保自己完全理解。

我整理了一份让我困惑、为什么会这样的点的清单,希望有人能为我解答。

  1. 为什么在执行current.remove之后,变量i 会从2 变成1?
  2. 为什么在第一次退出循环时,当i 变为3 时,current.remove会紧接着执行两次?
  3. 为什么在第二次执行current.remove之后(把 {1,2,3} 加入结果后),i会从1 变为2?

解决方案

current.remove() 实际上并不是在同一个地方执行两次。发生的情况是,递归调用会返回到调用栈的上一层级,在继续循环之前,每一层都会移除它添加的那个值。

这部分是关键的:

current.add(nums[i]);

backtrack(nums, i + 1, current, result);

current.remove(current.size() - 1);

流程基本如下:

  1. 添加一个值
  2. 进入更深的递归
  3. 从递归返回
  4. 移除最后一个值
  5. 继续测试其他可能性

因此在调试时看到多次 remove() 调用,通常是在不同的递归调用中发生的,而不是在同一个执行层级。

同样的情况也出现在 i。每个递归调用都有自己的循环状态,因此当递归结束时,执行会从它离开的上一层继续。

例如,在探索:

[1,2,3]

之后,算法会回到:

[1,2]

移除 3,然后继续检查其他分支。

帮助我更好理解回溯的一点,是把它想象成在一棵树上进行探索,完成每个分支后再向后退步:

[]
├── [1]
│   ├── [1,2]
│   │   └── [1,2,3]
│   └── [1,3]
├── [2]
│   └── [2,3]
└── [3]

完成一个分支后,算法会“撤销”上一次的选择,以便继续探索下一个分支。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章