回溯和递归
文章目录
- 回溯和递归
- 1-1 树形问题 Letter Combinations of a Phone Number
- 1-2 什么是回溯_排列问题 Permutations
- 1-3 组合问题 Combinations
-
-
-
- [77. 组合](https://leetcode-cn.com/problems/combinations/)
- [39. 组合总和](https://leetcode-cn.com/problems/combination-sum/)
- [40. 组合总和 II](https://leetcode-cn.com/problems/combination-sum-ii/)
- [216. 组合总和 III](https://leetcode-cn.com/problems/combination-sum-iii/)
- [78. 子集](https://leetcode-cn.com/problems/subsets/)
- [90. 子集 II](https://leetcode-cn.com/problems/subsets-ii/)
- [401. 二进制手表](https://leetcode-cn.com/problems/binary-watch/)
-
-
- 1-5 二维平面上的回溯法 Word Search
- 1-6 floodfill算法,一类经典问题 Number of Islands-
- 1-7 回溯法是经典人工智能的基础 N Queens
写这篇博客的目的是为了解决leetcode的39和40题,这道题是一道很经典的面试题.属于中等难度的问题
回溯法是解决很多算法问题的常见思想,甚至可以说是传统人工智能的基础方法。其本质依然是使用递归的方法在树形空间中寻找解。在这一章,我们来具体看一下将递归这种技术使用在非二叉树的结构中,从而认识回溯这一基础算法思想。…
1-1 树形问题 Letter Combinations of a Phone Number
题意介绍
注意:这里是树形问题,不是树的问题
我们先看leetcode17题:17. 电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
分析步骤和思路
如何分析问题?
- 字符串的合法性
- 空字符串
- 多个解顺序
这些要求不是本题中的要求,但是我们在面试中遇到这种题的时候,需要考虑以上情况
这也是我们称这些问题为树形问题的原因,这些树还是满树
递归式的分析
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
/*
* 思路:
* */
private:
const string letterMap[10]={
"", //0
"", //1
"abc", //2
"def", //3
"ghi", // 4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz" //9
};
vector<string> result;
/**
*
* @param digits 数字字符串"23"
* @param index 每一次都处理字符串的一位,每位数字字符串代表的索引
* @param str 之前已经转换的字符串
*/
void findCombination(const string &digits,int index, const string &str){
// 1.0每位字符处理
char c = digits[index];
// 4.0递归终止条件
if (index == digits.size()){
result.push_back(str);
return;
}
// 2.0 找到当前位数字符,可以代表的字符
string letters=letterMap[c-'0'];
// 3.0 我们获得了每位数字代表的字符串例如2,代表"abc"
for(int i=0;i<letters.size();i++)
{
findCombination(digits,index+1,str+letters[i]);
}
return;
}
public:
vector<string> letterCombinations(string digits) {
// 使用之前情况存储总值的数组
result.clear();
if (digits=="")
return result;
findCombination(digits,0,"");
for(int i=0;i<result.size();i++)
{
cout<<result[i]<<" ";
}
return result;
}
};
int main(){
string str="234";
Solution one;
one.letterCombinations(str);
return 0;
}
在递归函数里面观察递归过程
时间复杂度大约是3n=O(2n)
回溯是暴力解法的一个主要实现手段
93. 复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “[email protected]” 是 无效的 IP 地址。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000"
输出:["0.0.0.0"]
示例 3:
输入:s = "1111"
输出:["1.1.1.1"]
示例 4:
输入:s = "010010"
输出:["0.10.0.10","0.100.1.0"]
示例 5:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
提示:
0 <= s.length <= 3000
s 仅由数字组成
131. 分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
1-2 什么是回溯_排列问题 Permutations
我们先根据一道例题进行了解什么是回溯?
回溯方法解决问题,和上面的树形问题的递归解法,有一些区别,就是选过的目标数组,不能出现重复,所以我们需要添加一个布尔数组放置重复放入
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
//
// Created by jiayadong on 2020/11/14.
//
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
private:
vector<vector<int>> result; //我们将所有的结果放入
vector<bool> used; //辅助数组
/**
*
* @param nums 当前数组
* @param index 此时第index的元素
* @param p 此时第index+1元素,获得一个index+1个元素的排列
*/
void generation(const vector<int>& nums,int index,vector<int>& p)
{
cout<<"index: "<<index<<endl;
// 1.先书写递归的终止条件
if (index == nums.size()){
// 6.满足一次结果的数组放入
cout<<"nums.size(): -------------->"<<index<<endl;
result.push_back(p);
return;
}
// 2.我们开始进行循环
for(int i=0;i<nums.size();i++)
{
// 3.先判断当前位置的数组是否已经使用过了
if (!used[i]){
// 4.将nums[i] 添加到当前数组里面
p.push_back(nums[i]);
used[i] = true;
// 5. 比较经典的是,回溯到上一步的时候,我们把这个数字恢复到,可选择的
cout<<"now number:"<<nums[i]<<endl;
generation(nums, index+1, p);
p.pop_back();
used[i] = false;
}
}
return;
}
public:
vector<vector<int>> permute(vector<int>& nums) {
// 初始一下
result.clear();
if (nums.size() == 0) return result;
vector<int> p;
// 初始化这个判断数组
used=vector<bool>(nums.size() , false);
generation(nums , 0, p);
return result;
}
};
int main(){
vector<int> nums{
1,2,3};
Solution one;
vector<vector<int>> result= one.permute(nums);
for(int i=0;i<result.size();i++)
{
for(int j=0;j<result[i].size();j++)
{
cout<<result[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
47. 全排列 II
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
1-3 组合问题 Combinations
77. 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
//
// Created by jiayadong on 2020/11/14.
//
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
private:
vector<vector<int>> res;
void generation(int n, int k, int start, vector<int> &c )
{
if (c.size()==k){
res.push_back(c);
return;
}
for(int i=start;i<=n;i++)
{
c.push_back(i);
generation(n,k,i+1,c);
c.pop_back();
}
return;
}
public:
vector<vector<int>> combine(int n, int k) {
// 在使用之前先进行清空
res.clear();
//这里我们先考虑,不合法的参数
if (n<=0 ||k<=0 || k>n) return res;
vector<int> c;
generation(n,k,1,c);
return res;
}
};
int main(){
return 0;
}
优化回溯和剪枝思想的使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dyCAdgis-1605352322975)(E:\计算机学习\pat汇总\leetcode汇总\回溯和递归.assets\image-20201114175459753.png)]
for(int i=start;i<=n-(k-c.size())+1; i++)
{
c.push_back(i);
generation(n,k,i+1,c);
c.pop_back();
}
return;
只需要改一下i的界限就可以了
39. 组合总和
给定一个无重复元素的数组 candidates
和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都是独一无二的。
1 <= target <= 500
40. 组合总和 II
给定一个数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
78. 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
90. 子集 II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
401. 二进制手表
二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。
每个 LED 代表一个 0 或 1,最低位在右侧。
例如,上面的二进制手表读取 “3:25”。
给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。
示例:
输入: n = 1
返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]
提示:
- 输出的顺序没有要求。
- 小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。
- 分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。
- 超过表示范围(小时 0-11,分钟 0-59)的数据将会被舍弃,也就是说不会出现 “13:00”, “0:61” 等时间。