首页 > 编程语言 >一文搞定回溯算法

一文搞定回溯算法

时间:2024-11-10 21:20:09浏览次数:3  
标签:搞定 target nums int List results 算法 回溯 new

回溯算法

基础入门

“尝试”与“回退”

剪枝

全排列问题 

子集和问题

 n 皇后问题

 相关题解

leetcode104.二叉树的最大深度

 法一:深度优先遍历

法二:广度优先遍历

leetcode113.路径总和Ⅱ

法一:深度优先遍历

leetcode46.全排列

法一:回溯

 leetcode47.全排列Ⅱ

法一:回溯 

leetcode39组合总和

法一:回溯 

leetcode40.组合总和Ⅱ

 法一:回溯

leetcode79.单词搜索

法一:回溯 


基础入门

给定一棵二叉树,搜索并记录所有值为 7 的节点,请返回节点列表。
/* 前序遍历:例题一 */
void preOrderTraversal(TreeNode node) {
    if (node != null) {
        if (node.val == 7) {
            // 记录解
            res.add(node);
        }
        preOrderTraversal(node.left);
        preOrderTraversal(node.right);
    }
}

“尝试”与“回退”

在二叉树中搜索所有值为 7 的节点, 请返回根节点到这些节点的路径
/* 前序遍历:例题二 */
void preOrderTraversal(TreeNode node) {
    if (node == null) {
        return;
    }
    // 尝试
    path.add(node);
    if (node.val == 7) {
        // 记录解
        res.add(new ArrayList<>(path));
    }
    preOrderTraversal(node.left);
    preOrderTraversal(node.right);
    // 回退
    path.remove(path.size() - 1);
}
在每次“尝试”中,通过将当前节点添加进 path 来记录路径;而在“回退”前,我们需要将该节点从 path 中弹出, 以恢复本次尝试之前的状态

剪枝

在二叉树中搜索所有值为 7 的节点,请返回根节点到这些节点的路径, 并要求路径中不包含值为 3 节点
/* 前序遍历:例题三 */
void preOrderTraversal(TreeNode node) {
    // 剪枝
    if (node == null || node.val == 3) {
        return;
    }
    // 尝试
    path.add(node);
    if (node.val == 7) {
        // 记录解
        res.add(new ArrayList<>(path));
    }
    preOrderTraversal(node.left);
    preOrderTraversal(node.right);
    // 回退
    path.remove(path.size() - 1);
}

 剪枝:遇到值为3的节点则提前返回

 

完整代码:

/* 判断当前状态是否为解 */
boolean isSolution(List<TreeNode> currentState) {
    return !currentState.isEmpty() && currentState.get(currentState.size() - 1).val == 7;
}

/* 记录解 */
void recordSolution(List<TreeNode> currentState, List<List<TreeNode>> results) {
    results.add(new ArrayList<>(currentState));
}

/* 判断在当前状态下,该选择是否合法 */
boolean isValid(List<TreeNode> currentState, TreeNode option) {
    return option != null && option.val != 3;
}

/* 更新状态 */
void makeChoice(List<TreeNode> currentState, TreeNode option) {
    currentState.add(option);
}

/* 恢复状态 */
void undoChoice(List<TreeNode> currentState) {
    currentState.remove(currentState.size() - 1);
}

/* 回溯算法:例题三 */
void backtrack(List<TreeNode> currentState, List<TreeNode> availableChoices, List<List<TreeNode>> results) {
    // 检查是否为解
    if (isSolution(currentState)) {
        // 记录解
        recordSolution(currentState, results);
    }
    // 遍历所有选择
    for (TreeNode option : availableChoices) {
        // 剪枝:检查选择是否合法
        if (isValid(currentState, option)) {
            // 尝试:做出选择,更新状态
            makeChoice(currentState, option);
            // 进行下一轮选择
            backtrack(currentState, Arrays.asList(option.left, option.right), results);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(currentState);
        }
    }
}

保留 return记录解后返回,不再继续搜索

 删除 return记录解后不返回,继续搜索

全排列问题 

输入一个整数数组,其中不包含重复元素,返回所有可能的排列。

/* 回溯算法:全排列 I */
void backtrack(List<Integer> currentState, int[] options, boolean[] used, List<List<Integer>> results) {
    // 当状态长度等于元素数量时,记录解
    if (currentState.size() == options.length) {
        results.add(new ArrayList<>(currentState));
        return;
    }
    // 遍历所有选择
    for (int i = 0; i < options.length; i++) {
        int option = options[i];
        // 剪枝:不允许重复选择元素
        if (!used[i]) {
            // 尝试:做出选择,更新状态
            used[i] = true;
            currentState.add(option);
            // 进行下一轮选择
            backtrack(currentState, options, used, results);
            // 回退:撤销选择,恢复到之前的状态
            used[i] = false;
            currentState.remove(currentState.size() - 1);
        }
    }
}

/* 全排列 I */
List<List<Integer>> permutationsI(int[] nums) {
    List<List<Integer>> results = new ArrayList<>();
    backtrack(new ArrayList<>(), nums, new boolean[nums.length], results);
    return results;
}

 输入一个整数数组,数组中可能包含重复元素,返回所有不重复的排列。

 

/* 回溯算法:全排列 II */
void backtrack(List<Integer> currentState, int[] options, boolean[] used, List<List<Integer>> results) {
    // 当状态长度等于元素数量时,记录解
    if (currentState.size() == options.length) {
        results.add(new ArrayList<>(currentState));
        return;
    }
    // 遍历所有选择
    Set<Integer> seen = new HashSet<>();
    for (int i = 0; i < options.length; i++) {
        int option = options[i];
        // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素
        if (!used[i] && !seen.contains(option)) {
            // 尝试:做出选择,更新状态
            seen.add(option); // 记录选择过的元素值
            used[i] = true;
            currentState.add(option);
            // 进行下一轮选择
            backtrack(currentState, options, used, results);
            // 回退:撤销选择,恢复到之前的状态
            used[i] = false;
            currentState.remove(currentState.size() - 1);
        }
    }
}

/* 全排列 II */
List<List<Integer>> permutationsII(int[] nums) {
    List<List<Integer>> results = new ArrayList<>();
    backtrack(new ArrayList<>(), nums, new boolean[nums.length], results);
    return results;
}

子集和问题

给定一个正整数数组 nums 和一个目标正整数 target ,请找出所有可能的组合,使得组合中的元素和等于 target 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。
/* 回溯算法:子集和 I */
void backtrack(List<Integer> currentSubset, int target, int[] options, int start, List<List<Integer>> results) {
    // 子集和等于 target 时,记录解
    if (target == 0) {
        results.add(new ArrayList<>(currentSubset));
        return;
    }
    // 遍历所有选择
    // 剪枝二:从 start 开始遍历,避免生成重复子集
    for (int i = start; i < options.length; i++) {
        // 剪枝一:若子集和超过 target ,则直接结束循环
        // 这是因为数组已排序,后边元素更大,子集和一定超过 target
        if (target - options[i] < 0) {
            break;
        }
        // 尝试:做出选择,更新 target 和 start
        currentSubset.add(options[i]);
        // 进行下一轮选择
        backtrack(currentSubset, target - options[i], options, i, results);
        // 回退:撤销选择,恢复到之前的状态
        currentSubset.remove(currentSubset.size() - 1);
    }
}

/* 求解子集和 I */
List<List<Integer>> subsetSumI(int[] nums, int target) {
    List<Integer> currentSubset = new ArrayList<>(); // 状态(子集)
    Arrays.sort(nums); // 对 nums 进行排序
    int start = 0; // 遍历起始点
    List<List<Integer>> results = new ArrayList<>(); // 结果列表(子集列表)
    backtrack(currentSubset, target, nums, start, results);
    return results;
}
给定一个正整数数组 nums 和一个目标正整数 target ,请找出所有可能的组合,使得组合中的元素和等于 target 。 给定数组可能包含重复元素,每个元素只可被选择一次 。请以列表形式返回这些组合,列表中不应包含重复组合。
/* 回溯算法:子集和 II */
void backtrack(List<Integer> currentSubset, int target, int[] options, int start, List<List<Integer>> results) {
    // 子集和等于 target 时,记录解
    if (target == 0) {
        results.add(new ArrayList<>(currentSubset));
        return;
    }
    // 遍历所有选择
    // 剪枝二:从 start 开始遍历,避免生成重复子集
    // 剪枝三:从 start 开始遍历,避免重复选择同一元素
    for (int i = start; i < options.length; i++) {
        // 剪枝一:若子集和超过 target ,则直接结束循环
        // 这是因为数组已排序,后边元素更大,子集和一定超过 target
        if (target - options[i] < 0) {
            break;
        }
        // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过
        if (i > start && options[i] == options[i - 1]) {
            continue;
        }
        // 尝试:做出选择,更新 target 和 start
        currentSubset.add(options[i]);
        // 进行下一轮选择
        backtrack(currentSubset, target - options[i], options, i + 1, results);
        // 回退:撤销选择,恢复到之前的状态
        currentSubset.remove(currentSubset.size() - 1);
    }
}

/* 求解子集和 II */
List<List<Integer>> subsetSumII(int[] nums, int target) {
    List<Integer> currentSubset = new ArrayList<>(); // 状态(子集)
    Arrays.sort(nums); // 对 nums 进行排序
    int start = 0; // 遍历起始点
    List<List<Integer>> results = new ArrayList<>(); // 结果列表(子集列表)
    backtrack(currentSubset, target, nums, start, results);
    return results;
}

 n 皇后问题

根据国际象棋的规则,皇后可以攻击与同处一行、一列或一条斜线上的棋子。给定

标签:搞定,target,nums,int,List,results,算法,回溯,new
From: https://blog.csdn.net/2403_85375987/article/details/143666335

相关文章

  • c++ 回溯算法
    概念回溯算法(Backtracking)是一种用于寻找所有可能解的算法。它通过递归构建解,并在发现当前解不符合条件时进行“回溯”撤销部分选择,直到找到有效的解或没有更多可能性时停止。回溯算法常用于求解组合、排列、子集、图的遍历等问题。基本思想选择:在某个阶段做出一个选择。......
  • 【优化参数】粒子群算法PSO求解三轴稳定航天器姿态控制PD参数优化问题【含Matlab源码
    ......
  • 【优化求解】蚁群算法ACO求解经济损失的航班延误恢复优化问题(目标函数:航班延误成本最
    ......
  • 哈希算法(开散列)- 支持string(this指针指向的理解)
    一.开散列的定义闭散列(开放地址法)的缺点是线性探测和二次探测都会存在哈希冲突的问题,数据越多冲突就会越明显,导致查询数据的时间复杂度大幅度提升个人思路:创建一个指针数组,当某个位置要插入一个数据,就再创建一个数组,指针数组对应位置的指针指向此数组的首元素(数组地址),......
  • (5)---【DDA画线算法】C语言-OpenGL库-计算机图形学
    本次实验项目         DDA画线算法理解与运用。算法介绍        DDA(DigitalDifferentialAnalyzer)画线算法是一种基于数值微分原理的直线生成算法。它主要用于在光栅系统中绘制直线,即在像素点阵中生成直线。DDA算法的核心思想是从一个端点开始,通过增量,逐......
  • 课程讲解--深入探究二分算法
     一、二分查找算法的基本概念定义与原理二分查找,也被称为折半查找,是一种在有序数据集合中查找特定元素的高效算法。其原理基于分治思想,每次查找都将查找区间缩小一半。例如,在一个有序数组中查找一个特定的数字,我们先比较数组中间位置的元素与目标元素的大小。如果中间元素......
  • 随机森林算法
    随机森林1.Bagging框架1.1算法引入Baggging框架通过有放回的抽样产生不同的训练集,从而训练具有差异性的弱学习器,然后通过平权投票、多数表决的方式决定预测结果。例子:目标:把下面的圈和方块进行分类采样不同数据集2)训练分类器3)平权投票,获取最终结果4)主......
  • Python实现SSA智能麻雀搜索算法优化BP神经网络回归模型(优化权重和阈值)项目实战
    说明:这是一个机器学习实战项目(附带数据+代码+文档+视频讲解),如需数据+代码+文档+视频讲解可以直接到文章最后关注获取。1.项目背景随着人工智能技术的发展,机器学习算法在各个领域的应用越来越广泛。其中,神经网络作为一类重要的机器学习方法,在模式识别、图像处理、自然语言处......
  • 每周算法2:数学+模拟+哈希表+栈+线性dp+贪心(简单)
    目录1.统计数字描述输入描述:输出描述: 题解2.两个数组的交集(哈希表)描述题解 3.点击消除(栈)描述输入描述:输出描述: 题解4.牛牛的快递(模拟+补充)描述输入描述:输出描述:题解 5.最小花费爬楼梯(简单线性dp)描述输入描述:输出描述:示例1题解6.数组中两......
  • Bayes-CNN-BiGRU-Att贝叶斯算法-卷机网络-双向门控循环单元-注意力机制多特分类预测 M
    %*****************************************************************************************************************************************************************************************************************%%清空环境变量warningoff%关闭报警......