一、题目

二、做题思路
2.1 状态表示(核心基础)
本题要求计算最长湍流子数组的长度 。湍流要求相邻比较符号交替翻转,因此需要区分最后一段比较方向。我们定义两个状态:
-
f[i]表示以arr[i]结尾,且最后一段比较为"下降"(即arr[i-1] > arr[i])的湍流子数组的最大长度。 -
g[i]表示以arr[i]结尾,且最后一段比较为"上升"(即arr[i-1] < arr[i])的湍流子数组的最大长度。
2.2 状态转移方程(关键难点)
要延长湍流子数组,当前比较必须与前一个比较方向相反:
-
若
arr[i-1] > arr[i](当前为下降),则它应接在上一个上升 状态之后,因此f[i] = g[i-1] + 1。 -
若
arr[i-1] < arr[i](当前为上升),则它应接在上一个下降 状态之后,因此g[i] = f[i-1] + 1。 -
若
arr[i-1] == arr[i],无法形成湍流,两个状态均保持为 1 (重新开始)。该方程完整体现了比较符号交替翻转的要求。
2.3 初始化(边界防护)
每个元素至少可以单独构成一个长度为 1 的湍流子数组,因此 f[i] 和 g[i] 均初始化为 1 (包括 i=0)。
2.4 填表顺序(递推方向)
每个 i 状态仅依赖 i-1 的状态,因此必须从左到右 (即 i 从 1 到 n-1)依次填充两个数组,确保每个状态计算时,其所有前置状态均已就绪。
2.5 返回值(目标映射)
题目要求返回最长湍流子数组的长度 ,即所有 f[i] 和 g[i] 中的最大值。我们在递推过程中同步维护 ret,最终返回 ret。
三、代码
cpp
class Solution {
public:
int maxTurbulenceSize(vector<int>& arr) {
int n = arr.size();
if (n == 0) return 0;
// 1. 创建dp表(两个一维数组)
// f[i] : 以 arr[i] 结尾,且最后一段比较关系为"下降"(arr[i-1] > arr[i])的湍流子数组的最大长度
// g[i] : 以 arr[i] 结尾,且最后一段比较关系为"上升"(arr[i-1] < arr[i])的湍流子数组的最大长度
vector<int> f(n, 1); // 初始化每个元素至少自身长度为1
vector<int> g(n, 1);
int ret = 1; // 全局最大湍流长度,至少为1
// 2. 初始化:dp[0] 已经为1,表示单个元素构成湍流子数组
// 3. 填表顺序:从左到右(i 从 1 到 n-1),因为 f[i]/g[i] 依赖 f[i-1]/g[i-1]
for (int i = 1; i < n; i++) {
// 4. 状态转移方程:
// 若当前比较为"下降"(前一个元素大于当前):
// 则可以从上一个"上升"状态(g[i-1])延长,长度加1
if (arr[i - 1] > arr[i]) {
f[i] = g[i - 1] + 1;
}
// 若当前比较为"上升"(前一个元素小于当前):
// 则可以从上一个"下降"状态(f[i-1])延长,长度加1
else if (arr[i - 1] < arr[i]) {
g[i] = f[i - 1] + 1;
}
// 若相等,则不满足湍流条件,f[i]和g[i]保持为1(重新开始)
// 更新全局最大值
ret = max(ret, max(f[i], g[i]));
}
// 5. 返回值:整个数组中的最长湍流子数组长度
return ret;
}
};
四、流程图

五、题目

六、做题思路
6.1 状态表示(核心基础)
本题要求判断字符串 s 能否被字典中的单词拼接而成 。我们定义 dp[i] 表示字符串 s 的前 i 个字符(即 s[0..i-1])能否被拆分成字典中的单词。
6.2 状态转移方程(关键难点)
考虑最后一个单词的起始位置 j(1 ≤ j ≤ i),那么前缀 s[0..i-1] 可拆分,当且仅当:
-
前缀
s[0..j-2]可拆分(即dp[j-1] == true),并且 -
子串
s[j-1..i-1](即第j到第i个字符)在字典中。
因此,状态转移方程为:
dp[i] = true 当且仅当存在 j ∈ [1, i],使得 dp[j-1] == true 且 s.substr(j-1, i-j+1) 在字典中 。
实际编码中,我们从 i 向前枚举 j ,一旦找到满足条件的 j,立即置 dp[i] = true 并跳出,以提升效率。
6.3 初始化(边界防护)
空串可以被拆分 ,因此 dp[0] = true 。其余 dp[i] 初始化为 false。为方便处理子串,在 s 前添加一个占位字符(如空格),使得 s[j] 对应原始字符串的第 j-1 个字符,这样取子串时 s.substr(j, i-j+1) 表示原始 s[j-1..i-1]。
6.4 填表顺序(递推方向)
dp[i] 依赖 dp[j-1](其中 j-1 < i),即更小的前缀长度 。因此必须从左到右 (即 i 从 1 到 n)依次填充 dp 表,确保每个状态计算时,其所有前置状态已就绪。
6.5 返回值(目标映射)
题目要求返回整个字符串 s 能否被拆分 ,即长度为 n 的前缀的可拆分性,对应 dp[n] 。因此直接返回 dp[n]。
七、代码
cpp
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// 1. 使用哈希集合存储单词,便于 O(1) 查询
unordered_set<string> hash;
for (const string& word : wordDict) {
hash.insert(word);
}
int n = s.size();
// 2. 创建dp表
// dp[i] 表示字符串 s 的前 i 个字符(即 s[0..i-1])能否被拆分成字典中的单词
// 为了方便处理边界,我们在 s 前面加一个空格,使索引从 1 开始对应原始字符
vector<bool> dp(n + 1, false);
// 3. 初始化:空串可以被拆分
dp[0] = true;
// 为了方便取子串,在 s 前面添加一个占位符(空格),这样 s[j] 对应原始第 j-1 个字符
// 注意:substr(j, len) 中的 j 是 1-based 索引(因为前面加了空格)
s = ' ' + s;
// 4. 填表顺序:从左到右(i 从 1 到 n),因为 dp[i] 依赖于更小的索引 dp[j-1]
for (int i = 1; i <= n; i++) {
// 内层循环从 i 向前遍历,尝试找最后一个单词的起始位置 j
// 从后向前可以尽早找到匹配并跳出(优化)
for (int j = i; j >= 1; j--) {
// 5. 状态转移方程:
// 如果前 j-1 个字符可以拆分(dp[j-1] == true),
// 并且子串 s[j..i] 在字典中,则 dp[i] = true
if (dp[j - 1] && hash.count(s.substr(j, i - j + 1))) {
dp[i] = true;
break; // 一旦成立,无需继续尝试其他 j
}
}
}
// 6. 返回值:dp[n] 表示整个字符串是否能被拆分
return dp[n];
}
};
八、流程图
