「知识点:栈」
从前向后遍历字符串,只考虑 (
和 )
:
(
,则入栈。)
,则从栈中弹出一个 (
那么在遍历过程中,栈中元素数量的最大值即为答案。栈中的(
可以理解为还没遍历到匹配的)
,即那些嵌套的(
。
class Solution {
public:
int maxDepth(string s) {
int depth = 0; // 并不需要一个 std::stack,只维护一下元素数量就可以了。
int anw = 0; // 记录一下 depth 的最大值。
for(auto c : s) {
if(c == '(') {
depth ++;
} else if(c == ')') {
depth --;
}
anw = max(anw, depth);
}
return anw;
}
};
「知识点:容斥」
设与点p
直接相连边的数量为
。
枚举所有点对
,其秩为
对于
,可以通过遍历一次 roads
搞定~
class Solution {
public:
int maximalNetworkRank(int n, vector<vector<int>>& roads) {
int anw = 0;
vector<vector<int>> cnt(n, vector<int>(n, 0));
vector<int> cl(n, 0), cr(n, 0);
for(const auto &r : roads) {
cnt[r[0]][r[1]]++; // 标记是否存在边(u,v)
cnt[r[1]][r[0]]++;
cl[r[0]]++; // 预处理与 p 直接相连边的数量
cl[r[1]]++;
cr[r[0]]++;
cr[r[1]]++;
}
for(int i = 0; i < n; i++) {
for(int j = i+1; j < n; j++) {
anw = max(anw, cl[i] + cr[j] - cnt[i][j]);
}
}
return anw;
}
};
「知识点:回文串」
设字符串长度为 l。如果 A,B 分割后可拼接为回文串,则必然存在
,满足下述两个要求:
前缀
和B中长度为n的 后缀
互为镜像, 或者,A中长度为n的 后缀
和B中长度为n的 前缀
互为镜像l-2n的中间部分
是一个回文串, 或者,B中长度为 l-2n的中间部分
是一个回文串。可以先找出符合上述要求的最大的 n
,然后判断剩下的长度为 l-2n 的中间部分
是否为回文串。
class Solution {
public:
bool isPalindrome(const std::string &a, int l, int r) {
// 判断中间部分是否为回文串
while(l < r) {
if(a[l] != a[r]) {
return false;
}
l++;
r--;
}
return true;
}
bool check(const string &a, const string &b) {
// 从 a 中选取前缀, 从 b 中选取后缀
for(int i = 0, j = a.size()-1; i < j; i++, j--) {
if(a[i] != b[j]) {
if(isPalindrome(a, i, j) || isPalindrome(b, i, j)) {
return true;
} else {
break;
}
}
if(j-i <= 2) { // 中间部分为空字符串或者为单字符的情况
return true;
}
}
// 从 b 中选取前缀, 从 a 中选取后缀
for(int i = 0, j = a.size()-1; i < j; i++, j--) {
if(b[i] != a[j]) {
return isPalindrome(a, i, j) || isPalindrome(b, i, j);
}
if(j-i <= 2) { // 中间部分为空字符串或者为单字符的情况
return true;
}
}
return true;
}
bool checkPalindromeFormation(string a, string b) {
return check(a, b);
}
};
「知识点:状态压缩,树的遍历」
首先,借助状态压缩的技巧,枚举点的子集s
。其次,检查s
是否可以组成一颗子树。最后,计算s
对应子树的直径,即城市之间的最大距离。
计算树的直径:因为数据量比较小,直接暴力搞吧,详见代码。(比较高级的做法是树形DP,老铁可以自行百度)。
class Solution {
public:
vector<int> vec[20];
vector<int> anw;
bool getPath(int s, int pre, int t, vector<int> &path) {
if(s == t) {
return true;
}
for(auto c : vec[s]) {
if(c == pre) {
continue;
}
if(getPath(c, s, t, path)) {
path.push_back(s);
return true;
}
}
return false;
}
int getDis(int s, int t, int status) {
vector<int> path;
// dfs 获取 (s, t) 之间的路径
getPath(s, -1, t, path);
// 判断路径上的点是否都在点集内
for(auto c : path) {
if((status&(1<<(c-1))) == 0) {
return -1;
}
}
return path.size();
}
int get(int n, int status) {
int maxDis = 0;
for(int i = 1, bi = 1; i <= n; i++, bi <<= 1) {
if((bi&status) == 0) {
continue;
}
for(int j = i+1, bj = bi<<1; j <= n; j++, bj <<= 1) {
// 两层 for 循环,枚举点对 (i,j)
if((bj&status) == 0) {
continue;
}
// 获取 (i,j)之间的路径长度,并判断路径上的点是否都在该点集内。
int tmpDis = getDis(i, j, status);
// tmpDis 不超过 0,说明status不能构成一棵子树
if(tmpDis <= 0) {
return 0;
}
maxDis = max(maxDis, tmpDis);
}
}
return maxDis;
}
vector<int> countSubgraphsForEachDiameter(int n, vector<vector<int>>& edges) {
// 先搞个邻接表
for(const auto &pair : edges) {
vec[pair[0]].push_back(pair[1]);
vec[pair[1]].push_back(pair[0]);
}
// 初始化答案
anw.resize(n-1);
//因为至少应该包含两个点,所以从 3 开始枚举
for(int i = 3, mask = 1<< n; i < mask; i++) {
int dis = get(n, i);
//利用 dis <= 0 表示点集不能构成子树
if(dis <= 0) {
continue;
}
anw[dis-1] ++;
}
return anw;
}
};