
本文详细阐述了如何通过最少次数的列表反转(reverse)和旋转(rotate)操作,将一个整数列表转换成目标列表。文章采用递归深度优先搜索(dfs)策略,构建操作树,并引入父操作剪枝优化,避免重复计算。教程提供了java实现代码,涵盖了核心递归逻辑、列表操作辅助函数,以及如何高效地找出最短转换路径,并探讨了获取具体操作序列的方法。
在软件开发中,我们经常需要对数据结构进行各种操作。设想这样一个场景:我们有两个整数列表a和b,它们包含相同的元素,但顺序不同。我们的目标是找出将列表a通过最少次数的特定操作转换成列表b所需的步骤。这里定义了两种核心操作:
问题在于,从列表a到列表b可能存在多种操作序列,我们感兴趣的是其中操作次数最少的那一个。
要找到最小操作数,我们可以将这个问题抽象为一个状态空间搜索问题。每个列表的当前状态都可以看作搜索树中的一个节点。从任何一个节点(即当前列表状态)出发,我们都可以应用reverse或rotate这两种操作,从而产生两个新的子节点(新的列表状态)。我们的目标就是从起始节点(列表a)出发,找到一条到达目标节点(列表b)的最短路径。
这种搜索过程非常适合使用递归的深度优先搜索(DFS)来实现。
立即学习“Java免费学习笔记(深入)”;
我们将定义两个主要的函数:
为了进行列表操作,我们需要实现rotate和reverse方法。同时,为了在递归过程中追踪上一步的操作,我们定义一个枚举类型OP。
import java.util.*;
public class ListTransform {
// 定义操作类型
enum OP {
REV, // 反转
ROT // 旋转
}
/**
* 旋转列表:将最后一个元素移到开头。
* 注意:此方法返回一个新的列表,不修改原始列表。
* @param list 待旋转的列表
* @return 旋转后的新列表
*/
private static List<Integer> rotate(List<Integer> list) {
var newList = new ArrayList<>(list);
// 使用 Collections.rotate(list, distance) 方法,distance 为 1 表示将最后一个元素移到开头
Collections.rotate(newList, 1);
return newList;
}
/**
* 反转列表:将列表中所有元素顺序颠倒。
* 注意:此方法返回一个新的列表,不修改原始列表。
* @param list 待反转的列表
* @return 反转后的新列表
*/
private static List<Integer> reverse(List<Integer> list) {
var newList = new ArrayList<>(list);
Collections.reverse(newList);
return newList;
}
// ... 递归搜索逻辑将在下一节实现
}重要提示: rotate 和 reverse 方法都创建并返回了一个新的列表,而不是直接修改传入的列表。这种“不可变性”在递归搜索中至关重要,它确保了每个递归调用都在一个独立的状态上操作,避免了副作用和状态混乱。
现在,我们来实现核心的递归搜索逻辑minimumOpsRec。
import java.util.*;
public class ListTransform {
enum OP { REV, ROT }
private static List<Integer> rotate(List<Integer> list) { /* ... */ return list; }
private static List<Integer> reverse(List<Integer> list) { /* ... */ return list; }
/**
* 启动列表转换的最小操作数计算。
* @param a 初始列表
* @param b 目标列表
* @return 最小操作数,如果无法转换则返回 Integer.MAX_VALUE
*/
public static int minimumOps(List<Integer> a, List<Integer> b) {
// 如果初始列表和目标列表相同,则无需操作
if (Objects.equals(a, b)) return 0;
// 分别尝试以反转和旋转开始,进行递归搜索
// 初始操作计数为1
int revCount = minimumOpsRec(reverse(a), b, 1, OP.REV);
int rotCount = minimumOpsRec(rotate(a), b, 1, OP.ROT);
// 返回两种起始操作中,操作数较小的那一个
return Math.min(revCount, rotCount);
}
/**
* 递归辅助函数,用于深度优先搜索最小操作数。
* @param currentList 当前列表状态
* @param targetList 目标列表
* @param currentCount 当前已执行的操作数
* @param lastOperation 上一步执行的操作类型,用于剪枝优化
* @return 从 currentList 到 targetList 所需的最小额外操作数,加上 currentCount;
* 如果无法转换或超出深度限制,则返回 Integer.MAX_VALUE。
*/
public static int minimumOpsRec(List<Integer> currentList, List<Integer> targetList, int currentCount, OP lastOperation) {
// 基本情况1:如果当前列表已达到目标列表,返回当前操作数
if (Objects.equals(currentList, targetList)) {
return currentCount;
}
// 基本情况2:设置搜索深度限制。
// 如果操作数超过列表大小的某个倍数(例如,这里的 targetList.size() * 2),
// 认为这条路径过长或无法达到目标,进行剪枝。
// 这个限制是一个启发式,可以防止无限递归,但对于某些复杂情况可能需要调整。
if (currentCount > targetList.size() * 2) { // 简单启发式剪枝
return Integer.MAX_VALUE;
}
// 递归调用,探索两种可能的后续操作
int revResult = Integer.MAX_VALUE;
int rotResult;
// 剪枝优化:如果上一步是反转操作,则避免再次立即反转。
// 因为 reverse(reverse(list)) == list,连续两次反转等同于没有操作,
// 这种路径是冗余且低效的。
if (lastOperation != OP.REV) {
revResult = minimumOpsRec(reverse(currentList), targetList, currentCount + 1, OP.REV);
}
// 总是可以尝试旋转操作
rotResult = minimumOpsRec(rotate(currentList), targetList, currentCount + 1, OP.ROT);
// 返回两个分支中操作数较小的那一个
return Math.min(revResult, rotResult);
}
public static void main(String[] args) {
var a = new ArrayList<>(List.of(1, 2, 3, 4));
var b = new ArrayList<>(List.of(2, 1, 4, 3)); // 目标:rotate(rotate(reverse(S)))
var output = minimumOps(a, b);
if (output == Integer.MAX_VALUE) {
System.out.println("无法在合理步数内转换");
} else {
System.out.println("最小转换次数: " + output); // 预期输出 3
}
var c = new ArrayList<>(List.of(1, 2, 3, 4));
var d = new ArrayList<>(List.of(4, 2, 1, 3)); // 示例:无法通过此算法解决的组合
var output2 = minimumOps(c, d);
if (output2 == Integer.MAX_VALUE) {
System.out.println("列表 " + c + " 无法转换为 " + d + " (或超出深度限制)");
}
}
}入口函数 minimumOps:
递归函数 minimumOpsRec:
上述代码只能返回最小操作数。如果需要获取具体的转换操作序列(例如 [ROT, ROT, REV]),我们需要修改递归函数的返回值类型,使其包含操作列表。
import java.util.*;
class ListTransformWithSequence {
enum OP { REV, ROT }
private static List<Integer> rotate(List<Integer> list) {
var newList = new ArrayList<>(list);
Collections.rotate(newList, 1);
return newList;
}
private static List<Integer> reverse(List<Integer> list) {
var newList = new ArrayList<>(list);
Collections.reverse(newList);
return newList;
}
/**
* 启动列表转换的最小操作数计算,并返回操作序列。
* @param a 初始列表
* @param b 目标列表
* @return 包含最小操作数和操作序列的 Map.Entry。如果无法转换,操作数将是 Integer.MAX_VALUE。
*/
public static Map.Entry<Integer, List<OP>> minimumOps(List<Integer> a, List<Integer> b) {
if (Objects.equals(a, b)) {
return new AbstractMap.SimpleEntry<>(0, new ArrayList<>());
}
// 分别尝试以反转和旋转开始
Map.Entry<Integer, List<OP>> revResult = minimumOpsRec(reverse(a), b, 1, OP.REV);
Map.Entry<Integer, List<OP>> rotResult = minimumOpsRec(rotate(a), b, 1, OP.ROT);
// 比较两个结果,选择操作数更小的那个
if (rotResult.getKey() >= revResult.getKey()) {
// 如果反转路径更短或相等,将当前操作(REV)添加到序列开头
revResult.getValue().add(0, OP.REV);
return revResult;
} else {
// 如果旋转路径更短,将当前操作(ROT)添加到序列开头
rotResult.getValue().add(0, OP.ROT);
return rotResult;
}
}
/**
* 递归辅助函数,用于深度优先搜索最小操作数和操作序列。
* @param currentList 当前列表状态
* @param targetList 目标列表
* @param currentCount 当前已执行的操作数
* @param lastOperation 上一步执行的操作类型
* @return 包含操作数和操作序列的 Map.Entry。
*/
public static Map.Entry<Integer, List<OP>> minimumOpsRec(List<Integer> currentList, List<Integer> targetList, int currentCount, OP lastOperation) {
// 基本情况1:如果当前列表已达到目标列表,返回当前操作数和空序列
if (Objects.equals(currentList, targetList)) {
return new AbstractMap.SimpleEntry<>(currentCount, new ArrayList<>());
}
// 基本情况2:深度限制
if (currentCount > targetList.size() * 2) {
return new AbstractMap.SimpleEntry<>(Integer.MAX_VALUE, new ArrayList<>());
}
Map.Entry<Integer, List<OP>> revBranchResult = null;
Map.Entry<Integer, List<OP>> rotBranchResult;
// 剪枝优化:避免连续两次反转
if (lastOperation != OP.REV) {
revBranchResult = minimumOpsRec(reverse(currentList), targetList, currentCount + 1, OP.REV);
}
rotBranchResult = minimumOpsRec(rotate(currentList), targetList, currentCount + 1, OP.ROT);
// 比较两个分支的结果
if (revBranchResult != null && rotBranchResult.getKey() >= revBranchResult.getKey()) {
// 如果反转分支更优(或相等),将当前操作添加到其序列开头
revBranchResult.getValue().add(0, OP.REV);
return revBranchResult;
} else {
// 如果旋转分支更优,将当前操作添加到其序列开头
rotBranchResult.getValue().add(0, OP.ROT);以上就是Java中列表转换的最小操作数:递归搜索与优化策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号