复习一下链式前向星
注意题目要求的是拓扑序严格大于的数,这个时候按顺序统计的拓扑序并不是严格大于的关系,因此要在拓扑图中不断更新
class Solution {
const static int N = 510 * 510;
public:
int ne[N], idx, h[N], e[N], ind[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
//更有钱且最安静的人
//寻找拓扑序,然后找到更安静的人
//b ---> a
idx = 0;
memset(h, -1, sizeof h);
int n = quiet.size();
for(int i = 0; i < richer.size(); i ++ )
{
int a = richer[i][0], b = richer[i][1];
add(a, b);//a到b的一条边
ind[b] ++;
}
vector<int> ans(n);
queue<int> q;
for(int i = 0; i < n; i ++ )
{
if(!ind[i]) q.push(i);
ans[i] = i;//初始答案
}
while(q.size())
{
int u = q.front();
q.pop();
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];//u比j更有钱
ind[j] --;
if(quiet[ans[j]] > quiet[ans[u]])//u更加安静
ans[j] = ans[u];
if(!ind[j]) q.push(j);
}
}
return ans;
}
};
搜索版本,对每个点搜索拓扑序比他大的点,更新最小quiet
class Solution {
public:
vector<int> loudAndRich(vector<vector<int>> &richer, vector<int> &quiet) {
int n = quiet.size();
vector<vector<int>> g(n);
for (auto &r : richer) {
g[r[1]].emplace_back(r[0]);
}
vector<int> ans(n, -1);
function<void(int)> dfs = [&](int x) {
if (ans[x] != -1) {
return;
}
ans[x] = x;
for (int y : g[x]) {
dfs(y);
if (quiet[ans[y]] < quiet[ans[x]]) {
ans[x] = ans[y];
}
}
};
for (int i = 0; i < n; ++i) {
dfs(i);
}
return ans;
}
};
对于无向图,如何进行拓扑排序,可以设置入度最小为1,这时拓扑序是外层小于内层。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
//最大值最小
//删除叶子节点,最终会剩下一个点或者两个点,如果有第三个点,那么可以删除
vector<int> g[n];
vector<int> ind(n);
for(int i = 0; i < n - 1; i ++ )
{
g[edges[i][0]].push_back(edges[i][1]);
g[edges[i][1]].push_back(edges[i][0]);
ind[edges[i][0]] ++;
ind[edges[i][1]] ++;
}
queue<int> q;
vector<bool> st(n);
vector<int> dist(n);
int cnt = 0;
for(int i = 0; i < n; i ++ )
{
if(ind[i] == 1)
{
q.push(i);
dist[i] = 0;
}
}
while(n - (cnt ++) > 2)//最终会剩余两个或者一个,这时到达了最内层
{
int u = q.front();
q.pop();
st[u] = true; // 删除
for(int i = 0; i < g[u].size(); i ++ )
{
int j = g[u][i];
if(!st[j])//为了防止反复,经过的不会再经过
{
dist[j] = max(dist[j], dist[u] + 1);
if(-- ind[j] == 1) q.push(j);
}
}
}
int res = *max_element(dist.begin(), dist.end());
vector<int> ans;
for(int i = 0; i < n; i ++ )
if(dist[i] == res) ans.push_back(i);
return ans;
}
};
反向建图 + 拓扑排序
一个节点不会进入环则是安全的,正向无法保证这个点的入度为0,反向建图,从终点开始拓扑排序,找到所有满足要求的点
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
//找到拓扑图的所有点
int n = graph.size();
vector<int> ind(n);
vector<int> g[n];
for(int i = 0; i < n; i ++ )
{
for(int j = 0; j < graph[i].size(); j ++ )
{
ind[i] ++;
g[graph[i][j]].push_back(i);//反向边
}
}
vector<int> ans;
queue<int> q;
for(int i = 0; i < n; i ++ )
{
if(!ind[i])
{
q.push(i);
}
}
while(q.size())
{
auto u = q.front();
q.pop();
ans.push_back(u);
for(int i = 0; i < g[u].size(); i ++ )
{
int j = g[u][i];
ind[j] --;
if(!ind[j]) q.push(j);
}
}
sort(ans.begin(), ans.end());
return ans;
}
};
该点的盛的雨水取决于周围边界的最小值,最短路的思路,从外圈最小高度不断扩展,
class Solution {
public:
typedef pair<int, pair<int,int>> PIII;
priority_queue<PIII, vector<PIII>, greater<PIII>> q;
bool st[201][201];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int trapRainWater(vector<vector<int>>& heightMap) {
int n = heightMap.size(), m = heightMap[0].size();
int ans = 0;
for(int i = 0; i < n; i ++ ){
for(int j = 0; j < m; j ++ ){
if(i == 0 || i == n - 1 || j == 0 || j == m - 1){
q.push({heightMap[i][j], {i, j}});
st[i][j] = true;
}
}
}
while(q.size()){
auto t = q.top();
int h = t.first, x = t.second.first, y = t.second.second;
q.pop();
for(int i = 0; i < 4; i ++ ){
int nx = dx[i] + x, ny = dy[i] + y;
if(nx < 0 || ny < 0 || nx >= n || ny >= m || st[nx][ny]) continue;
if(heightMap[nx][ny] < h){
ans += h - heightMap[nx][ny];
}
st[nx][ny] = true;
q.push({max(heightMap[nx][ny], h), {nx, ny}});
}
}
return ans;
}
};
并查集的思路:
由于数值的范围在20000以内,可以用并查集搞。
把要流出去的点和n * m相连。从0到最高点,依次考虑每个高度。
假设当前考虑到高度为v的方块,枚举所有高度为v的方块,计算有多少个v的块可以填一个高度的水。
由于所以低于这个方块的都已经考虑过,直接看周围是否有已经vis过的方块,然后把他们和当前块连起来,表示往当前块上放水会流向这些块。
对于这一层的答案,就是高度为v的块 - 要流出去的块。
详细来说 对于给定高度v
的块,我们给遍历到的全部格子加上v+1
的水量,因为高于v
的格子不会有水的增加,检查四周的格子是否遍历过,如果这个格子已经是边界,那么表示增加的水会流出来,如果四周是遍历过表示水会从这个格子流向四周这些格子,如果这些格子有流向边界的,那么这些格子的水都会流出边界。最终对于每个水位增加的水量为cnt - sz[find(n*m)]
,这是遍历到的格子增加水量,但是有的格子会从边界流出,边界点为n * m
class Solution {
public:
int f[220 * 220], vis[220 * 220], sz[220 * 220];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int n, m;
int find(int x )
{
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
bool check(int x, int y)
{
if(x < 0 || x >= n || y < 0 || y >= m) return 0;
return 1;
}
int trapRainWater(vector<vector<int>>& heightMap) {
n = heightMap.size(), m = heightMap[0].size();
for(int i = 0; i <= n * m; i ++ ) f[i] = i, sz[i] = 1;
sz[n * m] = 0;
int V = 0;
vector<int> hp[20010];
for(int i = 0; i < n; i ++ )
for(int j = 0; j < m; j ++ )
{
hp[heightMap[i][j]].push_back(i * m + j);
V = max(heightMap[i][j], V);
}
int cnt = 0, res = 0;
for(int v = 0; v < V; v ++ )
{
for(int i = 0; i < hp[v].size(); i ++ )
{
cnt ++;
int x = hp[v][i] / m, y = hp[v][i] % m;
vis[x * m + y] = 1;
for(int k = 0; k < 4; k ++ )
{
int nx = x + dx[k], ny = y + dy[k];
int flag = 0;
if(!check(nx, ny) || vis[nx * m + ny]) //四周有处理过或者是边界
{
int fx, fy;
if(!check(nx, ny)) fx = find(n * m);
else fx = find(nx * m + ny);
fy = find(x * m + y);
if(fx == fy) continue;
sz[fx] += sz[fy];
f[fy] = fx;
}
}
}
cout << cnt << " " << sz[find(n * m)] << endl;
res += cnt - sz[find(n * m)];
}
return res;
}
};
我使用了prim算法,这样不用建立并查集,但是因为不是稠密图,所以效率不高
class Solution {
public:
int dist[10010];
int st[10010];
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
int minimumEffortPath(vector<vector<int>>& heights) {
//找到一条路径,它的高度差最大值最小
//二分答案,找到合适的dfs路径
//对于所有的边,从小到大,并查集直到两个点相连。
//从左上角开始,每次寻找离自己这个集合最近的一个点,
int n = heights.size(), m = heights[0].size();
int ans = 0;
memset(dist, 0x3f, sizeof(dist));
dist[0] = 0;
for(int i = 1; i <= n * m; i ++ )//最多需要加入nm个点
{
int t = -1;
for(int j = 0; j < n * m; j ++ )
{
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
}
ans = max(ans, dist[t]);//记录所有加入的点的最大路径值
st[t] = true;
if(t == n * m - 1) break;
int x = t / m, y = t % m;//这个点是要加入的点
for(int j = 0; j < 4; j ++ )//更新一下周围的点
{
int nx = x + dx[j], ny = y + dy[j];
if(nx < 0 || ny < 0 || nx >= n || ny >= m) continue;
dist[nx * m + ny] = min(dist[nx * m + ny], abs(heights[nx][ny] - heights[x][y]));
}
}
return ans;
}
};
堆+广度优先搜索,从最小的dep寻找是否能到达四周的游泳池,类似于dijkstra算法
class Solution {
public:
typedef pair<int,int> PII;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int swimInWater(vector<vector<int>>& grid) {
//深度优先搜索
int n = grid.size(), m = grid[0].size();// x * m + y
priority_queue <PII, vector<PII>, greater<PII> > q;
vector<int> st(m * n);
q.push({grid[0][0], 0});
st[0] = true;
int ans;
int cnt = 0;
while(!q.empty())
{
cnt ++;
auto t = q.top();
q.pop();
int dep = t.first, u = t.second;
if(u == m * n - 1)
{
ans = dep;
break;
}
for(int i = 0; i < 4; i ++ )
{
int nx = dx[i] + u / m;
int ny = dy[i] + u % m;
if(nx < 0 || ny < 0 || nx >= n || ny >= m) continue;
if(st[nx * m + ny]) continue;//只会入队一次
q.push({max(dep, grid[nx][ny]), nx * m + ny});
st[nx * m + ny] = true;
}
}
return ans;
}
};
从小到大,如果四周有比它小的那就是可以游到,知道可以游到终点。
class Solution {
public:
int f[2510];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int find(int x)
{
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
int swimInWater(vector<vector<int>>& grid) {
//从最小的开始,每次将这个点和周围比他小的点连接起来,直到起点和终点连接起来了
vector<pair<int, int>> nums;
int n = grid.size();
for(int i = 0; i < n * n; i ++ ) f[i] = i;
for(int i = 0; i < n; i ++ )
for(int j = 0; j < n; j ++ )
{
nums.push_back({grid[i][j], i * n + j});
}
sort(nums.begin(), nums.end());//从小到大
int ans;
for(int i = 0; i < nums.size(); i ++ )
{
int dep = nums[i].first, u = nums[i].second;
int a = find(u);
for(int j = 0; j < 4; j ++ )
{
int nx = dx[j] + u / n;
int ny = dy[j] + u % n;
if(nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
if(dep > grid[nx][ny])
{
int b = find(nx * n + ny);
f[b] = a;
}
}
if(find(0) == find(n * n - 1))
{
ans = dep;
break;
}
}
return ans;
}
};
最短路问题
class Solution {
public:
int dist[110][110];
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
//k -- 所有
memset(dist, 0x3f, sizeof(dist));
for(int i = 0; i < times.size(); i ++)
{
int u = times[i][0], to = times[i][1], w = times[i][2];
dist[u][to] = w;
}
for(int k = 1; k <= n; k ++ )
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
{
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
int ans = 0;
dist[k][k] = 0;
for(int i = 1; i <= n; i ++ )
{
ans = max(ans, dist[k][i]);
}
if(ans == 0x3f3f3f3f) return -1;
return ans;
}
};
裂开解法
可知Floyd算法是经过中转以后的最短路求解。那么可以算出1、3、7、15、31等次中转的最短路,这样得到k次的最短路就是答案。
注意点:
(2n + 1)
次中转的结果,也就是0,1,3,7,15,31,所以k&1
时还要再算一步class Solution {
public:
int dp[200][200];
int dp2[200][200];
int n;
int findCheapestPrice(int _n, vector<vector<int>>& flights, int src, int dst, int K) {
n = _n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
{
if(i != j)
dp[i][j] = 1e9;
}
for(int i = 0; i < flights.size(); i ++)
{
int x = flights[i][0], y = flights[i][1], w = flights[i][2];
dp[x][y] = min(dp[x][y], w);
}
memcpy(dp2, dp, sizeof(dp));
while(K)//快速幂求出经过k次中转的最短路
{
if(K & 1) qsm(dp, dp2);
qsm(dp2, dp2);
K >>= 1;
}
return dp[src][dst] == 1e9 ? -1: dp[src][dst];
}
void qsm(int a[][200], int b[][200])//a不知道中转了几次,b不知道中转了几次,总共中转了加起来
{
static int temp[200][200];
memset(temp, 0x3f, sizeof(temp));
for(int m = 0; m < n; m++)
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
{
if(a[i][m] + b[m][j] <= a[i][j])//小于等于可以围着自己中转
{
temp[i][j] = min(temp[i][j], a[i][m] + b[m][j]);
}
}
memcpy(a, temp, sizeof(temp));//指定的大小是temp的大小
}
};
这样算出的结果是所有点对的最短路,但是题目要求只要源节点到目的节点的最短路,这样的话不用枚举起点的终点,只要bellman算法松弛k + 1次即可
class Solution {
public:
int dist[200][200];
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
memset(dist, 0x3f, sizeof dist);
dist[0][src] = 0;
for(int i = 1; i <= k + 1; i ++ )
{
for(auto &t : flights)
{
dist[i][t[1]] = min({dist[i][t[1]], dist[i - 1][t[1]], dist[i - 1][t[0]] + t[2]});
}
}
return dist[k + 1][dst] == 0x3f3f3f3f ? -1 : dist[k + 1][dst];
}
};
class Solution {
public:
const int mod = 1e9 + 7;
typedef pair<int, int> PII;
int countRestrictedPaths(int n, vector<vector<int>>& edges) {
vector<vector<PII>> g(n + 1);
for(auto &e :edges)
{
int u = e[0], v = e[1], d = e[2];
g[u].push_back({v, d});
g[v].push_back({u, d});
}
priority_queue<PII, vector<PII>, greater<PII>> q;
vector<int> dist(n + 1, INT_MAX);
dist[n] = 0;
vector<bool> vis(n + 1, false);
q.push({0, n});
while(!q.empty())
{
auto [dis, to] = q.top();
q.pop();
if(vis[to]) continue;
vis[to] = true;
for(auto [x, w] : g[to])
{
if(dist[x] > dis + w)
{
dist[x] = dis + w;
q.push({dist[x], x});
}
}
}
vector<int> index(n), dp(n + 1);
iota(index.begin(), index.end(), 1);
sort(index.begin(), index.end(), [&dist](auto a, auto b){
return dist[a] > dist[b];
});
dp[1] = 1;
for(auto & ind : index)
{
for(auto & [x, _]: g[ind])
{
if(dist[x] < dist[ind]) dp[x] = (dp[x] + dp[ind]) % mod;
}
}
return dp[n];
}
};
class Solution {
public:
int ans = INT_MAX;
vector<int> jobs;
int energy(int k)
{
int n = jobs.size();
vector<int> s(k);
for(int i = 0; i < n; i ++ )//对于每项工作,找到一个最闲的工人分配
{
int kk = 0;
for(int j = 1; j < k; j ++ )
if(s[j] < s[kk]) kk = j;
s[kk] += jobs[i];
}
int res = 0;
for(auto &x : s)
res = max(x, res);//找到最大工作时间
ans = min(ans, res);
return res; //能量越小越好
}
void th(int k)
{
int n = jobs.size();
double T = 10000;//一个很高的温度
random_shuffle(jobs.begin(), jobs.end());//产生随机的工作分配顺序
while(T > 1e-4) //降温过程
{
T *= 0.92;
int a = rand() % n, b = rand() % n;
int ea = energy(k);
swap(jobs[a], jobs[b]);
int eb = energy(k);
int dt = eb - ea;
if(dt <= 0 || exp(-dt / T) > rand() / RAND_MAX) continue;//以一定概率接受
swap(jobs[a], jobs[b]);//不接受
}
}
int minimumTimeRequired(vector<int>& _jobs, int k) {
//每个工人至少分配一个工作
jobs = _jobs;
for(int i = 0; i < 7; i ++ )
th(k);
return ans;
}
};
状态压缩
class Solution {
public:
int minimumTimeRequired(vector<int>& jobs, int k) {
int n = jobs.size();
int m = (1 << n);
vector<int> all (m, 0);
for (int i = 1; i < m; ++i) {
all[i] = all[i & (i - 1)] + jobs[__builtin_ctz(i)];
}
// all[state] -> all time of state
vector<vector<int>> dp(k, vector<int>(m, INT_MAX));
for (int i = 0; i < m; ++i) {
dp[0][i] = all[i];
}
for (int i = 1; i < k; ++i) {
for (int j = 0; j < m; ++j) {
for (int x = j; x; x = (x - 1) & j) {
dp[i][j] = min(dp[i][j], max(dp[i - 1][j - x], all[x]));
}
}
}
return dp[k - 1][m - 1];
}
};
#define x first
#define y second
class Solution {
public:
string end = "123450";
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
typedef pair<int, string> PIS;
int f(string s)
{
int res = 0;
for(int i = 0; i < s.size(); i ++ )
if(s[i] != '0')
{
int p = s[i] - '1';
res += abs(i / 3 - p / 3) + abs(i % 3 - p % 3);
}
return res;
}
int bfs(string state)
{
unordered_map <string, int> dist;
priority_queue <PIS, vector<PIS>, greater<PIS>> q;
q.push({f(state), state});//将初始状态入栈
dist[state] = 0;
while(q.size())
{
auto t = q.top();
q.pop();
string s = t.y;
if(s == end) break;
int x, y;
for(int i = 0; i < 6; i ++ )//找到0的位置
{
if(s[i] == '0')
{
x = i / 3;
y = i % 3;
break;
}
}
for(int i = 0; i < 4; i ++ )//和上下左右交换
{
int nx = x + dx[i], ny = y + dy[i];
if(nx < 0 || ny < 0 || nx > 1 || ny > 2) continue;//两行三列
string source = s;
swap(source[x * 3 + y], source[nx * 3 + ny]);
if(!dist.count(source) || dist[source] > dist[s] + 1)//dist是无穷大或者是比s + 1大
{
dist[source] = dist[s] + 1;
q.push({f(source) + dist[source], source});
}
}
}
return dist[end];
}
int slidingPuzzle(vector<vector<int>>& board) {
//A* 多次入栈,终点出栈只能确定终点的最短路径
//判断是否有解
string start;
for(auto &t : board)
for(auto &x : t)
start += x + '0';
int cnt = 0;
for(int i = 0; i < 6; i ++ )
for(int j = i + 1; j < 6; j ++ )
{
if(start[i] != '0' && start[j] != '0' && start[j] < start[i]) cnt ++;
}
if(cnt % 2) return -1;
else return bfs(start);
}
};
class Solution {
public:
vector<string> arr;
int n;
int ans = 0;
bool book[27];
int enengy()
{
int res = 0;
memset(book, 0, sizeof book);
for(int i = 0; i < n; i ++ )
{
int flag = true;
for(auto c: arr[i])
{
if(book[c - 'a'])
{
flag = false;
break;
};
book[c - 'a'] = true;
}
if(flag) res += arr[i].size();
}
return res;
}
void th()
{
srand(time(NULL));
random_shuffle(arr.begin(), arr.end());
for(int t = 1e6; t > 1e-6; t *= 0.97)
{
int a = rand() % n, b = rand() % n;
int ea = enengy();
swap(arr[a], arr[b]);
int eb = enengy();
ans = max({ans, ea, eb});
int dt = ea - eb;
if(dt < 0) continue;
if(exp(-1 * dt / t) >= rand() / RAND_MAX) continue;
swap(arr[a], arr[b]);
}
}
int maxLength(vector<string>& _arr) {
for(auto s: _arr)
{
memset(book, 0, sizeof book);
bool flag = true;
for(auto c: s)
{
if(book[c - 'a'])
{
flag = false;
break;
}
book[c - 'a'] = true;
}
if(flag) arr.push_back(s);
}
n = arr.size();
if(!n) return 0;
for(int i = 0; i < 2; i ++ )
th();
return ans;
}
};