一、面试题 08.01. 三步问题 - 力扣(LeetCode)
算法代码:
class Solution {
public:const int MOD = 1e9 + 7;int waysToStep(int n) {// 1. 创建 dp 表// 2. 初始化// 3. 填表// 4. 返回// 处理边界情况if (n == 1 || n == 2)return n;if (n == 3)return 4;vector<int> dp(n + 1);dp[1] = 1, dp[2] = 2, dp[3] = 4;for (int i = 4; i <= n; i++)dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;return dp[n];}
};
这段代码的目的是计算上楼梯的方式数,每次可以走1步、2步或3步。代码使用了动态规划(DP)来解决这个问题。下面是代码的详细思路:
1. 创建 DP 表
-
使用一个数组
dp
来存储每个台阶对应的上楼梯方式数。 -
dp[i]
表示上到第i
个台阶的方式数。
2. 初始化
-
对于前三个台阶,直接初始化:
-
dp[1] = 1
:只有一种方式(走1步)。 -
dp[2] = 2
:有两种方式(1+1 或 2)。 -
dp[3] = 4
:有四种方式(1+1+1, 1+2, 2+1, 3)。
-
3. 填表
-
从第4个台阶开始,每个台阶的方式数等于前三个台阶方式数的和:
-
dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
-
-
由于结果可能非常大,每次计算后都对
MOD
取模,防止溢出。
4. 返回结果
-
最终返回
dp[n]
,即上到第n
个台阶的方式数。
边界情况处理
-
如果
n
是1、2或3,直接返回对应的值,避免不必要的计算。
代码优化
-
由于每次只需要前三个状态,可以优化空间复杂度,使用三个变量代替整个
dp
数组。
优化后的代码
class Solution {
public:const int MOD = 1e9 + 7;int waysToStep(int n) {if (n == 1 || n == 2)return n;if (n == 3)return 4;int a = 1, b = 2, c = 4; // 分别代表 dp[i-3], dp[i-2], dp[i-1]int result = 0;for (int i = 4; i <= n; i++) {result = ((a + b) % MOD + c) % MOD;a = b;b = c;c = result;}return result;}
};
优化思路
-
使用三个变量
a
,b
,c
来存储前三个状态,减少空间复杂度。 -
每次更新
result
后,滚动更新a
,b
,c
的值。
这样,代码的空间复杂度从 O(n)
降低到了 O(1)
,同时保持了时间复杂度的 O(n)
。
二、5. 最长回文子串 - 力扣(LeetCode)
算法代码:
class Solution {
public:string longestPalindrome(string s) {// 中⼼扩展算法int begin = 0, len = 0, n = s.size();for (int i = 0; i < n; i++) // 依次枚举所有的中点{// 先做⼀次奇数⻓度的扩展int left = i, right = i;while (left >= 0 && right < n && s[left] == s[right]) {left--;right++;}if (right - left - 1 > len) {begin = left + 1;len = right - left - 1;}// 偶数⻓度的扩展left = i, right = i + 1;while (left >= 0 && right < n && s[left] == s[right]) {left--;right++;}if (right - left - 1 > len) {begin = left + 1;len = right - left - 1;}}return s.substr(begin, len);}
};
这段代码的目的是找到字符串 s
中的最长回文子串。它使用了中心扩展算法,通过枚举每个可能的中心点,向左右扩展,找到最长的回文子串。以下是代码的详细思路:
1. 中心扩展算法的核心思想
-
回文串可以分为两种:
-
奇数长度:中心是一个字符(例如
"aba"
)。 -
偶数长度:中心是两个字符(例如
"abba"
)。
-
-
通过枚举每个可能的中心点,向左右扩展,找到以该中心点为基准的最长回文子串。
2. 代码实现思路
变量说明
-
begin
:记录最长回文子串的起始位置。 -
len
:记录最长回文子串的长度。 -
n
:字符串s
的长度。
步骤
-
枚举所有可能的中心点:
-
遍历字符串
s
,每个字符s[i]
都可能是回文串的中心点。
-
-
奇数长度的扩展:
-
初始化
left
和right
为当前中心点i
。 -
向左右扩展,检查
s[left]
和s[right]
是否相等。 -
如果相等,继续扩展;否则停止。
-
计算当前回文子串的长度
right - left - 1
,如果比len
大,则更新begin
和len
。
-
-
偶数长度的扩展:
-
初始化
left
为i
,right
为i + 1
(表示中心是两个字符)。 -
向左右扩展,检查
s[left]
和s[right]
是否相等。 -
如果相等,继续扩展;否则停止。
-
计算当前回文子串的长度
right - left - 1
,如果比len
大,则更新begin
和len
。
-
-
返回结果:
-
使用
s.substr(begin, len)
截取最长回文子串并返回。
-
3. 代码优化
-
时间复杂度:
O(n^2)
,其中n
是字符串的长度。每个中心点最多扩展n
次。 -
空间复杂度:
O(1)
,只使用了常数级别的额外空间。
4. 优化后的代码
class Solution {
public:string longestPalindrome(string s) {int begin = 0, len = 0, n = s.size();for (int i = 0; i < n; i++) {// 奇数长度扩展int l = i, r = i;expandAroundCenter(s, l, r, begin, len);// 偶数长度扩展l = i, r = i + 1;expandAroundCenter(s, l, r, begin, len);}return s.substr(begin, len);}private:void expandAroundCenter(const string& s, int l, int r, int& begin, int& len) {while (l >= 0 && r < s.size() && s[l] == s[r]) {l--;r++;}int currentLen = r - l - 1;if (currentLen > len) {begin = l + 1;len = currentLen;}}
};
5. 优化思路
-
将奇数长度和偶数长度的扩展逻辑提取到一个单独的函数
expandAroundCenter
中,避免代码重复。 -
通过传递
begin
和len
的引用,直接更新最长回文子串的起始位置和长度。
6. 示例运行
输入:
string s = "babad";
Solution sol;
cout << sol.longestPalindrome(s); // 输出 "bab" 或 "aba"
运行过程:
-
枚举中心点
i
:-
i = 0
:奇数扩展得到"b"
,偶数扩展得到""
。 -
i = 1
:奇数扩展得到"bab"
,偶数扩展得到""
。 -
i = 2
:奇数扩展得到"aba"
,偶数扩展得到""
。 -
i = 3
:奇数扩展得到"a"
,偶数扩展得到""
。 -
i = 4
:奇数扩展得到"d"
,偶数扩展得到""
。
-
-
最终最长回文子串为
"bab"
或"aba"
。
7. 总结
-
中心扩展算法是一种直观且高效的方法,适用于解决最长回文子串问题。
-
通过优化代码结构,可以提高代码的可读性和可维护性。