509. 斐波那契数
斐波那契数 (通常用
F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和1
开始,后面的每一项数字都是前面两项数字的和。也就是:F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定
n
,请计算F(n)
。示例 1:
输入:n = 2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3 输出:2 解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
暴力递归
#if 1 // 如果条件为真,则编译以下代码
class Solution { // 定义一个名为Solution的类
public:
int dfs(int n){ // 定义一个名为dfs的递归函数,用于计算斐波那契数列的第n项
if(n==0) return 0; // 如果n为0,直接返回0(斐波那契数列的第0项是0)
if(n==1||n==2) return 1; // 如果n为1或2,直接返回1(斐波那契数列的第1项和第2项都是1)
return dfs(n-1)+dfs(n-2); // 递归调用自身,计算第n项的值,即前两项的和
}
void solveinit(){ // 定义一个名为solveinit的函数,目前是空的,可能用于初始化操作
}
int fib(int n) { // 定义一个名为fib的函数,用于接口调用,返回斐波那契数列的第n项
solveinit(); // 调用solveinit函数(尽管它目前没有实际操作)
return dfs(n); // 调用dfs函数计算并返回斐波那契数列的第n项
}
};
#endif // 结束#if预处理指令
记忆化递归
#if 1 // 如果条件为真,则编译以下代码
class Solution { // 定义一个名为Solution的类
public:
vector<int> memory; // 定义一个vector,用于存储已计算的斐波那契数,以避免重复计算
int n; // 定义一个整型变量n,用于存储斐波那契数列的需求项数
int dfs(int n){ // 定义一个名为dfs的递归函数,用于计算斐波那契数列的第n项
if(n==0) return 0; // 如果n为0,直接返回0(斐波那契数列的第0项是0)
if(n==1||n==2) return 1; // 如果n为1或2,直接返回1(斐波那契数列的第1项和第2项都是1)
if(memory[n]!=-1) return memory[n]; // 如果这一项已经计算过,直接返回之前计算的结果
memory[n]=dfs(n-1)+dfs(n-2); // 递归调用自身,计算第n项的值,即前两项的和,并存储结果
return memory[n]; // 返回存储的结果
}
void solveinit(){ // 定义一个名为solveinit的函数,用于初始化内存数组
memory.clear(); // 清空memory数组
memory.resize(n+1,-1); // 将memory数组大小调整为n+1,并初始化所有值为-1
}
int fib(int _n) { // 定义一个名为fib的函数,用于接口调用,返回斐波那契数列的第n项
n=_n; // 将传入的参数_n赋值给成员变量n
solveinit(); // 调用solveinit函数进行初始化
return dfs(n); // 调用dfs函数计算并返回斐波那契数列的第n项
}
};
#endif // 结束#if预处理指令
动态规划
class Solution { // 定义一个名为Solution的类
public:
int n; // 定义一个整型变量n,用于存储斐波那契数列的需求项数
vector<int> dp; // 定义一个名为dp的vector数组,用于动态规划中存储斐波那契数列的各项值
void solveinit(){ // 定义一个名为solveinit的函数,用于初始化dp数组
dp.clear(); // 清空dp数组
dp.resize(n+1); // 将dp数组的大小调整为n+1(因为需要从0到n的位置)
dp[0]=0; // 初始化斐波那契数列的第0项为0
dp[1]=1; // 初始化斐波那契数列的第1项为1
dp[2]=1; // 初始化斐波那契数列的第2项也为1
}
int fib(int _n) { // 定义一个名为fib的函数,用于计算并返回斐波那契数列的第n项
n=_n; // 将传入的参数_n赋值给成员变量n
if(n==0) return 0; // 如果n为0,直接返回0(斐波那契数列的第0项是0)
if(n==1||n==2) return 1; // 如果n为1或2,直接返回1(斐波那契数列的第1项和第2项都是1)
solveinit(); // 调用solveinit函数进行dp数组的初始化
for(int i=3;i<=n;i++){ // 从第3项开始,遍历到第n项
dp[i]=dp[i-1]+dp[i-2]; // 动态规划公式,当前项等于前两项的和
}
return dp[n]; // 返回dp数组中的第n项,即斐波那契数列的第n项
}
};
动态规划+空间压缩
class Solution { // 定义一个名为Solution的类
public:
int n; // 定义一个整型变量n,用于存储斐波那契数列的需求项数
int a, b, c; // 定义三个整型变量a, b, c,用于计算斐波那契数列
void solveinit(){ // 定义一个名为solveinit的函数,用于初始化变量a, b, c
a=1; // 初始化a为1,表示斐波那契数列的第二项
b=1; // 初始化b为1,表示斐波那契数列的第二项
c=2; // 初始化c为2,起初设置为第三项的预设值,虽然它将被计算覆盖
}
int fib(int _n) { // 定义一个名为fib的函数,用于计算并返回斐波那契数列的第n项
n=_n; // 将传入的参数_n赋值给成员变量n
if(n==0) return 0; // 如果n为0,直接返回0(斐波那契数列的第0项是0)
if(n==1||n==2) return 1; // 如果n为1或2,直接返回1(斐波那契数列的第1项和第2项都是1)
solveinit(); // 调用solveinit函数进行变量初始化
for(int i=3;i<=n;i++){ // 从第3项开始,遍历到第n项
c=a+b; // 当前项c计算为前两项a和b的和
a=b; // 将b的值赋给a,为下一次计算做准备
b=c; // 将当前计算出的c的值赋给b,为下一次计算做准备
}
return c; // 返回变量c的值,即斐波那契数列的第n项
}
};
983. 最低票价
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为
days
的数组给出。每一项是一个从1
到365
的整数。火车票有 三种不同的销售方式 :
一张 为期一天 的通行证售价为
costs[0]
美元;一张 为期七天 的通行证售价为
costs[1]
美元;一张 为期三十天 的通行证售价为
costs[2]
美元。通行证允许数天无限制的旅行。 例如,如果我们在第
2
天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第2
天、第3
天、第4
天、第5
天、第6
天、第7
天和第8
天。返回 你想要完成在给定的列表
days
中列出的每一天的旅行所需要的最低消费 。示例 1:
输入:days = [1,4,6,7,8,20], costs = [2,7,15] 输出:11 解释: 例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。 在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。 在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。 你总共花了 $11,并完成了你计划的每一天旅行。
示例 2:
输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15] 输出:17 解释: 例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。 在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 你总共花了 $17,并完成了你计划的每一天旅行。
提示:
1 <= days.length <= 365
1 <= days[i] <= 365
days
按顺序严格递增
costs.length == 3
1 <= costs[i] <= 1000
暴力递归
class Solution { // 定义一个名为Solution的类
public:
vector<int> days; // 定义一个向量days,存储预计旅行的所有天数
vector<int> costs; // 定义一个向量costs,存储三种不同通行证的费用
int __n; // 定义一个整数__n,用于记录days向量的长度
vector<int> __jiaqi; // 定义一个向量__jiaqi,存储三种通行证的有效天数
int dfs(int __i) { // 定义一个名为dfs的递归函数,用于计算从第__i天开始的最低票价
if (__i == __n) // 如果__i等于__n,说明已经处理完所有旅行的天数
return 0; // 在这种情况下,没有更多的费用,返回0
int __curday = days[__i]; // 获取当前处理的天数
int __ans = INT_MAX; // 初始化__ans为最大整数,用于找到最小花费
int __j = __i; // 初始化索引__j为当前位置__i
for (int k = 0; k < 3; k++) { // 遍历三种通行证
int __nextday = __curday + __jiaqi[k]; // 计算当前通行证有效的最后一天
while (__j < __n && days[__j] < __nextday) { // 找到当前通行证覆盖的最后一个旅行日
__j++;
}
__ans = min(__ans, costs[k] + dfs(__j)); // 更新__ans为最小花费,包括当前通行证的费用加上后续旅行的最小费用
}
return __ans; // 返回计算的最小花费
}
void solveinit() { // 定义一个名为solveinit的函数,用于初始化变量和向量
__n = days.size(); // 设置__n为days向量的长度
__jiaqi.clear(); // 清空__jiaqi向量
__jiaqi.resize(3); // 设置__jiaqi向量的大小为3
__jiaqi[0] = 1; // 设置一天通行证的有效天数
__jiaqi[1] = 7; // 设置七天通行证的有效天数
__jiaqi[2] = 30; // 设置三十天通行证的有效天数
}
int mincostTickets(vector<int>& _days, vector<int>& _costs) { // 定义一个名为mincostTickets的函数,用于计算整个旅行计划的最低票价
days = _days; // 将输入的_days向量赋值给days
costs = _costs; // 将输入的_costs向量赋值给costs
solveinit(); // 调用solveinit函数进行初始化
return dfs(0); // 从第0天开始调用dfs函数,计算并返回最低票价
}
};
记忆化递归
class Solution { // 定义一个名为Solution的类
public:
vector<int> days; // 定义一个向量days,存储计划旅行的所有天数
vector<int> costs; // 定义一个向量costs,存储三种不同通行证的费用
int __n; // 定义一个整数__n,用于记录days向量的长度
vector<int> __jiaqi; // 定义一个向量__jiaqi,存储三种通行证的有效天数
vector<int>
__memory; // 定义一个向量__memory,用于记忆化搜索,存储已计算的结果,避免重复计算
vector<int>
__dp; // 定义一个向量__dp,用于动态规划,存储从每一天起的最小花费
void solveinit() { // 定义一个名为solveinit的函数,用于初始化变量和向量
__n = days.size(); // 设置__n为days向量的长度
__jiaqi.clear(); // 清空__jiaqi向量
__jiaqi.resize(3); // 设置__jiaqi向量的大小为3
__jiaqi[0] = 1; // 设置一天通行证的有效天数为1
__jiaqi[1] = 7; // 设置七天通行证的有效天数为7
__jiaqi[2] = 30; // 设置三十天通行证的有效天数为30
__memory.clear(); // 清空__memory向量
__memory.resize(__n + 1,
-1); // 设置__memory向量的大小为__n+1,并初始化为-1
__dp.clear(); // 清空__dp向量
__dp.resize(__n + 1, -1); // 设置__dp向量的大小为__n+1,并初始化为-1
__dp[__n] = 0; // 设置动态规划基准情况:最后一天之后的花费为0
}
int mincostTickets(
vector<int>& _days,
vector<int>&
_costs) { // 定义一个名为mincostTickets的函数,用于计算整个旅行计划的最低票价
days = _days; // 将输入的_days向量赋值给days
costs = _costs; // 将输入的_costs向量赋值给costs
solveinit(); // 调用solveinit函数进行初始化
for (int __i = __n - 1; __i >= 0;
__i--) { // 从最后一个旅行日向前计算每一天的最低票价
int __curday = days[__i]; // 获取当前处理的天数
int __ans = INT_MAX; // 初始化__ans为最大整数,用于找到最小花费
int __j = __i; // 初始化索引__j为当前位置__i
for (int k = 0; k < 3; k++) { // 遍历三种通行证
int __nextday =
__curday + __jiaqi[k]; // 计算当前通行证允许的最后一天
while (__j < __n &&
days[__j] <
__nextday) { // 找到当前通行证覆盖的最后一个旅行日
__j++;
}
__ans = min(
__ans,
costs[k] +
__dp[__j]); // 更新__ans为最小花费,包括当前通行证的费用加上后续旅行的最小费用
}
__dp[__i] =
__ans; // 将计算出的最小费用存储在动态规划数组__dp中,对应于当前天数__i的位置。
}
return __dp[0]; // 最终返回从第一天开始的最低总花费
}
};
动态规划
class Solution { // 定义一个名为Solution的类
public:
vector<int> days; // 定义一个向量days,存储计划旅行的所有天数
vector<int> costs; // 定义一个向量costs,存储三种不同通行证的费用
int __n; // 定义一个整数__n,用于记录days向量的长度
vector<int> __jiaqi; // 定义一个向量__jiaqi,存储三种通行证的有效天数
vector<int> __memory; // 定义一个向量__memory,用于记忆化搜索,存储已计算的结果,避免重复计算
vector<int> __dp; // 定义一个向量__dp,用于动态规划,存储从每一天起的最小花费
void solveinit() { // 定义一个名为solveinit的函数,用于初始化变量和向量
__n = days.size(); // 设置__n为days向量的长度
__jiaqi.clear(); // 清空__jiaqi向量
__jiaqi.resize(3); // 设置__jiaqi向量的大小为3
__jiaqi[0] = 1; // 设置一天通行证的有效天数为1
__jiaqi[1] = 7; // 设置七天通行证的有效天数为7
__jiaqi[2] = 30; // 设置三十天通行证的有效天数为30
__memory.clear(); // 清空__memory向量
__memory.resize(__n + 1, -1); // 设置__memory向量的大小为__n+1,并初始化为-1
__dp.clear(); // 清空__dp向量
__dp.resize(__n + 1, -1); // 设置__dp向量的大小为__n+1,并初始化为-1
__dp[__n] = 0; // 设置动态规划基准情况:最后一天之后的花费为0
}
int mincostTickets(
vector<int>& _days,
vector<int>&
_costs) { // 定义一个名为mincostTickets的函数,用于计算整个旅行计划的最低票价
days = _days; // 将输入的_days向量赋值给days
costs = _costs; // 将输入的_costs向量赋值给costs
solveinit(); // 调用solveinit函数进行初始化
for (int __i = __n - 1; __i >= 0; __i--) { // 从最后一个旅行日向前计算每一天的最低票价
int __curday = days[__i]; // 获取当前处理的天数
int __ans = INT_MAX; // 初始化__ans为最大整数,用于找到最小花费
int __j = __i; // 初始化索引__j为当前位置__i
for (int k = 0; k < 3; k++) { // 遍历三种通行证
int __nextday = __curday + __jiaqi[k]; // 计算当前通行证允许的最后一天
while (__j < __n && days[__j] < __nextday) { // 找到当前通行证覆盖的最后一个旅行日
__j++;
}
__ans = min(__ans, costs[k] + __dp[__j]); // 更新__ans为最小花费,包括当前通行证的费用加上后续旅行的最小费用
}
__dp[__i] = __ans; // 将计算出的最小费用存储在动态规划数组__dp中,对应于当前天数__i的位置。
}
return __dp[0]; // 最终返回从第一天开始的最低总花费
}
};
91. 解码方法
一条包含字母
A-Z
的消息通过以下映射进行了 编码 :'A' -> "1"
'B' -> "2"
...
'Z' -> "26"
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,
"11106"
可以映射为:
"AAJF"
,将消息分组为(1 1 10 6)
"KJF"
,将消息分组为(11 10 6)
注意,消息不能分组为
(1 11 06)
,因为"06"
不能映射为"F"
,这是由于"6"
和"06"
在映射中并不等价。给你一个只含数字的 非空 字符串
s
,请计算并返回 解码 方法的 总数 。题目数据保证答案肯定是一个 32 位 的整数。
示例 1:
输入:s = "12" 输出:2 解释:它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:
输入:s = "226" 输出:3 解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
示例 3:
输入:s = "06" 输出:0 解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。
提示:
1 <= s.length <= 100
s
只包含数字,并且可能包含前导零。
暴力递归
class Solution { // 定义一个名为Solution的类
public:
string __s; // 定义一个字符串变量__s,用于存储输入的编码字符串
int __n; // 定义一个整数变量__n,用于存储字符串的长度
int dfs(int __i) { // 定义一个名为dfs的递归函数,用于计算从位置__i开始的解码方法总数
if (__i == __n) // 如果索引__i等于字符串长度__n,表示已经处理完所有字符
return 1; // 在这种情况下,找到了一种有效的解码方式,返回1
if (__s[__i] == '0') // 如果当前字符是'0',它不能解码为任何字母(没有单独的'0'映射)
return 0; // 返回0,表示从这个位置开始的字符串无法解码
int __ans = 0; // 初始化__ans变量为0,用于累加从当前位置开始的所有可能的解码方式
__ans += dfs(__i + 1); // 递归调用dfs函数处理下一个字符,并将返回值加到__ans
if (__i + 1 < __n && // 如果当前位置后还有至少一个字符,并且
((__s[__i] - '0') * 10 + (__s[__i + 1] - '0')) <= 26) // 当前位置和下一个位置的两个数字组成的数小于等于26
__ans += dfs(__i + 2); // 递归调用dfs函数处理接下来的两个字符,并将返回值加到__ans
return __ans; // 返回从当前位置开始的解码方法总数
}
void solveinit() { __n = __s.size(); } // 定义一个名为solveinit的函数,用于初始化__n为字符串__s的长度
int numDecodings(string s) { // 定义一个名为numDecodings的函数,接受一个字符串s,并返回可能的解码总数
__s = s; // 将传入的字符串赋值给成员变量__s
solveinit(); // 调用solveinit函数进行初始化
return dfs(0); // 从字符串的第一个字符开始调用dfs函数,并返回解码的总数
}
};
记忆化递归
class Solution { // 定义一个名为Solution的类
public:
string __s; // 定义一个字符串__s,用于存储输入的编码消息
int __n; // 定义一个整数__n,用于存储输入字符串的长度
vector<int> __memory; // 定义一个向量__memory,用于记忆化递归中存储中间结果,避免重复计算
int dfs(int __i) { // 定义一个递归函数dfs,用于从第__i个字符开始计算解码方法的总数
if (__i == __n) // 如果__i达到字符串的末尾
return 1; // 则表示找到了一种完整的解码方式,返回1
if (__s[__i] == '0') // 如果当前字符是'0'
return 0; // 则当前路径无法解码(因为'0'不能单独解码),返回0
if (__memory[__i] != -1) // 如果当前位置的解码方法数已经被计算过
return __memory[__i]; // 直接返回存储的结果,避免重复计算
int __ans = 0; // 初始化当前位置的解码方法总数为0
__ans += dfs(__i + 1); // 尝试解码当前一个字符,然后递归计算剩余字符串的解码方法数,并累加到__ans
if (__i + 1 < __n && // 如果当前位置后还有至少一个字符,并且
((__s[__i] - '0') * 10 + (__s[__i + 1] - '0')) <= 26) // 如果当前字符和下一个字符组成的两位数小于等于26
__ans += dfs(__i + 2); // 尝试解码当前两个字符,然后递归计算剩余字符串的解码方法数,并累加到__ans
__memory[__i] = __ans; // 将计算结果存储在__memory中,用于记忆化
return __ans; // 返回从当前位置开始的解码方法总数
}
void solveinit() { // 定义一个初始化函数solveinit
__n = __s.size(); // 初始化__n为字符串__s的长度
__memory.clear(); // 清空__memory向量
__memory.resize(__n + 1, -1); // 重新调整__memory大小,并初始化所有元素为-1
}
int numDecodings(string s) { // 定义一个函数numDecodings,接收一个字符串s并返回解码方法的总数
__s = s; // 将传入的字符串赋值给成员变量__s
solveinit(); // 调用solveinit函数进行初始化
return dfs(0); // 从字符串的第一个字符开始调用dfs函数,并返回解码的总数
}
};
动态规划
class Solution { // 定义一个名为Solution的类
public:
string __s; // 定义一个字符串变量__s,用于存储输入的编码字符串
int __n; // 定义一个整数变量__n,用于存储字符串的长度
vector<int> __memory; // 定义一个整数向量__memory,用于存储记忆化递归的中间结果
vector<int> __dp; // 定义一个整数向量__dp,用于存储动态规划的中间结果
int dfs(int __i) { // 定义一个名为dfs的递归函数,用于计算从索引__i开始的解码方法总数
if (__i == __n) // 如果索引__i等于字符串长度__n,表示已经达到字符串末尾
return 1; // 返回1,表示找到了一种解码方法
if (__s[__i] == '0') // 如果当前位置的字符是'0'
return 0; // 返回0,因为'0'不能解码成任何字符
if (__memory[__i] != -1) // 如果当前位置的解码方法已经被计算过
return __memory[__i]; // 直接返回记忆化的结果,避免重复计算
int __ans = 0; // 初始化解码方法数为0
__ans += dfs(__i + 1); // 递归调用dfs函数计算单个字符的解码方法,并累加到__ans
if (__i + 1 < __n && // 如果当前位置后面还有至少一个字符,并且
((__s[__i] - '0') * 10 + (__s[__i + 1] - '0')) <= 26) // 当前字符与下一字符组成的数字小于等于26
__ans += dfs(__i + 2); // 递归调用dfs函数计算两个字符的解码方法,并累加到__ans
__memory[__i] = __ans; // 将计算的结果存储在__memory中,用于记忆化
return __ans; // 返回从当前位置开始的解码方法总数
}
void solveinit() { // 定义一个初始化函数solveinit
__n = __s.size(); // 初始化__n为字符串__s的长度
__memory.clear(); // 清空__memory向量
__memory.resize(__n + 1, -1); // 重新设置__memory大小,并初始化为-1
__dp.clear(); // 清空__dp向量
__dp.resize(__n + 1, -1); // 重新设置__dp大小,并初始化为-1
__dp[__n] = 1; // 设置动态规划基准情况:字符串末尾之后的解码方法数为1
}
int numDecodings(string s) { // 定义一个名为numDecodings的函数,用于计算字符串s的解码方法总数
__s = s; // 将输入的字符串s赋值给成员变量__s
solveinit(); // 调用solveinit函数进行初始化
for (int __i = __n - 1; __i >= 0; __i--) { // 从字符串的最后一个字符向前遍历
int __ans = 0; // 初始化当前位置的解码方法数为0
if (__s[__i] == '0') { // 如果当前字符是'0'
__dp[__i] = 0; // 直接设置当前位置的解码方法数为0,因为'0'不能解码
continue; // 跳过当前循环
}
__ans += __dp[__i + 1]; // 累加单个字符的解码方法数到__ans
if (__i + 1 < __n && ((__s[__i] - '0') * 10 + (__s[__i + 1] - '0')) <= 26) {
__ans += __dp[__i + 2]; // 累加从当前位置开始的两个字符形成的数字后的解码方法数到__ans
}
__dp[__i] = __ans; // 将计算得到的总解码方法数存储在__dp数组的当前索引位置
}
return __dp[0]; // 返回从整个字符串的开始位置解码的方法总数,即动态规划数组的第一个元素的值
}
};
总结
1.
暴力递归改写记忆化递归和动态规划过程十分简单,记忆化递归可以说是动态规划的自顶向下版本,正常动态规划可以理解为自底向上版本.
2.
但不是所有的递归都可以改写为记忆化搜索和动态规划.定义的递归具有重复子问题,递归函数对应某一个位置dp值.
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!