我们先来看看字符串的子串和子序列有什么区别?
-
字符串的子串:必须是连续的一段
-
字符串的子序列:可以不连续,但是相对次序不能乱(每个字符 要跟不要 所有的路都走一遍)——深度优先遍历
子序列
打印出字符串的全部子序列
首先明确,子序列!=子串
最长公共子串要求在原字符串中是连续的,而子序列只需要保持相对顺序一致,并不要求连续。
例:“abc”
从位置0开始,有两种决策1、要;2、不要
向后走,每个位置同样两种决策,递归
递归结束就是走到了字符串最后
#include <string>
#include <vector>
#include <iostream>
using namespace std;
class Solution{
std::vector<std::string> ans;
/**
* 当前来到了str[index]字符
* str[0..index-1]已经走过了,之前的选择都在path上,之前的选择已经不能改变了,就是path。
* 但是str[index....]还能自由选择,把str[index....]所有生成的子序列,放入到answer里
*
* @param str 指定的字符串(固定)
* @param index 当前所处的位置
* @param ans 之前决策依据产生的答案
* @param path 之前已经做的选择
*/
void helper(const std::string &str, int index, std::string path){
// 当前来到了字符串的最后位置,已经不能再做决策了,answer只能放入之前的决策
if(index == str.size()){
//std::cout << path << "\n";
ans.push_back(path);
return;
}
helper(str, index + 1, path); // 没有要index 位置的字符
helper(str, index + 1, path + str[index]); // 要了index 位置的字符
}
public:
std::vector<std::string> getAllSubSequences(std::string src){
std::string path;
helper(src, 0,path);
return ans;
}
};
int main(){
Solution a;
a.getAllSubSequences("abc");
}
其递归树如下:
打印出字符串的全部子序列,没有重复值
set可以去重
class Solution{
std::set<std::string> ans;
/**
* 当前来到了str[index]字符
* str[0..index-1]已经走过了,之前的选择都在path上,之前的选择已经不能改变了,就是path。
* 但是str[index....]还能自由选择,把str[index....]所有生成的子序列,放入到answer里
*
* @param str 指定的字符串(固定)
* @param index 当前所处的位置
* @param ans 之前决策依据产生的答案
* @param path 之前已经做的选择
*/
void helper(const std::string &str, int index, std::string path){
// 当前来到了字符串的最后位置,已经不能再做决策了,answer只能放入之前的决策
if(index == str.size()){
//std::cout << path << "\n";
ans.emplace(path);
return;
}
helper(str, index + 1, path); // 没有要index 位置的字符
helper(str, index + 1, path + str[index]); // 要了index 位置的字符
}
public:
std::set<std::string> getAllSubSequences(std::string src){
std::string path;
helper(src, 0,path);
return ans;
}
};
子串
打印出字符串的全部子串
迭代
#include <string>
#include <vector>
using namespace std;
void helper(std::string str, std::vector<std::string> & ans){
for(int i = 0; i < str.size(); i++){
std::string tmp;
for(int j = i; j < str.size(); j++){
tmp += str[j];
ans.push_back(tmp);
}
}
}
int main(){
std::vector<std::string> ans;
helper("abcd", ans);
for(std::string &i : ans){
printf("%s\t", i.c_str());
}
}
全排列
打印一个字符串的全部全排列
所谓的全排列,指的是所有字符都要,只是顺序不同。
采用舍弃添加的方式
#include <string>
#include <vector>
using namespace std;
void process(std::vector<char> rest, std::string path, std::vector<std::string> &ans){
if(rest.empty()){
ans.emplace_back(path);
}else{
int N = rest.size();
for (int i = 0; i < N; ++i) {
char cur = rest[i];
rest.erase(rest.begin() + i);
process(rest, path + cur, ans);
rest.insert(rest.begin() + i, cur);
}
}
}
std::vector<std::string> permutation(std::string s){
std::vector<std::string> ans;
if(s.empty()){
return ans;
}
std::vector<char> rest;
for(char ch : s){
rest.emplace_back(ch);
}
std::string path;
process(rest, path, ans);
return ans;
}
int main(){
std::vector<std::string> ans;
ans = permutation("abc");
for(std::string &i : ans){
printf("%s\t", i.c_str());
}
}
在第一大列选择a时,形成了一些结果,当我进行第二大列递归时,得把a添加回去,保持最开始的abc,在第二大列选择b时进行递归得到的结果才是正确的,对于每一列的每一位选择都是如此,递归结束后要恢复现场。递归结束后要恢复现场。
一直在原始字符串上以交换的方式进行递归
在第一大列形成结果acb时,如果不恢复现场,当我进行第二大列递归时,是从acb开始进行0、1位交换形成cab,和后面的重复了,所以每一步交换递归结束后要恢复现场。
#include <string>
#include <vector>
using namespace std;
/**
* 递归获取全排列
*
* @param str 当前经历过交换后的字符
* @param index 当前交换到哪个位置了
* @param answer 结果
*/
void process(std::string str, int idx, std::vector<std::string> &ans){
// 当前来到了字符串的最后位置,已经不能再交换了,answer只能放入之前交换后的字符
if(idx == str.size()){
ans.emplace_back(str);
}else{
// index之前的已经交换过不能再变了,所以从index往后还可以再交换
for (int i = idx; i < str.size(); ++i) {
std::swap(str[idx], str[i]);
process(str, idx + 1, ans);
std::swap(str[idx], str[i]);
}
}
}
std::vector<std::string> permutation(std::string s){
std::vector<std::string> ans;
if(s.empty()){
return ans;
}
process(s, 0, ans);
return ans;
}
打印一个字符串的全部全排列,没有重复值
#include <string>
#include <vector>
using namespace std;
void process(std::string str, int idx, std::vector<std::string> &ans){
if(idx == str.size()){
ans.emplace_back(str);
return;
}
std::vector<bool> visited(256);
// index之前的已经交换过不能再变了,所以从index往后还可以再交换
for (int i = idx; i < str.size(); ++i) {
// str[i]位置对应字符没有出现过才递归交换,否则忽略
if(!visited[str[i]]){
visited[str[i]] = true;
std::swap(str[idx], str[i]);
process(str, idx + 1, ans);
std::swap(str[idx], str[i]);
}
}
}
std::vector<std::string> permutation(std::string s){
std::vector<std::string> ans;
if(s.empty()){
return ans;
}
process(s, 0, ans);
return ans;
}
因为采用Set去重,程序会重复的做很多相同字符的递归操作,将产生的相同字符串放到Set中由Set去重;而采用以上方式,对于相同的字符就不会再重复的递归了,有效减少了重复分支的递归操作,俗称剪枝。
相当于Set是从结果中过滤去重,而以上方式是在中途的过程就已经去重了。