1,关键词解释
1.1 暴力递归:
1, 把问题转化为规模缩小了的同类问题的子问题
2, 有明确的不需要继续进行递归的条件(base case)
3, 有当得到了子问题的结果之后的决策过程
4, 不记录每一个子问题的解
1.2 动态规划:
1, 从暴力递归中来
2, 将每一个子问题的解记录下来,避免重复计算
3, 把暴力递归的过程,抽象成了状态表达
4, 并且存在化简状态表达,使其更加简洁的可能
2,学会尝试才能掌握
2.1 P类问题和NP问题
P类问题:时间复杂度为多项式; 知道怎么算,让计算机帮我算。
NP问题:时间复杂度很复杂,指数级或位置; 不知道怎么算,但是知道怎么尝试。
2.2 尝试的重要性
学会了尝试,在不断的实践中积累经验才能真正掌握这些算法思想的精髓。许多本科毕业生甚至研究生,都缺乏这种能力。
3,例题实践
3.1 求n!的值
#include <iostream> using namespace std; class Factorial { public: //方法一:递归版 int factor(int n) { if (n<0) return -1; if (n == 1 || n == 0) return 1; else { return n*factor(n - 1); } } //方法二:直接法 int factor2(int n) { int res=1; for (int i = 1; i <= n; ++i) { res *= i;//1×2×3×……×n } return res; } }; int main(){ Factorial test; //cout << test.factor(3) << endl; cout << test.factor2(4)<<endl; return 0; }
3.2 汉诺塔问题
问题描述:
三根柱子:"left","mid","right" 要求: 1.要把放在“left”杆子上的n个从大到小叠加放置的圆盘移动到“right”杆子上; 2.移动过程中,一个只能移动一个圆盘,且大的圆盘不能放置在小的圆盘上。
递归分解:
- 当n为1时,"left"杆子上只有一块圆盘,可以直接将它移动至"right"杆子上;(base case:递归出口)
- 当n大于1时,要想使得第1步成立,要先把"left"上面的n-1块圆盘移动至辅助的"mid"杆子上;
- 最后,将"mid"杆子上的n-1块圆盘移动至"right"杆子上。
//题目地址:https://www.nowcoder.com/questionTerminal/7d6cab7d435048c4b05251bf44e9f185 class Hanoi { public: vector<string> getSolution(int n) { //判断n是否合法输入 if(n<=0) return res; func(n,"left","mid","right"); return res; } void func(int n, string from, string mid,string to){ if(n==1) res.push_back("move from "+from+" to "+to); else{ func(n-1,from,to,mid); func(1,from,mid,to); func(n-1,mid,from,to); } } private: vector<string> res; };
3.3 打印一个字符串(“abc”)的所有的子序列(注:不是子串)
a:选或不选 b:选或不选 c:选或不选 2×2×2=8种可能,包含空子序列。
#include <iostream> #include <string> using namespace std; //void printAllSbu(char str[],int i,string res) void printAllSub(char *str,int i, string res) { //str[]字符串数组的实际大小为sizeof(str),有效大小为sizeof(str)-1 if (i == sizeof(str)-1) { std::cout << res; return; } else { printAllSub(str, i + 1, res);//不选str[i] printAllSub(str, i + 1, res + str[i]);//选str[i] } } void main() { string test = "abc"; char v[4]; //strncpy_s 优化后更安全的函数 strncpy_s(v, test.c_str(), test.length() + 1);//必须加1, 还占一个位置 printAllSub(v, 0, " "); }
3.4 打印一个含n个字母的字符串(如:“abc”)的所有的全排列
#case1:假设不含重复字母
递归思路:
- (bese case:) n=1时,如果字符串中只有一个元素,直接生成全排列;
- 当n>1时,如果能生成n-1个元素的全排列,就能生成n个元素的全排列,以三个字符"abc"为例:
-
- 首先我们固定第一个字符a,排列后面的两个字符bc;
- 当两个字符bc排列求好后,我们把字符b和第一字符a交换,使得b固定在第一个字符,排列后面的两个字符ac;
- 当两个字符ac排列求好后,我们把字符c和第一字符a交换,使得c固定在第一个字符,排列后面的两个字符ab;这里特别需要注意一点,我们上一步交换了a和b的位置,要想保证我们正确的交换a和c的位置,需要恢复原字符串,先换回a和b的位置。因为,我们确定第一个字符串和后面每一个字符串交换,固定第一个位置的元素时,是基于初始字符串abc的次序考虑的。(参考资料的第一篇博文中存在代码错误就是这里没有先恢复原串)
既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了
/*打印一个字符串中所有字母的全排列(假设不含重复字母))*/ #include <iostream> #include <string> using namespace std; void printAllArr(char str[],int i) { int n = sizeof(str)/sizeof(str[0])-1;//获取数组长度 sizeof(str)/sizeof(str[0]) //cout << n << endl; if (i == n-1) { for(int w = 0; w < n;++w){ cout << str[w]; } cout << endl; return; } else { for (int j = i; j <n; ++j) { swap(str[i], str[j]); printAllArr(str, i + 1); swap(str[i], str[j]);//没有这一行,结果会出错。
} } } void main() { string test = "abc"; char v[4]; strncpy_s(v, test.c_str(), test.length() + 1);//必须加1, 还占一个位置 printAllArr(v, 0); }
#case2:含有重复字母,且要求输出结果无重复
关键思路:如果str[i]和str[j]相同,则忽略交换。(如:"abca",遇到第一个a和第四个a则不交换。)
#include <iostream> #include <string> using namespace std; //判断是否需要交换 int is_swap(char *str, int begin, int k) { int i, flag; for (i = begin, flag = 1; i < k; i++) { if (str[i] == str[k]) { flag = 0; break; } } return flag; } //打印所有的全排列 void printAllArr(char str[], int i, int n) { /*--------不能在这里获取正确的数组长度----------*/ //int n = sizeof(str) / sizeof(str[0]);//遗留问题:这里为什么固定是4呢? //cout << n << endl; //原因:这里参数传递只是数组的首元素指针(32位的内存地址),并不是整个数组,所有固定是4 //cout << sizeof(str) << endl; //4 //cout << sizeof(str[0]) << endl; //1 if (i == n - 1) { for (int w = 0; w < n; ++w) { cout << str[w]; } cout << endl; return; } else { for (int j = i; j < n; ++j) { if (is_swap(str, i, j)) {//判断是否需要交换,相同则不交换 swap(str[i], str[j]); printAllArr(str, i + 1,n); swap(str[i], str[j]); } } } } void main() { char str[4] = { 'a','b','c','a'}; int length = sizeof(str) / sizeof(str[0]);//获取数组长度 //cout <<"length = " <<length << endl; printAllArr(str, 0, length); }
参考资料:
1.输出一个字符串的全排列 (注:该文提供的代码结果有误,少了上面标红的一行代码)
3. https://www.nowcoder.com/courses/semester/senior 《牛客高级项目课——(牛客网)》--大牛·左程云