章节 | 题目来源 | 链接 | 分类 | 备注 |
---|---|---|---|---|
祖父节点值为偶数的节点和 | Leetcode.com | 祖父节点值为偶数的节点和 | 树 DFS | |
紫书 6.4.2 6-14 Abbott的复仇 | BFS | |||
水域大小 | 程序员面试金典 | 水域大小 | BFS + Flood Fill | 广搜 + 染色 |
扫雷 | 谷歌 | 扫雷 | BFS | BFS + Flood Fill |
迷宫 | acwing.com | 迷宫 | BFS | 模板题 |
迷宫问题 | acwing.com | 迷宫问题 | BFS | 模板题+每次记录前驱节点 |
矩阵中的路径 | 剑指offer | 矩阵路径 | DFS | 模板题 |
紫书 6.4.3 6-15 拓扑排序 | 6-15 Uva10305 | Ordering Tasks | 拓扑排序 | 模板题 |
判断是否是拓扑序列 | acwing.com | 判断是否是拓扑序列 | 拓扑排序 | 检查序列的入度 |
可达性统计 | acwing.com | 可达性统计 | 拓扑排序 | bitset的使用 |
奖金 | acwing.com | 奖金 | 拓扑排序 | 反着建图 |
📅 6.26
计算所有父节点的父节点值为偶数的节点的值
这是树 + DFS的题目。已知根节点,判断他是否有孙子节点,如果有则将其累加,最后递归下去。
class Solution {
public int sumEvenGrandparent(TreeNode root) {
if(root == null) return 0;
int res = 0;
if(root.val % 2 == 0){
if(root.left != null){
if(root.left.left != null) res += root.left.left.val;
if(root.left.right != null) res += root.left.right.val;
}
if(root.right != null){
if(root.right.left != null) res += root.right.left.val;
if(root.right.right != null) res += root.right.right.val;
}
}
return res + sumEvenGrandparent(root.left) + sumEvenGrandparent(root.right);
}
}
📅 6.18
输入一个数组,0代表水域,求每个连通的水域的大小,8个方向都算连通
宽搜确定每个水域大小 染色保证不会重复
Python
from collections import deque
class Solution:
def pondSizes(self, land: List[List[int]]) -> List[int]:
rows = len(land)
cols = len(land[0])
path = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, -1], [-1, 1]]
def bfs(row, col):
s = 0
queue = deque([(row, col)])
while queue:
size = len(queue)
while size > 0:
size -= 1
x, y = queue.popleft()
s += 1
for k in range(8):
nx = x + path[k][0]
ny = y + path[k][1]
if nx >= 0 and nx < rows and ny >= 0 and ny < cols and land[nx][ny] == 0:
land[nx][ny] = -1
queue.append((nx, ny))
return s
res = []
for i in range(rows):
for j in range(cols):
if land[i][j] == 0:
land[i][j] = -1
size = bfs(i, j)
res.append(size)
res.sort()
return res
扫雷游戏,有一个方格,其中若无地雷,则有一数字标识周围雷的数量(8个格子中雷的数量),如果此数字是0, 则点击时会自动递归点击周围8个方格。已知所有雷的位置,问最少点几次可以完成游戏;
- 先计算出每个方格的数字,如果当前点是雷,则标注为-1, for循环遍历及可计算
- 之后先点击数字是0的格子,利用dfs扩展,点过的点标注为-1。这里注意每次dfs之前要判断一下,当前点是不是数字为0
- 之后点击剩余的格子, 及for循环遍历计算剩余的不是-1的点
Cpp
#include <iostream>
using namespace std;
const int N = 310;
char str[N][N];
int g[N][N];
int path[8][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
int n;
// 如果当前点是0 则可以扩展点击
void dfs(int i, int j){
int c = g[i][j];
g[i][j] = -1;
if(c != 0) return;
for(int k = 0; k < 8; k ++){
int nx = i + path[k][0];
int ny = j + path[k][1];
if(nx >= 0 && nx < n && ny >= 0 && ny < n && g[nx][ny] != -1){
dfs(nx, ny);
}
}
}
int main(){
int T;
scanf("%d", &T);
for(int I = 1; I <= T; I ++){
scanf("%d", &n);
for(int i = 0; i < n; i ++){
scanf("%s", str[i]);
}
// 先统计每个点的周围的雷的个数
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(str[i][j] == '*'){
g[i][j] = -1;
} else {
g[i][j] = 0;
for(int k = 0; k < 8; k ++){
int nx = i + path[k][0];
int ny = j + path[k][1];
if(nx >= 0 && nx < n && ny >= 0 && ny <= n && str[nx][ny] == '*'){
g[i][j] ++;
}
}
}
}
}
// 先点0点
// 如果当前点是0 则让其扩展
int res = 0;
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(g[i][j] == 0){
res ++;
dfs(i, j);
}
}
}
// 点其他点
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(g[i][j] != -1){
res ++;
}
}
}
printf("Case #%d: %d\n", I, res);
}
}
Python
📅 6.4 打卡
T = int(input())
for case in range(1, T + 1):
n = int(input())
strr = [[0 for i in range(n + 1)] for _ in range(n + 1)]
g = [[0 for i in range(n + 1)] for _ in range(n + 1)]
path = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, -1], [-1, 1]]
# 把输入节点的周围八个点都点一遍
def dfs(i, j):
c = g[i][j]
# 讲此点标记
g[i][j] = -1
if c != 0:
return
for item in path:
nx = i + item[0]
ny = j + item[1]
if nx >= 0 and nx < n and ny >= 0 and ny < n and g[nx][ny] != -1:
dfs(nx, ny)
# 输入
for i in range(n):
t = list(map(str, input().strip().split()))
for j in range(n):
strr[i][j] = t[0][j]
# 计算每个点的周围的雷的个数
for i in range(n):
for j in range(n):
if strr[i][j] == '*':
g[i][j] = -1
else:
cnt = 0
for item in path:
nx = i + item[0]
ny = j + item[1]
if nx >= 0 and nx < n and ny >= 0 and ny < n and strr[nx][ny] == '*':
cnt += 1
g[i][j] = cnt
res = 0
# 先点0 的点
for i in range(n):
for j in range(n):
if g[i][j] == 0:
dfs(i, j)
res += 1
# 统计现在还有多少剩余点
for i in range(n):
for j in range(n):
if g[i][j] != -1:
res += 1
print('Case #{}: {}'.format(case, res))
Java
📅打卡 🆕 Java版本
import java.io.*;
import java.util.*;
class Main{
public static final int N = 310;
public static int n;
public static char[][] str = new char[N][N];
public static int[][] g = new int[N][N];
public static int[][] path = new int[][] {{0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
public static void dfs(int i, int j){
int c = g[i][j];
g[i][j] = -1;
if(c != 0){
return;
}
for(int k = 0; k < 8; k ++){
int nx = i + path[k][0];
int ny = j + path[k][1];
if(nx >= 0 && nx < n && ny >= 0 && ny < n && g[nx][ny] != -1){
dfs(nx, ny);
}
}
}
public static void main(String[] args) throws IOException{
int T = 0;
// Scanner in = new Scanner(new BufferedInputStream(System.in));
// T = in.nextInt();
AReader in = new AReader(System.in);
T = in.nextInt();
for(int I = 1; I <= T; I ++){
n = in.nextInt();
for(int i = 0; i < n; i ++){
String s = in.nextLine();
for(int j = 0; j < n; j ++){
// str[i][j] = in.nextChar();
str[i][j] = s.charAt(j);
}
}
// 先将每个点标记周围又多少个雷
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(str[i][j] == '*'){
g[i][j] = -1;
} else {
int cnt = 0;
for(int k = 0; k < 8; k ++){
int nx = i + path[k][0];
int ny = j + path[k][1];
if(nx >= 0 && nx < n && ny >= 0 && ny < n && str[nx][ny] == '*'){
cnt ++;
}
}
g[i][j] = cnt;
}
}
}
int res = 0;
//
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(g[i][j] == 0){
dfs(i, j);
res ++;
}
}
}
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
if(g[i][j] != -1){
res ++;
}
}
}
System.out.println("Case #" + I + ": " + res);
}
}
}
// 输入输出模板来自 https://www.rainng.com/java-acm-fast-io/
class AReader implements Closeable {
private BufferedReader reader;
private StringTokenizer tokenizer;
public AReader(InputStream inputStream) {
reader = new BufferedReader(new InputStreamReader(inputStream));
tokenizer = new StringTokenizer("");
}
private String innerNextLine() {
try {
return reader.readLine();
} catch (IOException ex) {
return null;
}
}
public boolean hasNext() {
while (!tokenizer.hasMoreTokens()) {
String nextLine = innerNextLine();
if (nextLine == null) {
return false;
}
tokenizer = new StringTokenizer(nextLine);
}
return true;
}
public String nextLine() {
tokenizer = new StringTokenizer("");
return innerNextLine();
}
public String next() {
hasNext();
return tokenizer.nextToken();
}
public int nextInt() {
return Integer.parseInt(next());
}
@Override
public void close() throws IOException {
reader.close();
}
}
class AWriter implements Closeable {
private BufferedWriter writer;
public AWriter(OutputStream outputStream) {
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
}
public void print(Object object) throws IOException {
writer.write(object.toString());
}
public void println(Object object) throws IOException {
writer.write(object.toString());
writer.write("\n");
}
@Override
public void close() throws IOException {
writer.close();
}
}
输入一个迷宫,输出从左上角走到右下角的最短距离
bfs模板题
Cpp
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
int d[N][N];
int g[N][N];
void bfs(){
int cnt = 0;
queue<PII> q;
int path[4][4] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
q.push({0, 0});
d[0][0] = 0;
while(q.size()){
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++){
int nx = t.first + path[i][0];
int ny = t.second + path[i][1];
if(nx >= 0 && nx < n && ny >= 0 && ny < m && d[nx][ny] == -1 && g[nx][ny] == 0){
q.push({nx, ny});
d[nx][ny] = d[t.first][t.second] + 1;
}
}
}
printf("%d\n", d[n - 1][m - 1]);
}
int main(){
scanf("%d %d", &n, &m);
memset(d, -1, sizeof(d));
for(int i = 0; i < n; i ++){
for(int j = 0; j < m; j ++){
scanf("%d", &g[i][j]);
}
}
bfs();
return 0;
}
找到最短路径
因为找到的最短路径,所以每个点的先驱节点应该是确定的,所以采用bfs + 每次记录先驱节点。之后通过最后一个点倒序走到起始点。
Cpp
#include <iostream>
#include <queue>
#include <unordered_map>
using namespace std;
const int N = 1010;
int g[N][N];
typedef pair<int, int> PII;
PII pre[N][N];
int n;
bool flag[N][N];
void bfs(){
queue<PII> q;
vector<PII> d;
q.push({0, 0});
pre[0][0] = {0, 0};
flag[0][0] = true;
int path[4][4] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
while(q.size()){
auto t = q.front();
q.pop();
if(t.first == n - 1 && t.second == n -1) return;
for(int i = 0; i < 4; i ++){
int x = t.first + path[i][0];
int y = t.second + path[i][1];
if(x >= 0 && x < n && y >= 0 && y < n && flag[x][y] == false && g[x][y] == 0){
q.push({x, y});
flag[x][y] = true;
pre[x][y] = { t.first, t.second };
}
}
}
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n;i ++){
for(int j = 0; j < n; j ++){
scanf("%d", &g[i][j]);
}
}
bfs();
vector<PII> res;
int i = n - 1, j = n - 1;
while(make_pair(i, j) != make_pair(0, 0)){
res.push_back(make_pair(i, j));
PII pre_pii = pre[i][j];
i = pre_pii.first;
j = pre_pii.second;
}
res.push_back({0, 0});
for(auto it = res.rbegin(); it != res.rend(); it ++){
printf("%d %d\n", it -> first, it -> second);
}
return 0;
}
DFS 模板题
Java
class Solution {
public int rows;
public int cols;
public int[][] path = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public boolean dfs(char[][] matrix, String str, int u, int x, int y){
if(u == str.length() - 1){
return true;
}
char ch = matrix[x][y];
matrix[x][y] = '*';
for(int i = 0; i < 4; i ++){
int nx = x + path[i][0];
int ny = y + path[i][1];
if(nx >= 0 && nx < rows && ny >= 0 && ny < cols && str.charAt(u + 1) == matrix[nx][ny]){
if(dfs(matrix, str, u + 1, nx, ny)){
return true;
}
}
}
matrix[x][y] = ch;
return false;
}
public boolean hasPath(char[][] matrix, String str) {
rows = matrix.length;
if(rows == 0) return false;
cols = matrix[0].length;
for(int i = 0; i < rows; i ++){
for(int j = 0; j < cols; j ++){
if(matrix[i][j] == str.charAt(0) && dfs(matrix, str, 0, i, j)){
return true;
}
}
}
return false;
}
}
拓扑排序
for(图中的点){
if(点入度为0){
入队列
}
}
while(队列不空){
node = 队列头弹出一个点
for(这个node指向所有节点){
if(如果删掉这条边 node指向的节点的入度为0){
将node指向的点入队列
}
}
}
Cpp
#include <iostream>
#include <cstring>
const int N = 110;
int e[N], ne[N], h[N];
int idx;
int q[N];
int d[N];
int n, m;
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void top_sort(){
int hh =0, tt = -1;
for(int i = 1; i <= n; i ++){
if(!d[i]){
q[++ tt] = i;
}
}
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if(-- d[j] == 0){
q[++ tt] = j;
}
}
}
}
int main(int argc, char const *argv[])
{
while(scanf("%d %d", &n, &m)){
if(m == 0 && n == 0) break;
if(m == 0){
for (int i = 1; i <= n; i++)
{
printf("%d ", i);
}
puts("");
} else {
memset(h, -1, sizeof(h));
memset(ne, 0, sizeof(ne));
memset(e, 0, sizeof(e));
memset(q, 0, sizeof(q));
memset(d, 0, sizeof(d));
idx = 0;
while(m --){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
d[b] ++;
}
top_sort();
for(int i = 0; i < n; i ++){
printf("%d ", q[i]);
}
puts("");
}
}
}
给定一个有向无环图和若干数字序列,判断这个序列是不是这个图的拓扑序列
从图的入度下手,拓扑序列,第一个点的入度一定为0,之后删除此点可达的边,下一点的入度也必为0,若下一点入度不为0,则此序列不是拓扑序列,一直检查整个序列。
Cpp
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
const int M = 10010;
int e[M], ne[M], h[N];
int idx;
int q[N];
int d[N];
int res[N];
int n, m;
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
// 检查此序列是不是拓扑序列
bool test(){
int temp[N];
for(int i = 1; i <= n; i ++){
temp[i] = d[i];
}
for(int k = 0; k < n; k ++){
int i = res[k];
// 如果此点的入度不为0 则错误
if(temp[i] != 0){
return false;
} else {
// 将此点的后继节点的入度 -1
for(int j = h[i]; j != -1; j = ne[j]){
-- temp[e[j]];
}
}
}
return true;
}
int main(){
memset(h, -1, sizeof(h));
scanf("%d %d", &n, &m);
while(m --){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
d[b] ++;
}
int x;
scanf("%d", &x);
int cnt = 0;
while(x --){
for(int i = 0;i < n; i ++){
scanf("%d", &res[i]);
}
if(!test()) printf("%d ", cnt);
cnt ++;
}
puts("");
return 0;
}
给定一个有向无环图,分别输出从每个点出发能到达的点的数量
假设点u能到达的所有点为f(u), 则f(u) = f(u1) + f(u2) + .... + f(un), u1 ~ un 为u的后继节点。先进行拓扑排序,之后按照拓扑排序的倒序分别统计每个点。
Cpp
#include <iostream>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int q[N];
int e[N], ne[N], h[N];
int idx;
int d[N];
bitset<N> f[N];
int n, m;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topsort(){
int hh = 0, tt = -1;
for(int i = 1; i <= n; i ++){
if(d[i] == 0){
q[++ tt] = i;
}
}
while(hh <= tt){
auto t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
auto j = e[i];
if(-- d[j] == 0){
q[++ tt] = j;
}
}
}
}
int main(){
scanf("%d %d", &n, &m);
memset(h, -1, sizeof(h));
while(m --){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
d[b] ++;
}
topsort();
// f[i] 中1的个数是点i后续节点的个数
for(int i = n - 1; i >= 0; i --){
int j = q[i];
f[j][j] = 1;
for(int i = h[j]; i != -1; i = ne[i]){
f[j] |= f[e[i]];
}
}
for(int i = 1; i <= n; i ++) printf("%d\n", f[i].count());
return 0;
}
公司召开了M个人的会议,商讨奖金问题。每个代表发言:我认为a的奖金应该比b高。每人的奖金至少是100元。
人与人之间的奖金关系是个有向无环图,其实就是求每个点之后的后继节点最深有多深。这样不如反向建图
在拓扑排序的时候,算出每个点的深度。
Cpp
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 10010;
const int M = 20010;
int e[M], ne[M], idx, h[N];
int d[N];
int dist[N];
int n, m;
int cnt;
typedef pair<int, int> PII;
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
// 在拓扑排序的时候记录最大深度
void top_sort(){
queue<PII> q;
for(int i = 1; i <= n; i ++){
if(!d[i]){
q.push({100, i});
dist[i] = 100;
}
}
while(q.size()){
auto t = q.front();
q.pop();
// 如果cnt == n 说明所有点都进入了队列并弹出 所以是可以拓扑排序的
// 如果 cnt < n 则说明不存在拓扑序列
cnt ++;
int j = t.second;
// 当前的点的深度
int distence = t.first;
for(int i = h[j]; i != -1; i = ne[i]){
int k = e[i];
if(-- d[k] == 0){
// 因为可能有不同的点可以到达这里 所以要用max计算深度
dist[k] = max(distence + 1, dist[k]);
q.push({dist[k], k});
}
}
}
}
int main(){
memset(h, -1, sizeof(h));
scanf("%d %d", &n , &m);
while(m --){
int a, b;
scanf("%d %d", &a, &b);
add(b, a);
d[a] ++;
}
top_sort();
int res = 0;
for(int i = 1; i <= n; i ++){
res += dist[i];
}
if(cnt < n){
puts("Poor Xed");
} else {
printf("%d\n", res);
}
return 0;
}
数组中给出边的拓扑结构,查看该图是否存在拓扑序列
Python
class Solution {
public:
const static int N = 100010;
int h[N], e[N], ne[N];
int q[N];
int d[N];
int idx;
int n;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool top_sort(){
int hh = 0, tt = -1;
for(int i = 0; i < n; i ++){
if(!d[i]){
q[++ tt] = i;
}
}
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if(-- d[j] == 0){
q[++ tt] = j;
}
}
}
return tt == n - 1;
}
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
n = numCourses;
memset(h, -1, sizeof(h));
for(auto pre : prerequisites){
add(pre[0], pre[1]);
d[pre[1]] ++;
}
return top_sort();
}
};