[高级数据结构] 4. 习题课
作者:互联网
马拉车算法
- 解决回文串问题
- 处理回文串的四种算法
- 暴力匹配
2种情况:1种是以某个字符为对称中心,1种是以间隙(2个字母之间)为对称中心
- 暴力匹配
int b[100];
int d[100]; // i + 1
for (int i = 0; i < size(str); i++) {
b[i] = 1; // i为中心的回文长度是它本身字符
while (i - b[i] >= 0 && i + b[i] < n && str[i - b[i]] == str[i + b[i]]) {
b[i]++;
}
}
- 二分+哈希
- 马拉车
- 回文自动机
Leetcode 5.最长回文子串
class Solution {
public:
// 预处理:统一奇数、偶数串为奇数串
string getNewString(string s) {
string ns = "#";
for (int i = 0; s[i]; i++) {
(ns += s[i]) += "#"; // s[i] -> ns, '#' -> ns
}
return ns;
}
string longestPalindrome(string s) {
if (s.size() == 0) return "";
string ns = getNewString(s);
vector<int> dis(ns.size()); // d[]数组,求以i为中心最长得回文子串长度
int l = 0, r = -1; // 错开,无交集
for (int i = 0; i < ns.size(); i++) {
if (i > r) dis[i] = 1;
else dis[i] = min(dis[l + r - i], r - i); // j = l + r - i;
// 暴力匹配
while (i - dis[i] >= 0 && i + dis[i] < ns.size() && ns[i - dis[i]] == ns[i + dis[i]]) {
dis[i]++;
}
// 更新l, r
if (i + dis[i] > r && i - dis[i] > 0) {
l = i - dis[i];
r = i + dis[i];
}
}
// 输出最长回文子串
string ret;
int tmp = 0; // 暂存长度
for (int i = 0; ns[i]; i++) {
if (tmp >= dis[i]) continue;
tmp = dis[i];
ret = "";
for (int j = i - dis[i] + 1; j < i + dis[i]; j++) {
if (ns[j] == '#') continue;
ret += ns[j];
}
}
return ret;
}
};
-
马拉车算法:暴力匹配 [ 复杂度o(n2) ] 的升级版, o(n) 解决 最大回文子串 问题
-
应用到缓存思想,减少一段暴力匹配,利用到前面已经求得的信息
-
求d[ ]数组
- [ l , r ] 最初暴力求得,后续维护这个区间
- d[k] 以k为中心最长的回文长度,其中l,r暴力求得,d[k] = (r - l)
- 核心公式
- i <= r时,d[i] = min(r - i, d[j]) ( l<j<k<i<r ),因为j,i关于k对称且d[j]已知
- i > r时,朴素实现
-
填充#,字符串长度n变为2n+1,一定是奇数,避免两次循环(以字符为中心,以2个字符之间为中心)
[并查集预习课] 游戏分组
#include <stdio.h>
#include <stdlib.h>
#define maxn 1000010 //c不支持 const int maxn = 。。。写法
int p[maxn];
int find(int x) {
if (p[x] != x) return p[x] = find(p[x]);
return p[x];
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) p[fx] = fy;
return ;
}
int main() {
int n, m, a, b;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
p[i] = i;
}
for (int i = 0; i < m; i++) {
scanf("%d%d", &a, &b);
merge(a, b);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += (p[i] == i); // 判断多少组
}
printf("%d\n", ans);
return 0;
}
- c不支持 const int maxn 写法,使用 #define
- 判断存在多少组:ans += (p[i] == i)
leetcode 685. 冗余连接 II
class Solution {
public:
/*
思路:逐个删除边,判断剩下的图,是不是一个有向树
判断思路:
1.有且只有一个节点入度为0 -> 集合无环 使用并查集判断,新插入节点已经在集合中时形成闭环
2.集合中没有节点的入度为2
*/
// p[]并查集. vis[]入度数组(n <= 1000), flag1->条件1, flag2->条件2
int p[2000], vis[2000], flag1 = 0, flag2 = 0;
int find(int x) {
if (p[x] != x) return p[x] = find(p[x]);
return p[x];
}
void union_find(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx != fy) p[fx] = fy;
else flag1 = 1; // 闭环,条件1不满足
}
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
// 从后往前遍历:返回最后出现在给定二维数组的答案
for (int i = edges.size() - 1; i >= 0; i--) { // 逐个删除边
flag1 = 0, flag2 = 0;
int x = edges[i][0]; // 起点
int y = edges[i][1]; // 终点
memset(vis, 0, sizeof(vis));
for (int k = 0; k < 2000; k++) {
p[k] = k;
vis[k] = 0; // 入度数组,初始化为0
}
for (int j = 0; j < edges.size(); j++) {
if (edges[j][0] == x && edges[j][1] == y) continue;
union_find(edges[j][0], edges[j][1]);
if (flag1 == 1) break;
// 入度+1
vis[edges[j][1]]++;
if (vis[edges[j][1]] > 1) {
flag2 = 1;
break;
}
}
if (flag1 == 0 && flag2 == 0) {
return {x, y};
}
}
return edges[0]; // 随便返回一个值,要不编译不过
}
};
- 思路:逐个删除边,判断剩下的图,是不是一个有向树
- 判断思路:
- 有且只有一个节点入度为0 -> 集合无环 使用并查集判断,新插入节点已经在
- 集合中没有节点的入度为2
[图论预习课] 灌溉
- 最小生成树- 克鲁斯卡尔算法(贪心选边),适合邻接表
- sort 对边
- 遍历所有边
- 当前最短边的点加入并查集,如果点在集合中则跳过该边继续遍历
- 最小生成树- prim算法(贪心选点),适合邻接矩阵
- 离当前集合最近的点加入
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1e4 + 10;
int p[maxn];
struct edge {
int u, v, c;
};
vector<edge> e;
bool cmp(edge x, edge y) {
return x.c < y.c;
}
int find(int x) {
if (p[x] != x) return p[x] = find(p[x]);
return p[x];
}
int union_find(int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) {
p[fx] = fy;
return 1;
}
return 0;
}
int main() {
int n, x;
scanf("%d", &n);
for (int i = 0; i <= n; i++) {
p[i] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &x);
if (x == 0) continue;
e.push_back((edge){i, j, x});
}
}
sort(e.begin(), e.end(), cmp);
int ans = 0;
for (int i = 0; i < e.size(); i++) {
int v = e[i].v, u = e[i].u, c = e[i].c;
if (union_find(v, u)) {
ans += c;
}
}
printf("%d\n", ans);
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <sstream>
#include <map>
#include <vector>
#include <set>
#include <unordered_map>
#include <time.h>
#include <stdint.h>
#include <queue>
#include <unordered_set>
#include <stack>
using namespace std;
const int maxn = 500 + 10;
const int inf = 0x3f3f3f3f;
int n, x;
int mp[maxn][maxn]; // 邻接矩阵
int vis[maxn], dis[maxn]; // vis 哪些点在G集合, 哪些在T集合, dis G中与T可达的点距离
void prim() {
vis[1] = 1; // 1节点加入到我们的T集合中去
for(int i = 1; i <= n; i++) {
dis[i] = mp[1][i]; // G集合中的点到T集合中的距离
}
for(int i = 2; i <= n; i++) { // 遍历所有的点
int minn = inf, v = -1; // v是我们找到的点
for(int j = 1; j <= n; j++) {
if(!vis[j] && minn > dis[j]) {
minn = dis[j];
v = j;
}
}
vis[v] = 1;
// 遍历所有非T集合中的点, 更新这些点到集合中的距离
for(int j = 1; j <= n; j++) {
if(!vis[j] && dis[j] > mp[v][j]) {
dis[j] = mp[v][j];
}
}
}
int sum = 0;
for(int i = 1; i <= n; i++) {
sum += dis[i];
}
printf("%d\n", sum);
return ;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &x);
mp[i][j] = x;
}
}
prim();
return 0;
}
标签:数据结构,return,int,高级,++,习题课,include,find,dis 来源: https://blog.csdn.net/weixin_44506866/article/details/118983822