Idea 02.暴力递归与动态规划(1)
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<) return -; if (n == || n == ) return ;
else {
return n*factor(n - );
} }
//方法二:直接法
int factor2(int n) {
int res=;
for (int i = ; i <= n; ++i) {
res *= i;//1×2×3×……×n
}
return res;
}
}; int main(){
Factorial test;
//cout << test.factor(3) << endl;
cout << test.factor2()<<endl;
return ;
}
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<=) return res;
func(n,"left","mid","right");
return res;
} void func(int n, string from, string mid,string to){
if(n==)
res.push_back("move from "+from+" to "+to);
else{
func(n-,from,to,mid);
func(,from,mid,to);
func(n-,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)-) {
std::cout << res;
return;
}
else {
printAllSub(str, i + , res);//不选str[i]
printAllSub(str, i + , res + str[i]);//选str[i]
} } void main() {
string test = "abc";
char v[];
//strncpy_s 优化后更安全的函数
strncpy_s(v, test.c_str(), test.length() + );//必须加1,\0还占一个位置
printAllSub(v, , " ");
}
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[])-;//获取数组长度 sizeof(str)/sizeof(str[0])
//cout << n << endl; if (i == n-) {
for(int w = ; 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 + );
swap(str[i], str[j]);//没有这一行,结果会出错。
}
} } void main() {
string test = "abc";
char v[];
strncpy_s(v, test.c_str(), test.length() + );//必须加1,\0还占一个位置
printAllArr(v, );
}
#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 = ; i < k; i++) {
if (str[i] == str[k]) {
flag = ;
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; // if (i == n - ) {
for (int w = ; 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 + ,n);
swap(str[i], str[j]);
}
}
}
} void main() {
char str[] = { 'a','b','c','a'};
int length = sizeof(str) / sizeof(str[]);//获取数组长度
//cout <<"length = " <<length << endl;
printAllArr(str, , length);
}
参考资料:
1.输出一个字符串的全排列 (注:该文提供的代码结果有误,少了上面标红的一行代码)
3. https://www.nowcoder.com/courses/semester/senior 《牛客高级项目课——(牛客网)》--大牛·左程云
Idea 02.暴力递归与动态规划(1)的更多相关文章
- 【LeetCode】300.最长递增子序列——暴力递归(O(n^3)),动态规划(O(n^2)),动态规划+二分法(O(nlogn))
算法新手,刷力扣遇到这题,搞了半天终于搞懂了,来这记录一下,欢迎大家交流指点. 题目描述: 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度. 子序列是由数组派生而来的序列,删除(或不删 ...
- 算法 递归 迭代 动态规划 斐波那契数列 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- 70. Climbing Stairs【leetcode】递归,动态规划,java,算法
You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb ...
- 面试题目——《CC150》递归与动态规划
面试题9.1:有个小孩正在上楼梯,楼梯有n个台阶,小孩一次可以上1阶.2阶或者3阶.实现一个方法,计算小孩有多少种上楼梯的方式. 思路:第4个数是前三个数之和 注意:能不能使用递归,能不能建立一个很大 ...
- java暴力递归回溯算法
今天这个问题是我之前一直想解决的,还记得以前第一次上蓝桥杯的课的时候,也就是大一高数期中模拟考试那天,下午去上蓝桥杯课,遇到这道题,当时写了写,根本没有思路,然后就给大一的模拟考试去了.印象深刻啊,一 ...
- python编写PAT 1007 Maximum Subsequence Sum(暴力 分治法 动态规划)
python编写PAT甲级 1007 Maximum Subsequence Sum wenzongxiao1996 2019.4.3 题目 Given a sequence of K integer ...
- OptimalSolution(1)--递归和动态规划(1)斐波那契系列问题的递归和动态规划
一.斐波那契数列 斐波那契数列就是:当n=0时,F(n)=0:当n=1时,F(n)=1:当n>1时,F(n) = F(n-1)+F(n-2). 根据斐波那契数列的定义,斐波那契数列为(从n=1开 ...
- 最大子序和:暴力->递归->动规->线段树
题目描述 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. LeetCode:53. 最大子序和 题解 显而易见的暴力解法 最容易想到的便是暴力穷 ...
- C#递归、动态规划计算斐波那契数列
//递归 public static long recurFib(int num) { if (num < 2) ...
随机推荐
- 什么是Spark
什么是Spark Apache Spark是一个开源集群运算框架, 相对于Hadoop的MapReduce会在运行完工作后将中介数据存放到磁盘中,Spark使用了存储器内运算技术,能在数据尚未写入硬盘 ...
- RNN概述-深度学习 -神经网络
一 RNN概述 前面我们叙述了BP算法, CNN算法, 那么为什么还会有RNN呢?? 什么是RNN, 它到底有什么不同之处? RNN的主要应用领域有哪些呢?这些都是要讨论的问题. 1) BP算法 ...
- 特殊符号 & 以太坊
&表示取二进制的末尾 &1表示如果末尾是奇数和偶数两种情况 0 偶数 1奇数 举例子: int a=1;int p=&a; 其中,p是指针,&a就是将a在内存中的实际地 ...
- A Compatible Pair
Description “年”是一个生活在海洋深处的怪物.每年,它都出现在陆地上,吞噬牲畜甚至是人.为了让怪物离开,人们用红色,光线和爆炸的声音填满他们的村庄,所有这些都吓跑了怪物. 小汤米有 n ...
- scanf格式控制符的完整格式
scanf格式控制的完整格式: % * m l或h 格式字符 ①格式字符与printf函数中的使用方式相同,以%d.%o.%x.%c.%s.%f.%e,无%u格式.%g ...
- 常用算法Java实现之快速排序
快速排序和冒泡排序相似,都是通过多次比较和交换来实现排序. 具体流程如下: 1.首先设定一个分界值,通过分界值将数组分成左右两部分,将大于等于分界值的数据交换集中到右侧数组,将小于分界值的数据交换集中 ...
- ACM 第十二天
博弈论(巴什博奕,威佐夫博弈,尼姆博弈,斐波那契博弈,SG函数,SG定理) 一. 巴什博奕(Bash Game): A和B一块报数,每人每次报最少1个,最多报4个,看谁先报到30.这应该是最古老的关 ...
- mysql授权远程连接
查一下你的MYSQL用户表里, 是否允许远程连接 1.授权 mysql>grant all privileges on *.* to 'root'@'%' identified by ...
- C#里面Console.Write()和Console.WriteLine()有什么区别?
Console.Write()和Console.WriteLine()都是System.Console提供的方法,两着主要用来将输出流由指定的输出装置(默认为屏幕)显示出来.两着间的差异在Consol ...
- HDU 4869 Turn the pokers(思维+逆元)
考试的时候没有做出来... 想到了答案一定是一段连续的区间,一直在纠结BFS判断最后的可行1数. 原来直接模拟一遍就可以算出来最后的端点... 剩下的就是组合数取模了,用逆元就行了... # incl ...