文章目录
509. 斐波那契数
直接递归
规律很明显,就是一项等于前两项的和。递归记得定义好出口(不然就会死循环)就是有确定值的 n = 0 n=0 n=0和 n = 1 n=1 n=1的情况
class Solution {
public:
int fib(int n) {
if(n==0) return 0;
if(n==1) return 1;
return fib(n-1)+fib(n-2);
}
};
记忆化递归
注意到上面的代码还是很多的重复计算的情况存在,主要是因为每一次计算都不能利用之前的运算结果。第n个斐波那契数已经算出来的情况下,算第n+1个斐波那契数还是要把第n个再算一遍。于是我们可以用一个数组把每一次的运算结果存起来
class Solution {
public:
int f[110]={0};
int cul(int n)
{
if(f[n]||n==0) return f[n];
return f[n]=cul(n-1)+cul(n-2);
}
int fib(int n) {
f[0]=0,f[1]=1;
return cul(n);
}
};
递推公式
直接从定义出发递推
class Solution {
public:
int fib(int n) {
int f[110]={0};
f[1]=1;
for(int i=2;i<=30;i++) f[i]=f[i-1]+f[i-2];
return f[n];
}
};
1137. 第 N 个泰波那契数
递推公式
和上一道题一样从定义出发,直接递推
class Solution {
public:
int tribonacci(int n) {
int f[40]={0};
f[1]=f[2]=1;
for(int i=3;i<=n;i++) f[i]=f[i-1]+f[i-2]+f[i-3];
return f[n];
}
};
118. 杨辉三角
递推公式
观察知每一项都等于上面一层的对应数字和其前一个数字的和(除了两边的情况)
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>>res;
res.push_back({1});
for(int i=1;i<numRows;i++)
{
vector<int>cnt;
for(int j=0;j<=i;j++)
{
if(j==0||j==i) cnt.push_back(1);
else cnt.push_back(res[i-1][j-1]+res[i-1][j]);
}
res.push_back(cnt);
}
return res;
}
};
119. 杨辉三角 II
滚动数组
用一个一维数组不断更新数组中的值。至于内层循环为什么要从后往前,这是用为滚动数组的特点是每一次更新都利用了上一次更新的结果来更新,每一次更新用到的值是res[j]和res[j-1],如果从前往后循环更新的话就会出现res[j-1]已经被更新,后面的值就会错误的被更新,这里实际还是降维了,简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int>res(rowIndex+1); //滚动数组
res[0]=1; //初始设边界为1
for(int i=2;i<=rowIndex+1;i++) //这一层循环表示迭代的次数
{
for(int j=i-1;j>=0;j--) //循环更新到这一层的每一个元素
{
if(j>=1) res[j]=res[j-1]+res[j];
}
}
return res;
}
};
70. 爬楼梯
斐波那契
class Solution {
public:
int f[110]={0};
int cul(int n)
{
if(f[n]||n==0) return f[n];
return f[n]=cul(n-1)+cul(n-2);
}
int climbStairs(int n) {
f[1]=1,f[2]=2;
return cul(n);
}
};
剑指 Offer 62. 圆圈中最后剩下的数字
约瑟夫环
一个很经典的问题。很容易发现,这道题是有很强的规律的(尽管我们还不知道规律是什么)。如果删除一个数字以后,剩下了n-1个数字并且还按照同样的方式删除元素,所以我们就发现这道题可以递归处理。删掉第n个元素以后,后面的所有元素的位置都会提前n个位置,而前面的n-1个元素顺次接在最后。所以反过来,当知道m-1个元素最后剩下的元素的位置以后,加上n就得到了答案。为了防止数组越界(使数组成环)所以我们对数组长度取余
class Solution {
public:
int lastRemaining(int n, int m) {
if(n==1) return 0;
return (lastRemaining(n-1,m)+m)%n;
}
};
剑指 Offer II 092. 翻转字符
动态规划
这道题的关键其实就是找一个分界点,把前面的0和后面的1分开。我们可以线性的枚举每一个位置,假设让这个位置作为分界点,判断是不是最优解。我们用一个dp数组来记录每一个点的状态(反转与不反转,是否作为01分界点)
class Solution {
public:
int minFlipsMonoIncr(string s) {
int n=s.size();
int dp[20010][2]={0};
//dp[i][j]表示需要反转的次数,i表示第i个点,j表示前一个点的值
s=' '+s;
for(int i=1;i<=n;i++)
{
dp[i][0]=dp[i-1][0];
//先把前一个点的直接转移过去
dp[i][1]=min(dp[i-1][0],dp[i-1][1]);
//这个点是1,前一个点也可以是0,所以取一个最小值
if(s[i]=='1') dp[i][0]++;
//如果前一个点是0当前点是1,反转次数+1
else dp[i][1]++;
}
return min(dp[n][0],dp[n][1]);
}
};