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

【算法训练营Day22】回溯算法part4

文章目录

  • 非递减子序列
  • 全排列
  • 全排列 II
  • 总结:常见的去重方法
  • 总结:回溯算法

非递减子序列

题目链接:491. 非递减子序列

解题逻辑:

该题是在前文子集 II的基础上加了两个条件和一个变化:

  • 结果至少有两个元素。解决办法:收集结果时对单个结果集的长度进行判断
  • 结果元素非递减。在递归时传入当前元素,这样下一层元素如果比这个元素小则直接跳过。
  • 变化:去重的方法。在子集 II中使用的去重方法只能在数组有序的时候才能用,而在这个题数组并不有序,所以本题中去重要通过hash表。

解题代码如下:

class Solution {List<Integer> item = new ArrayList<>();List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> findSubsequences(int[] nums) {backtracking(nums,0,Integer.MIN_VALUE);return result;}public void backtracking(int[] nums,int startIndex,int lastNum){if(startIndex == nums.length) return;Set<Integer> box = new HashSet<>();for(int i = startIndex;i < nums.length;i++) {if(nums[i] < lastNum || box.contains(nums[i])) continue;item.add(nums[i]);if(item.size() >= 2) result.add(new ArrayList(item));backtracking(nums,i + 1,nums[i]);item.remove(item.size() - 1);box.add(nums[i]);}}
}

全排列

题目链接:46. 全排列

解题思路:

与组合的不同之处在于:

  • 无需偏移量startIndex来规定每层循环的起点
  • 通过set来存放该层循环中已经使用过哪些元素(或者通过一个相同长度的数组来标记原数组中哪些元素被使用过)

解题代码:

class Solution {List<Integer> item = new ArrayList<>();List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {backtracking(nums,new HashSet<Integer>());return result;}public void backtracking(int[] nums,Set<Integer> box){if(item.size() == nums.length) {result.add(new ArrayList(item));return;}for(int i = 0;i < nums.length;i++) {if(box.contains(nums[i])) continue;item.add(nums[i]);box.add(nums[i]);backtracking(nums,box);item.remove(item.size() - 1);box.remove(nums[i]);}}
}

全排列 II

题目链接:47. 全排列 II

这个题与上一题的区别在于本体的数字序列可以包含重复数字,所以最后的排列可能会存在重复。

与上一题解决方法的变化在于:

  • 不能使用set去存储已使用过的元素,而是使用record数组对元素进行标记
  • 去重方法沿用老方法:将数组排序,如果下一个遍历的元素没发生变化的话那么跳过循环

解题逻辑:

class Solution {List<Integer> item = new ArrayList<>();List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> permuteUnique(int[] nums) {Arrays.sort(nums);backtracking(nums,new int[nums.length]);return result;}public void backtracking(int[] nums,int[] record){if(item.size() == nums.length) {result.add(new ArrayList(item));return;}int old = Integer.MIN_VALUE;for(int i = 0;i < nums.length;i++) {if(record[i] != 0 || old == nums[i]) continue;item.add(nums[i]);record[i] = 1;backtracking(nums,record);item.remove(item.size() - 1);record[i] = 0;old = nums[i];}}
}

总结:常见的去重方法

在回溯算法中常用到的两种去重方法:

  • 处理方法1:在数组有序的情况下,如果下一个遍历的元素没发生变化的话那么跳过循环。
  • 处理方式2:在数组无序的情况下,将已遍历的元素添加到set中进行记录,如果下一次循环该元素已经在set中,则跳过循环。

总结:回溯算法

回溯算法能解决如下问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

组合问题的核心就在于循环中引入偏移量startIndex,通过这个偏移量保证了只会向后取数不会折返。而排列问题就不能再使用偏移量,而是使用set(只能适用于无重复数据的数组)或者标记数组来确保每一个数都取了一遍。切割问题本质是组合问题的变种,只不过将递归的取数字变成了递归的切割。至于子集问题也可以当作组合问题的变种,只不过是收集结果的地方发生了变化。

最后要说的是只要是回溯问题,那么只要能将该问题抽象成树形进行思考,那么这一题已经做对了80%

http://www.xdnf.cn/news/1256977.html

相关文章:

  • mysql_mcp_server_pro源码部署及启动报错新手指南:让智能体长出手来直接获取到最底层的数据
  • Webpack 5 高性能配置方案
  • MyBatis-Plus 更新逻辑删除字段(is_delete)无效问题分析与解决方案
  • C#里使用NModbus来读取寄存器的值
  • localforage的数据仓库、实例、storeName和name的概念和区别
  • 杰理-获取系统运行时间 jiffies_msec
  • QT5.15 mingw
  • AI题解5
  • Windows Oracle 11 g dmp数据库恢复笔记
  • java excel转图片常用的几种方法
  • [论文阅读] 软件工程 | 软件工程中的同理心:表现、动机与影响因素解析
  • 微信小程序与后台管理系统开发全流程指南
  • 单链表专题---暴力算法美学(1)(有视频演示)
  • 【性能测试】---测试工具篇(jmeter)
  • 超声波自动气象站如何精准预警极端天气
  • 深入解析 Dash 中的 dcc.Checklist:构建高效多选交互界面
  • 【LeetCode】set和map相关算法题 前K个高频单词、随机链表的复制、两个数组的交集、环形链表
  • 视觉语言模型的空间推理缺陷——AI 在医学扫描中难以区分左右
  • 生成式AI时代,Data+AI下一代数智平台建设指南
  • 8.3.1 注册服务中心Etcd
  • 商城小程序怎么做?如何开发母婴用品商城小程序?
  • [C++20]协程:语义、调度与异步 | Reactor 模式
  • NVIDIA/k8s-device-plugin仓库中GPU无法识别问题的issues分析报告
  • LoRaWAN的网络拓扑
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,绘制新增、编辑模式支持点、线、面的捕捉
  • 【已解决】-bash: mvn: command not found
  • PyTorch LSTM文本生成
  • 专题:2025财务转型与AI赋能数字化报告|附30+份报告PDF汇总下载
  • Casrel关系抽取
  • 【2025最新】在 macOS 上构建 Flutter iOS 应用