week14
131 - 分割回文串
这里有一个很重要的思想,预处理减少复杂度;
这里可以用递推的思想把判断回文串的步骤提前判断出来; 从f[i, j] 表示 s[i ... j]是回文子串(闭区间)
f[i, j] 可以由 f[i + 1, j - 1] 递推而来 只要满足 s[i+1 ... j-1] 是回文子串并且s[i] == s[j]即可
这样再dfs搜索分界线即可。
class Solution {
public:
int n;
vector<vector<string>> res;
vector<vector<bool>> f;
vector<string> path;
public:
vector<vector<string>> partition(string s) {
n = s.size();
f = vector<vector<bool>>(n, vector<bool>(n));
// 注意这里的枚举顺序
for(int j = 0; j < n; j++)
for(int i = 0; i <= j; i++)
{
if (i == j)
f[i][j] = true; // 只有一个字母
else if (s[i] == s[j])
{
if (i + 1 > j - 1 || f[i + 1][j - 1]) // 只有两个字母 或者 f[i+1][j-1]为true
f[i][j] = true;
}
}
dfs(s, 0);
return res;
}
void dfs(string& s, int u)
{
if(u == n) res.push_back(path);
else
{
for(int i = u; i < n; i++)
{
if(f[u][i])
{
path.push_back(s.substr(u, i - u + 1));
dfs(s, i + 1);
path.pop_back();
}
}
}
}
};132 - 分割回文串ii
这里第一步 预处理回文串判断和上面一题是一样的;
接下来的动态规划的状态f[i] 表示 s[1 ... i]中满足都为回文子串的所有分割方式,值为最小的子块数量;
显然 f[i] 可以在所有满足s[k ... i]为回文子串的状态中转移过来, f[i] = min(f[k - 1] + 1)
最后的答案是f[n] - 1 分割次数比子块数量少1;
133 - 克隆图
分为两次 先拷贝点 再拷贝边;
拷贝点要建立原始点和复制点的映射关系,遍历一遍图 (BFS/DFS) 注意遍历图要判重!
然后拷贝每个点的边就可以了
134 - 加油站
首先对于这种环状数组问题常见的操作方式就是复制一遍 破环成链;
[1,2,3,4,5]变成[1,2,3,4,5,1,2,3,4,5], 再用一个长度为n的窗口 即为从不同起点开始的路径。
每一个加油站i 能到达的花费实际上是gas[i] - cost[i] 才能开到下一站
整段路程的花费实际上就是每一小段的花费的前缀和s[j] - s[i],只要满足前缀和的最小值大于等于0就证明可以完整走完这一段路程。
转化为滑动窗口求最小值的问题:单调队列
枚举起点i, 对于s[j] - s[i] 而言 i 不变 实际上找的就是s[j]的在[i ... i + n - 1]中的最小值,这样我们需要从后往前维护单调队列才行(从前往后 i 后面的还没见过)
这样最小值即为队首元素s[q[hh]]
135 - 分发糖果

这题实际上就是 每个小朋友得到的最少糖果数是根据左右两边 连续下降的次数 决定的; 比如图上的情况 这个小朋友至少要得到3个 才能满足要求;
按照这个想法, 分别统计左右两边 连续下降的次数 最后加起来得到答案即可
136 - 只出现一次的数字
相同两个数的异或值为0
所以所有的数异或操作后,剩下的一个数字就是只出现一次的了;
137 - 只出现一次的数字ii

主要是这个按位的状态转移方程怎么来的很奇怪
one = (one ^ x) & (~two)two = (two ^ x) & (~one)
理解这么做是对的就好了
138 - 复制带随机指针的链表
这个题和上一个克隆图是一样的 也是分两步 先复制Node 再复制指针
同样也是建立一个原始Node 和 复制Node 的hash表
但这个需要额外开一个哈希表去记录对应关系, 根据链表的特性可以节省这个空间;

139 - 单词拆分
简单的动态规划,这里去substr带来的复杂度同样可以用字符串哈希来处理
140 - 单词拆分ii
上面的基础上额外记录一个从那个状态转移而来的,最后dfs枚举所有的组合即可;
Last updated