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

Day22--回溯--77. 组合,216. 组合总和 III,17. 电话号码的字母组合

Day22–回溯–77. 组合,216. 组合总和 III,17. 电话号码的字母组合

回溯理论知识

《代码随想录》:

  • 什么是回溯法?
    • 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
    • 回溯是递归的副产品,只要有递归就会有回溯。
    • 所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数。
  • 回溯法的效率?
    • 回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法
    • 因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
  • 那么既然回溯法并不高效为什么还要用它呢?
    • 因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。
  • 回溯法,一般可以解决如下几种问题:
    • 组合问题:N个数里面按一定规则找出k个数的集合
    • 切割问题:一个字符串按一定规则有几种切割方式
    • 子集问题:一个N个数的集合里有多少符合条件的子集
    • 排列问题:N个数按一定规则全排列,有几种排列方式
    • 棋盘问题:N皇后,解数独等等
  • 如何理解回溯法?
    • 回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
    • 因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度
    • 递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

摘抄:大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

// 回溯算法模板框架
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}

77. 组合

思路:

回溯法backtracking。时间复杂度: O(n * 2^n)

// 递归法,使用startIndex作为重要递归参数
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return res;}private void backtracking(int n, int k, int startIndex) {// 当够k个了,就加到resif (path.size() == k) {res.add(new ArrayList(path));return;}for (int i = startIndex; i <= n; i++) {// path加入本层元素path.add(i);// 下一层从i+1开始backtracking(n, k, i + 1);path.remove(path.size() - 1);}}
}

思路:
剪枝优化

// 递归法,使用startIndex作为重要递归参数
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return res;}private void backtracking(int n, int k, int startIndex) {// 剪枝;剩余的元素(n - startIndex + 1)加上已有的元素path.size()都不够k个了,就返回if ((n - startIndex + 1) + path.size() < k) {return;}// 当够k个了,就加到resif (path.size() == k) {res.add(new ArrayList(path));return;}for (int i = startIndex; i <= n; i++) {// path加入本层元素path.add(i);// 下一层从i+1开始backtracking(n, k, i + 1);path.remove(path.size() - 1);}}
}

216. 组合总和 III

题目翻译:k个数相加等于n,注意数字只能1->9不是1->n

思路:

递归法。使用startIndex和target作为重要递归参数

  • 写在递归函数中的,+1或者-1,就是自带“回溯”。
  • 先手动+1,递归后再手动-1的效果是一样的
// 递归法。使用startIndex和target作为重要递归参数
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {backtracking(k, 1, n);return res;}private void backtracking(int k, int startIndex, int target) {// 剪枝,不然会超时。// 当target<0,证明和过大了,当path.size()超过了k个,证明和过小了(需要的数字过多)if (target < 0 || path.size() > k) {return;}// 个数够了 且 target==0if (path.size() == k && target == 0) {res.add(new ArrayList(path));}// 注意这里是小于9不是小于nfor (int i = startIndex; i <= 9; i++) {path.add(i);// 下一层遍历从i+1开始,同时,仍需要的target,要减去本层已有的i// 写在递归函数中的,+1/-1,就是自带“回溯”。// 先手动+1,递归后再手动-1的效果是一样的backtracking(k, i + 1, target - i);path.remove(path.size() - 1);}}
}

17. 电话号码的字母组合

思路:

组合问题。就是要将按键转为字符数组。比如按键”23“,就是['a','b','c']和['d','e','f']的组合问题。

class Solution {// 结果集List<String> res = new ArrayList<>();// 路径上的元素集StringBuilder path = new StringBuilder();public List<String> letterCombinations(String digits) {backtracking(digits, 0);return res;}private void backtracking(String digits, int index) {// 如果index=n,证明越界了,已经遍历完了if (index == digits.length()) {return;}// 获取当前循环的按键所拥有的字母char[] ch = getChars(digits.charAt(index));// 遍历这个按键的字母// 如果遍历到了最后一个按键,要做收尾工作,把path转为字符串记录到resif (index == digits.length() - 1) {for (int i = 0; i < ch.length; i++) {path.append(ch[i]);res.add(new String(path));path.deleteCharAt(path.length() - 1);}} else {// 如果不是最后一个按键,递归遍历下一个按键(index+1)for (int i = 0; i < ch.length; i++) {path.append(ch[i]);backtracking(digits, index + 1);// 记得path要做回溯操作path.deleteCharAt(path.length() - 1);}}}// 获取这个按键所拥有的字母private char[] getChars(char number) {switch (number) {case '2': {return new char[] { 'a', 'b', 'c' };}case '3': {return new char[] { 'd', 'e', 'f' };}case '4': {return new char[] { 'g', 'h', 'i' };}case '5': {return new char[] { 'j', 'k', 'l' };}case '6': {return new char[] { 'm', 'n', 'o' };}case '7': {return new char[] { 'p', 'q', 'r', 's' };}case '8': {return new char[] { 't', 'u', 'v' };}case '9': {return new char[] { 'w', 'x', 'y', 'z' };}}return null;}
}

其中,获取这个按键所拥有的字母可以做优化。遍历到了最底层,拼接的时机可以优化。

class Solution {// 结果集List<String> res = new ArrayList<>();// 路径上的元素集StringBuilder path = new StringBuilder();// 按键所拥有的字母,其中"0","1"是空串String[] numString = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) {return res;}backtracking(digits, 0);return res;}private void backtracking(String digits, int index) {// 如果index=n,证明越界了,已经遍历完了if (index == digits.length()) {// 要做收尾工作,把path转为字符串记录到resres.add(new String(path));return;}// 获取当前循环的按键所拥有的字母String letters = numString[digits.charAt(index) - '0'];// 遍历这个按键的字母for (int i = 0; i < letters.length(); i++) {path.append(letters.charAt(i));// 如果不是最后一个按键,递归遍历下一个按键(index+1)backtracking(digits, index + 1);// 记得path要做回溯操作path.deleteCharAt(path.length() - 1);}}}
http://www.dtcms.com/a/313551.html

相关文章:

  • 深入剖析Java拦截器:从原理到实战
  • Python3 中使用zipfile进行文件(夹)的压缩、解压缩
  • 一加Ace5无法连接ColorOS助手解决(安卓设备ADB模式无法连接)
  • 跟我学C++中级篇——常函数
  • javaweb开发之Servlet笔记
  • vulhub ELECTRICAL靶场攻略
  • 【Reading Notes】(8.4)Favorite Articles from 2025 April
  • Back to the Features:附录B
  • 控制建模matlab练习08:根轨迹
  • 常⻅框架漏洞
  • 电力电子技术知识总结-----PWM知识点
  • 基于Spring Data JPA与Redis二级缓存集成实战指南
  • C语言基础12——结构体2
  • vulhub-ELECTRICAL靶机
  • Python Pandas.factorize函数解析与实战教程
  • 验房收房怎么避免被坑?
  • elk快速部署、集成、调优
  • [CISCN 2023 初赛]go_session
  • 第十章:如何真正使用Java操作redis
  • VUE-第二季-01
  • Day 30:模块和库的导入
  • Git 常用命令指南:从入门到高效开发
  • 数据结构之链表
  • sublime text2配置
  • 设备维护计划制定指南:基于数据驱动的全流程技术实现
  • 接口测试用例的编写
  • solidworks打开step报【警告!可用的窗口资源极低】的解决方法
  • Kubernetes中ingress,egress,slb等概念的含义
  • 电路设计(电容)设计细节
  • 【华为OD机试】从小桶里取球