当前位置: 首页 > 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.dtcms.com/a/319983.html

相关文章:

  • Linux中进程地址空间
  • Godot ------ 中级人物血条制作01
  • 【LLM】扩散模型与自回归模型:文本生成的未来对决
  • GPT-5今夜亮相?OpenAI神秘直播预告,暗示新模型将至
  • 无人机未来的通信脉络:深度解析远距离无线通信模块的革新
  • 【源码】AndroidPlayer
  • 为何毫米波需要采用不同的DPD方法?如何量化其值?
  • pma_init reset_pb
  • 服务器Docker安装教程
  • openGauss3.10企业版单机部署(openEuler20.03 SP3)
  • 嵌入式学习硬件(一)ARM体系架构
  • 【数字图像处理系列笔记】Ch05:傅里叶变换与频率域滤波
  • 哈勃网络计划大规模升级卫星以创建全球蓝牙层
  • AI代码审查大文档处理技术实践
  • mysql如何实现备份某个数据库并还原备份
  • RHCA - CL260 | Day04:对象存储、存储池、用户认证
  • Mysql自定义顺序查询
  • 预约时间组件
  • 能源材料顶刊AEM:钛酸锂“双模储能”设计范式——兼得高能量与高功率密度
  • 智慧能源设备巡检准确率↑32%:陌讯多模态融合算法实战解析
  • Redis模块-RedisJson
  • Q-Learning详解:从理论到实践的全面解析
  • 数据合规——解读跨境电商隐私合规白皮书(2024)【附全文阅读】
  • React(三):脚手架、组件化、生命周期、父子组件通信、插槽、Context
  • AR技术:制造业质量控制的“智能革新”
  • 第五十三篇:LLaMA.cpp的“量化秘籍”:Q4_K_M、Q5_K_S深度解析
  • Numpy科学计算与数据分析:Numpy数组索引与切片入门
  • Linux学习记录 DNS
  • “认知裂缝边缘”地带
  • 河南萌新联赛2025第(四)场:河南大学