当前位置: 首页 > 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.lryc.cn/news/613116.html

相关文章:

  • Pytest项目_day07(pytest)
  • npm 与 npx 区别详解。以及mcp中npx加载原理。
  • 《深入理解Java字符串:从基础到高级特性》
  • 贪心+矩阵算法
  • 与页面共舞 —— Content Scripts 的魔法
  • 面向对象之类、继承和多态
  • leafletMap封装使用
  • 动手学深度学习13.11. 全卷积网络 -笔记练习(PyTorch)
  • Linux 中断系统全览解析:从硬件到软件的全路线理解
  • 外部排序总结(考研向)
  • MongoDB数据存储界的瑞士军刀:cpolar内网穿透实验室第513号挑战
  • 数据结构:双向链表(Doubly Linked List)
  • 生成对抗网络(GAN)实战 - 创建逼真人脸图像
  • 电路相量法
  • (易视宝)易视TV is-E4-G-全志A20芯片-安卓4-烧写卡刷工具及教程
  • C++的“模板”
  • day069-Jenkins基础使用与参数化构建
  • golang的面向对象编程,struct的使用
  • 急危重症专科智能体”构建新一代急诊、手术与重症中心的AI医疗方向探析
  • 【深度学习机器学习】构建情绪对话模型:从数据到部署的完整实践
  • 小鸡模拟器安卓版:经典街机游戏的移动体验
  • Elcomsoft Wireless Security Auditor 安装教程-安全检测工具使用指南
  • 数据结构----栈和队列认识
  • 深度学习-卷积神经网络CNN-1×1卷积层
  • 仓库管理系统-21-前端之入库和出库管理
  • Windows中安装rustup-init.exe以及cargo build报错443
  • 零基础-动手学深度学习-9.3. 深度循环神经网络
  • 流程图使用规范
  • Android 之 面试八股文
  • GCC与NLP实战:编译技术赋能自然语言处理