一、字符串的全部子序列
描述
给定一个字符串s,长度为n,求s的所有子序列
1.子序列: 指一个字符串删掉部分字符(也可以不删)形成的字符串,可以是不连续的,比如"abcde"的子序列可以有"ace","ad"等等
2.将所有的子序列的结果返回为一个字符串数组
3.字符串里面可能有重复字符,但是返回的子序列不能有重复的子序列,比如"aab"的子序列只有"","a","aa","aab","ab","b",不能存在2个相同的"ab"
4.返回字符串数组里面的顺序可以不唯一
数据范围:
0<=s.length<=160<=s.length<=16
要求:时间复杂度为O(2n)O(2n)
思路:
递归,大体流程:
basecase:
- 遍历完整个string,将path加入到答案中
recur:
- 选择不将当前字符加入到path中
- 选择将当前字符加入到path中,进入递归后消除影响(path.pop_back)
代码:
#include <string>
#include <unordered_set>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return string字符串vector
*/
unordered_set<string> set;
void f(string s,int i,string& path){
int len=s.size();
if(i==len){
set.insert(path);
return ;
}
f(s,i+1,path);
path+=s[i];
f(s,i+1,path);
path.pop_back();
}
vector<string> generatePermutation(string s) {
// write code here
set.clear();
string path;
f(s,0,path);
vector<string> vec(set.begin(),set.end());
return vec;
}
};
优化思路:
优化path,用char*模拟string。大体流程:
basecase:
- 遍历完整个string,将范围内的path加入到答案中
recur:
- 选择不将当前字符加入到path中
- 选择将当前字符加入到path中,进入递归后消除影响(利用_size覆盖掉已经插入的)
优化代码:
#include <cstdio>
#include <string>
#include <unordered_map>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return string字符串vector
*/
unordered_set<string> set;
void f(string s,int i,char* path,int _size){
int len=s.size();
if(i==len){
string str(path,path+_size);
set.insert(str);
return ;
}
f(s,i+1,path,_size);
path[_size]=s[i];
f(s,i+1,path,_size+1);
}
vector<string> generatePermutation(string s) {
// write code here
set.clear();
char path[17]={0};
f(s,0,path,0);
vector<string> vec(set.begin(),set.end());
return vec;
}
};
二、子集 II
给你一个整数数组
nums
,其中可能包含重复元素,请你返回该数组所有可能的子集
(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
思路:
分析该题与第一题的区别,第一题字符间的相对顺序不能改变,而该题中子集内部数字的相对顺序可以改变,所以通过排序nums数组可以达到提升效率的效果。
如何利用有序性?
- 可以根据加入多少个相同的元素向下递归,而不是每次根据是否选定该元素向下递归(假设有m组相同元素构成nums,各组个数为x1,x2……xm(即x1+x2……+xn),那么总时间接近x1*x2……*xm;而与2^N(2的x1次方*2的x2次方……*2的xm次方)相比,前者时间更短)
- 替代unordered_set,根据加入相同元素的个数可以区分不同的path,不再需要unordered_set
大体流程:
先排序nums后利用递归将答案找出
basecase:
- 若遍历完数组,将path加入到ret中
recur:
- 确定相同元素的个数,再根据不同个数向下递归
代码:
class Solution {
public:
vector<vector<int>>ret;
void f(vector<int>& nums,int i,vector<int>&path){
int n=nums.size();
if(i>=n){
ret.push_back(path);
return;
}
int cnt=1,k=i+1;
while(k<n&&nums[k]==nums[i]){++k;++cnt;}
f(nums,i+cnt,path);
for(k=1;k<=cnt;++k){
path.push_back(nums[i]);
f(nums,i+cnt,path);
}
path.erase(path.end()-cnt,path.end());
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
ret.clear();
sort(nums.begin(),nums.end());
vector<int> path;
f(nums,0,path);
return ret;
}
};
三、全排列
给定一个不含重复数字的整数数组
nums
,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
思路:
依照全排列的概念:取n个不同的元素,按照一定顺序排列的全部排列情况叫做全排列
那么大体流程为:
basecase:
- 遍历完数组后,将排列情况添加到答案中
recur:
- 循环遍历该pos后的全部元素:取元素与当前位置交换,在添加到path后对下一个pos进行recur,最后复原交换
代码:
class Solution {
public:
vector<vector<int>> ret;
void f(vector<int>& nums, int i, vector<int>& path) {
int len = nums.size();
if (i == len) {
ret.push_back(path);
return;
}
path.push_back(nums[i]);
f(nums, i + 1, path);
path.pop_back();
for (int j = i+1; j < len; ++j) {
swap(nums[i],nums[j]);
path.push_back(nums[i]);
f(nums, i + 1, path);
swap(nums[i],nums[j]);
path.pop_back();
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<int> path;
ret.clear();
f(nums,0,path);
return ret;
}
};
四、全排列 II
给定一个可包含重复数字的整数集合
nums
,按任意顺序 返回它所有不重复的全排列。
思路:
分析与题目三的区别,该题有重复的数字,如果按照前面的解法,那将有排列重复。那么怎么避免重复?对相同的数字只走一次,即在循环遍历过程中记录遇到过的元素,再次遇到时跳过
大体流程与题目三类似
代码:
class Solution {
public:
vector<vector<int>> ret;
void f(vector<int>& nums, int i, vector<int>& path) {
int len = nums.size();
if (i == len) {
ret.push_back(path);
return;
}
vector<int> cnts(21,1);
//unordered_set<int>set;set.insert(nums[i]);
cnts[nums[i]+10]=0;
path.push_back(nums[i]);
f(nums, i + 1, path);
path.pop_back();
for (int j = i + 1; j < len; ++j) {
//if(set.contains(nums[j]))continue;
if(!cnts[nums[j]+10])continue;
//set.insert(nums[j]);
cnts[nums[j]+10]=0;
swap(nums[i], nums[j]);
path.push_back(nums[i]);
f(nums, i + 1, path);
swap(nums[i], nums[j]);
path.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<int> path;
ret.clear();
f(nums, 0, path);
return ret;
}
};
五、用递归函数和栈操作逆序栈
描述
一个栈依次压入1,2,3,4,5那么从栈顶到栈底分别为5,4,3,2,1。将这个栈转置后,从栈顶到栈底为1,2,3,4,5,也就是实现了栈中元素的逆序,请设计一个算法实现逆序栈的操作,但是只能用递归函数来实现,而不能用另外的数据结构。
给定一个栈Stack以及栈的大小top,请返回逆序后的栈。
思路:
双层递归:
递归1:从栈中获取底部元素,维持上面的元素不变
- basecase:若栈的大小为1,弹出并返回底部元素
- recur:获取顶部元素,在获取底部元素(recur1)后,将顶部元素再次插入
递归2:将获取的底部元素,依次插入到栈中,使最先获取的底部元素最后插入
- basecase:若栈为空,返回
- recur:获取底部元素(recur1),在将其之后的全部底部元素插入(recur2)后,插入底部元素
代码(与题目规范不一致):
#include <iostream>
using namespace std;
#include <stack>
int get_bottom(stack<int>& stk) {
if (stk.size() == 1) {
int tmp = stk.top();
stk.pop();
return tmp;
}
int t1 = stk.top();
stk.pop();
int ret = get_bottom(stk);
stk.push(t1);
return ret;
}
void reverse(stack<int>& stk) {
if(stk.empty())return;
int n=get_bottom(stk);
reverse(stk);
stk.push(n);
}
int main() {
stack<int>stk;
int x;
while (cin >> x) {
stk.push(x);
}
reverse(stk);
while (stk.size()) {
cout << stk.top() << endl;
stk.pop();
}
return 0;
}
六、用递归排序一个栈
思路:
升序,双层递归:
递归1:从栈中获取最小元素,维持其他元素不变
- basecase:若栈的大小为1,弹出并返回底部元素
- recur:弹出顶部元素,在获取最小元素(recur1)后,将顶部元素再次插入
递归2:将获取的最小元素,依次插入到栈中,使最先获取的最小元素最后插入
- basecase:若栈为空,返回
- recur:获取最小元素(recur1),在将其之后的全部最小元素插入(recur2)后,插入最小元素
代码:
#include <iostream>
using namespace std;
#include <stack>
int get_min(stack<int>& stk,int& m,int& cnt) {
if (stk.size() == 0) {
return m;
}
int tmp = stk.top();
stk.pop();
m = min(tmp, m);
m = min(m, get_min(stk, m, cnt));
if (tmp == m){
++cnt;
if (cnt > 1)stk.push(tmp);
}
else stk.push(tmp);
return m;
}
void sort_stack(stack<int>& stk) {
if (stk.size() == 0) {
return;
}
int cnt = 0;
int m = INT_MAX;
m = get_min(stk, m, cnt);
sort_stack(stk);
stk.push(m);
}
int main() {
stack<int>stk;
stk.push(3);
stk.push(2);
stk.push(1);
stk.push(4);
stk.push(2);
stk.push(1);
stk.push(5);
sort_stack(stk);
while (stk.size()) {
cout << stk.top() << endl;
stk.pop();
}
return 0;
}
简化思路:
将代码简单化,递归的功能分给多个递归完成
降序,多层递归:
确定stack的深度dep
递归2:
- 向下递归dep层找到最小的元素并返回
递归3:
- 向下递归dep层找到最小的元素个数并返回
递归4:
- 向下递归dep层将全部的最小元素压入栈中,其他元素维持相对顺序不变
大体流程:
- 根据确定的dep,循环dep--:递归2,3,4
简化代码:
int get_min(stack<int>& stk,int m, int dep) {
if (dep == 1) {
m = min(m, stk.top());
return m;
}
int top = stk.top();
stk.pop();
m = min(m, top);
int tmp = get_min(stk, m, dep - 1);
m = min(m,tmp);
stk.push(top);
return m;
}
int get_cnt(stack<int>& stk, int m, int dep) {
if (dep == 1) {
return m == stk.top();
}
int cnt = 0;
int top = stk.top();
stk.pop();
if (top == m)++cnt;
cnt += get_cnt(stk, m, dep - 1);
stk.push(top);
return cnt;
}
void set_mins(stack<int>& stk, int m, int cnt,int dep) {
if (dep == 0) {
while (cnt--) {
stk.push(m);
}
return;
}
int top = stk.top();
stk.pop();
set_mins(stk, m, cnt, dep - 1);
if (top != m)stk.push(top);
}
void sort_stack(stack<int>& stk) {
if (stk.size() == 0|| stk.size() == 1) {
return;
}
int dep = stk.size();
int m, cnt;
while (dep) {
m=get_min(stk,INT_MAX,dep);
cnt = get_cnt(stk, m, dep);
set_mins(stk, m, cnt, dep);
dep -= cnt;
}
}
int main() {
stack<int>stk;
stk.push(3);
stk.push(2);
stk.push(1);
stk.push(4);
stk.push(2);
stk.push(1);
stk.push(5);
sort_stack(stk);
while (stk.size()) {
cout << stk.top() << endl;
stk.pop();
}
return 0;
}
七、汉诺塔
在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。
思路:
递归函数:将位于A上的n个盘子移到C去
大体流程:
basecase:
- 当n==1时,将位于A上的n个盘子移到C去
recur:
- 将位于A上的n-1个盘子移到B去(recur)
- 将位于A上的最后一个盘子移到C去
- 将位于B上的n-1个盘子移到C去(recur)
代码:
class Solution {
public:
void remove(vector<int>& A, vector<int>& B, vector<int>& C,int n){
if(n==1){
C.push_back(A.back());
A.pop_back();
return;
}
remove(A,C,B,n-1);
C.push_back(A.back());
A.pop_back();
remove(B,A,C,n-1);
}
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
remove(A,B,C,A.size());
}
};
打印思路:
与之前的类似,只是将插入和删除行为换成了打印
打印代码:
void print_Hanoi(int n, char from[], char to[], char other[]) {
if (n == 1) {
printf("将第%d个汉诺塔从%s移到%s\n", n, from, to);
return;
}
print_Hanoi(n - 1, from, other, to);
printf("将第%d个汉诺塔从%s移到%s\n", n, from, to);
print_Hanoi(n - 1, other, to, from);
}