当前位置: 首页 > news >正文

回溯算法:List 还是 ArrayList?一个深拷贝引发的思考

在学习和使用回溯算法解决问题时,我们经常会遇到需要维护一个结果列表,例如所有可能的子集、组合或排列。 这个结果列表通常是一个 List<List<Integer>>,其中内部的 List<Integer> 代表一个具体的解。

然而,在构建这些内部的 List<Integer> 时,我们应该使用 List 接口还是 ArrayList 类呢? 这个问题看似简单,但背后隐藏着一个关于深拷贝和浅拷贝的重要概念,它直接影响到回溯算法的正确性。

问题重现:回溯算法中的陷阱

让我们考虑一个简单的例子:使用回溯算法找到一个数组的所有子集。 一个常见的实现方式如下:

import java.util.ArrayList;
import java.util.List;public class Subsets {public static List<List<Integer>> subsets(int[] nums) {List<List<Integer>> res = new ArrayList<>();backtrack(nums, 0, new ArrayList<>(), res);return res;}private static void backtrack(int[] nums, int index, List<Integer> currentSubset, List<List<Integer>> res) {// 基本情况:将当前子集添加到结果列表res.add(currentSubset); // 潜在的问题!// 递归探索for (int i = index; i < nums.length; i++) {currentSubset.add(nums[i]); // 选择backtrack(nums, i + 1, currentSubset, res);currentSubset.remove(currentSubset.size() - 1); // 撤销选择 (回溯)}}public static void main(String[] args) {int[] nums = {1, 2, 3};List<List<Integer>> allSubsets = subsets(nums);System.out.println(allSubsets);}
}

展开

这段代码看起来很合理,但如果你运行它,你会发现结果是错误的! 例如,对于输入 [1, 2, 3],你可能会得到类似 [[], [], [], [], [], [], [], []] 的结果,而不是预期的 [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

问题出在哪里?

问题在于这行代码:

res.add(currentSubset);

这里,我们直接将 currentSubset 添加到 res 中,而没有进行任何拷贝。 这意味着 res 中的所有元素都指向同一个 currentSubset 对象。 当我们在回溯过程中修改 currentSubset 时,res 中的所有元素都会受到影响,最终导致错误的结果。

深拷贝的必要性

为了解决这个问题,我们需要对 currentSubset 进行深拷贝,然后再将其添加到 res 中。 深拷贝会创建一个新的 ArrayList 对象,并将 currentSubset 中的所有元素复制到这个新的 ArrayList 中。 这样,res 中的每个元素都将指向一个独立的列表,而对 currentSubset 的修改不会影响到 res 中的其他元素。

正确的代码如下:

import java.util.ArrayList;
import java.util.List;public class Subsets {public static List<List<Integer>> subsets(int[] nums) {List<List<Integer>> res = new ArrayList<>();backtrack(nums, 0, new ArrayList<>(), res);return res;}private static void backtrack(int[] nums, int index, List<Integer> currentSubset, List<List<Integer>> res) {// 基本情况:将当前子集添加到结果列表res.add(new ArrayList<>(currentSubset)); // 深拷贝!// 递归探索for (int i = index; i < nums.length; i++) {currentSubset.add(nums[i]); // 选择backtrack(nums, i + 1, currentSubset, res);currentSubset.remove(currentSubset.size() - 1); // 撤销选择 (回溯)}}public static void main(String[] args) {int[] nums = {1, 2, 3};List<List<Integer>> allSubsets = subsets(nums);System.out.println(allSubsets);}
}

展开

现在,res.add(new ArrayList<>(currentSubset)) 创建了一个 currentSubset 的副本,并将这个副本添加到 res 中。 这样,res 中的每个元素都指向一个独立的列表,结果就是正确的。

相关文章:

  • Jenkins 代理自动化-dotnet程序
  • 配置HADOOP_HOME环境变量和maven_HOME环境变量
  • 线代第二章矩阵第二课:矩阵的加法、减法、数乘
  • Python+Playwright:编写自动化测试的避坑策略
  • Mac系统升级node.js版本和npm版本并安装pnpm
  • Node.js Session 原理简单介绍 + 示例代码
  • Sui 的工具生态简化了游戏开发者的 Web3 集成流程
  • 技术与情感交织的一生 (六)
  • My Diary Pro:记录生活,珍藏回忆
  • Android NDK 编译 so 文件 抹除导出符号 反逆向
  • 如何争取高层对项目的支持
  • Docker安装 (centos)
  • GitHub 封禁中国 IP:影响、原因及应对
  • 浏览器自动化检测对抗:修改navigator.webdriver属性的底层实现
  • python的strip()函数用法; 字符串切片操作
  • 解锁动态规划的奥秘:从零到精通的创新思维解析(8)
  • 深入理解UML动态图:系统行为建模全景指南
  • CExercise_13_1排序算法_3快速排序算法,包括单向分区以及双向分区
  • Redis之缓存过期淘汰策略
  • 应急响应篇钓鱼攻击邮件与文件EML还原蠕虫分析线索定性处置封锁
  • 上海发布首份直播电商行业自律公约,禁止虚假宣传、商业诋毁
  • 明星同款撕拉片,为何能炒到三四百一张?
  • 河南省省长王凯在郑州调研促消费工作,走访蜜雪冰城总部
  • 中国以优化营商环境为支点,为全球企业提供可预期市场环境
  • 欧派家居:一季度营收降4.8%,目前海外业务整体体量仍较小
  • 印巴局势快速升级,外交部:呼吁印巴以和平稳定的大局为重