【深度优先遍历】经典岛屿问题汇总
提示:如果本文对您有帮助,欢迎点赞支持!
目录
前言
一、求岛屿的数量
二、求岛屿的周长
三、求岛屿的面积
四、求矩阵的最长递增路径
五、在矩阵中匹配字符串
前言
岛屿系列算法问题是经典的面试高频题,岛屿系列题目的核心考点就是用 DFS/BFS 算法遍历二维数组。如何在二维矩阵中使用 DFS 搜索呢?
我们可以把二维矩阵中的每个点看做一个节点,这个节点的上下左右四个位置就是相邻节点,那么整个矩阵就可以抽象成一幅网状的「图」结构,二维矩阵的搜索问题就变成了从某个点出发,向上下左右四个方向进行深度搜索的问题,该dfs函数的框架如下:
// 从(i, j)出发,向相邻四个方向搜索
void dfs(vector<vector<int>>& grid,int i,int j, vector<vector<bool>>& visited) {int m = grid.length, n = grid[0].length;// 如果超出索引边界,返回if (i < 0 || j < 0 || i >= m || j >= n) return;// 如果已遍历过 (i, j),返回if (visited[i][j]) return;// 进入节点 (i, j)visited[i][j] = true;// 标记当前点已经访问过dfs(grid, i - 1, j, visited); // 向上搜索dfs(grid, i + 1, j, visited); // 向下搜索dfs(grid, i, j - 1, visited); // 向左搜索dfs(grid, i, j + 1, visited); // 向右搜索
}
该dfs函数有一个 visited
布尔数组的参数,其目的是防止走回头路,在不同的问题中可以灵活变换,这里放入模板是告诉你二维矩阵需要有一个标记来防止走回头路。
阅读完本文,你将可以提交如下LeetCode题目:
类型 |
LeetCode题目
|
求岛屿的数量 |
200. 岛屿数量(中等难度) |
1254. 统计封闭岛屿的数目(中等难度) |
|
1905. 统计子岛屿(中等难度) |
|
694. 不同岛屿的数量(中等难度) |
|
求岛屿的周长 |
463. 岛屿的周长(简单难度) |
求岛屿的面积 |
695. 岛屿的最大面积(中等难度) |
剑指 Offer II 105. 岛屿的最大面积(中等难度) |
|
1020. 飞地的数量(中等难度) |
|
求矩阵的最长递增路径 |
329. 矩阵中的最长递增路径(困难难度) |
剑指 Offer II 112. 最长递增路径 | |
在矩阵中匹配字符串 |
79. 单词搜索(中等难度) |
剑指 Offer 12. 矩阵中的路径(中等难度) |
一、求岛屿的数量
200. 岛屿数量(中等难度)
输入一个二维网格grid,其中'1'表示陆地,'0'表示水域,整个网格被水完全包围,陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿,如下gird中共有6个岛屿。
点评:
在下述写法中,dfs
函数遍历到值为 '0'
的位置会直接返回,而且搜索一个岛屿后就不会再遍历该岛屿中的"1",所以我们可以直接省略visited数组,只要把经过的位置都设置为 '0'
,就可以起到不走回头路的作用。
注意该题目中是个字符矩阵。
class Solution {
public:/* 深度优先遍历:二维遍历每个点,如果当前点是陆地就深度遍历每个点都以上下左右的方式去做深度递归,搜索过的方格都需要做标记,*/int m;int n;int numIslands(vector<vector<char>>& grid) {m = grid.size(); n = grid[0].size();// 遍历二维数组中的每个点int res=0;for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果当前点是陆地,就判断是否形成岛屿(该条件可以删除,但是影响效率)if(grid[i][j] == '1'){dfs(grid,i,j);//一定会形成一个岛屿,搜索后就淹没了res++;}}}return res;}// 从[i,j]向四周搜索,将搜索过的1标记为其他值(0,淹没)void dfs(vector<vector<char>>& grid,int i,int j){// 如果当前点超出范围if(i<0||i>=m||j<0||j>=n) return ;// 如果当前点是湖水if(grid[i][j]=='0') return ;// 如果当前点已遍历过if(grid[i][j]=='2') return ;grid[i][j]='2';// 遍历过就标记为2,只要不标记为1(陆地)就行// 向四周遍历dfs(grid,i-1,j);// 向左遍历dfs(grid,i+1,j);// 向右遍历dfs(grid,i,j-1);// 向上遍历dfs(grid,i,j+1);// 向下遍历}
};
1254. 统计封闭岛屿的数目(中等难度)
输入一个二维网格grid,其中0表示陆地,1表示水域,陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿,封闭岛屿则是其四周完全被水包围,如下gird中共有2个封闭岛屿。
点评:
该题目中没有假定二维矩阵四周是被海水包围的,所以「靠边的岛屿」理论上可能就不是「封闭岛屿」。
所以在dfs
函数中将「靠边的岛屿」标记下,这样统计的岛屿个数中去除「靠边的岛屿」就是「封闭岛屿」的个数。
class Solution {
public:/* 该题用0表示土地,1表示水(和其他岛屿问题不同)所有的岛屿可以分为封闭岛屿和非封闭岛屿,我们已知如何求所有岛屿数量(200题)封闭岛屿的特点是接触到了边界,所以只要判断当前统计的岛屿是不是封闭岛屿即可*/int m;int n;int closedIsland(vector<vector<int>>& grid) {m = grid.size(); n = grid[0].size();// 遍历二维数组中的每个点int res=0;for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果当前点是陆地,就扩散if(grid[i][j] == 0){bool isClosed=false;dfs(grid,i,j,isClosed) ;if(!isClosed) res++;}}}return res;}void dfs(vector<vector<int>>& grid,int i,int j,bool& isClosed){// 如果当前点超出范围就返回if(i<0||i>=m||j<0||j>=n) {isClosed=true;return ;}// 如果当前点不是陆地就返回if(grid[i][j]==1) return ;// 如果当前点遍历过就返回if(grid[i][j]==2) return ;grid[i][j]=2;// 遍历过就标记为2// 向四周遍历dfs(grid,i-1,j,isClosed);// 向左遍历dfs(grid,i+1,j,isClosed);// 向右遍历dfs(grid,i,j-1,isClosed);// 向上遍历dfs(grid,i,j+1,isClosed);// 向下遍历}
};
1905. 统计子岛屿(中等难度)
输入一个2个二维网格grid1和grid2,其中1表示陆地,0表示水域,陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿,假设整个网格被水完全包围。如果grid2的一个岛屿的每一个格子都被grid1中同一个岛屿完全包含,那么称grid2中的这个岛屿为子岛屿。
点评:
这道题的关键在于,如何快速判断子岛屿?
什么情况下 grid2
中的一个岛屿 B
是 grid1
中的一个岛屿 A
的子岛?
当岛屿 B
中所有陆地在岛屿 A
中也是陆地的时候,岛屿 B
是岛屿 A
的子岛。
反过来说,如果岛屿 B
中存在一片陆地,在岛屿 A
的对应位置是海水,那么岛屿 B
就不是岛屿 A
的子岛。
那么只要遍历 grid2
中的所有岛屿,把那些不可能是子岛的岛屿排除掉,剩下的就是子岛。
依据这个思路,可以直接写出下面的代码:
class Solution {
public:/*当岛屿B中所有陆地在岛屿 A 中也是陆地的时候,岛屿 B 是岛屿 A 的子岛。反过来说,如果岛屿 B 中存在一片陆地,在岛屿 A 的对应位置是海水,那么岛屿 B就不是岛屿A的子岛*/int m;int n;int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {m = grid1.size(); n = grid1[0].size();for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果岛屿A中陆地在岛屿B中是海水,就淹掉这座小岛if(grid1[i][j]==0&&grid2[i][j]==1)dfs(grid2,i,j);//淹掉这座小岛}}// 遍历二维数组中的每个点int res=0;for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果当前点是陆地,就判断if(grid2[i][j] == 1){dfs(grid2,i,j);res++;}}}return res;}void dfs(vector<vector<int>>& grid,int i,int j){// 如果当前点超出范围if(i<0||i>=m||j<0||j>=n) return ;// 如果当前点是湖水if(grid[i][j]!=1) return ;// 如果当前点已遍历过if(grid[i][j]==0) return ;grid[i][j]=0;// 遍历过就标记为0// 向四周遍历dfs(grid,i-1,j);// 向左遍历dfs(grid,i+1,j);// 向右遍历dfs(grid,i,j-1);// 向上遍历dfs(grid,i,j+1);// 向下遍历}
};
694. 不同岛屿的数量(中等难度)
输入一个二维网格grid,其中1表示陆地,0表示水域,如果陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿。假设岛屿四周被海水包围。问网格中共有多少个形状不同的岛屿。两个岛屿被认为是相同的,当且仅当一个岛屿可以通过平移变换(不可以旋转、翻转)和另一个岛屿重合。
点评:
我们已经会统计矩阵中所有岛屿的数量,也会统计其中「封闭岛屿」的数量,该题是求其中「不同岛屿」的数量。那么如何区分「不同岛屿」?
该题目中的意思是两个岛屿的节点的拓扑结构一样就是相同岛屿(可以直接平移得到),否则就是「不同岛屿」。那么如何表示一个岛屿的拓扑结构呢?这和岛屿的遍历顺序有关。
简单点说,从grid[i][j]
出发向四周搜索时,不同的结构在不同的时机返回的顺序不一样,有的结构可能向上搜索一次就直接返回,向下搜索多次才能返回。对于形状相同的岛屿,如果从同一起点出发,dfs
函数遍历的顺序肯定是一样的。
我们可以为dfs函数输入一个字符串参数,其每次向四个方向搜索一次就添加一个不同的字符,这样相同结构的岛屿就会得到一条相同的字符串,否则就是不同的字符串。
将每个岛屿搜索后的字符串放入哈希集合中,哈希集合自动帮我们去重,然后返回哈希集合的大小即可。
class Solution {
public:int m; // 行数int n; // 列数int numDistinctIslands(vector<vector<int>>& grid) {m = grid.size(); n = grid[0].size();unordered_set<string> set;// 使用哈希集合来存储字符串// 遍历二维数组中的每个点for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果当前点是陆地,就扩散if(grid[i][j] == 1){string str = "";dfs(grid,i,j,str);set.insert(str);}}}return set.size();}void dfs(vector<vector<int>>& grid,int i,int j,string& str){// 如果当前点超出范围就返回if(i<0||i>=m||j<0||j>=n)return;// 如果当前点不是陆地就返回if(grid[i][j]==0)return;grid[i][j]=0;// 遍历过就标记为2// 向四周遍历dfs(grid,i-1,j,str);// 向左遍历str += '1';dfs(grid,i+1,j,str);// 向右遍历str += '2';dfs(grid,i,j-1,str);// 向上遍历str += '3';dfs(grid,i,j+1,str);// 向下遍历str += '4';}
};
二、求岛屿的周长
463. 岛屿的周长(简单难度)
输入一个二维网格grid,其中1表示陆地,0表示水域,整个网格被水完全包围,如果陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿,求网格的所有岛屿的周长之和。
点评:
设计dfs
函数从[i,j]出发向四周遍历,返回四个方向边界和湖水的个数,那么就可以得到一个岛屿的周长。
本题的题意是矩阵中始终只会有一个岛屿,我们的代码求的是所有岛屿的周长之和,更进一步:
class Solution {
public:/* 该题中没有岛内湖,则所有的边长只有两种可能:边界或者湖*/int m;int n;int islandPerimeter(vector<vector<int>>& grid) {m = grid.size(); n = grid[0].size();int res=0;// 遍历二维数组中的每个点for(int i = 0; i < m; i++){for(int j = 0; j <n; j++){// 如果当前点是陆地,就扩散if(grid[i][j] == 1){res+=dfs(grid,i,j);}}}return res;}// 从[i,j]出发向四周遍历,返回四个方向边界和湖水的个数int dfs(vector<vector<int>>& grid,int i,int j){// 如果当前点超出范围,可做周长if(i<0||i>=m||j<0||j>=n) return 1;// 如果当前点是湖水,可做周长if(grid[i][j]==0) return 1;// 如果当前点已遍历过,不可做周长if(grid[i][j]==2) return 0;grid[i][j]=2;// 遍历过就标记为2// 向四周遍历int res=0;res+=dfs(grid,i-1,j);// 向左遍历res+=dfs(grid,i+1,j);// 向右遍历res+=dfs(grid,i,j-1);// 向上遍历res+=dfs(grid,i,j+1);// 向下遍历return res;}
};
三、求岛屿的面积
695. 岛屿的最大面积(中等难度)
剑指 Offer II 105. 岛屿的最大面积(中等难度)
输入一个二维网格grid,其中1表示陆地,0表示水域,如果陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿。假设四周都被谁包围,求grid中最大的岛屿面积,如果没有岛屿,返回0。
点评:
该题目求所有岛屿的最大面积,我们可以用dfs
函数求出一个岛屿的面积,最后保存下面积最大的数即可。
改造dfs
函数使其返回一个整型面积,显然在grid[i][j]
的面积=向上下左右搜索的面积之和+1
注意该题目中的矩阵最大为50阶。
class Solution {
public:int maxAreaOfIsland(vector<vector<int>>& grid) {int res = 0;// 遍历二维数组中的每个点for(int i = 0; i < grid.size(); i++){for(int j = 0; j <grid[0].size(); j++){// 如果当前点是陆地,就扩散if(grid[i][j] == 1){res=max(res,dfs(grid,i,j));//记录每个点遍历的面积的最大值}}}return res;}// 搜索[i,j]向四周扩散后能获得的最大面积int dfs(vector<vector<int>>& grid,int i,int j){// 如果当前点超出范围就返回if(i<0||i>=grid.size()||j<0||j>=grid[0].size()) return 0;// 如果当前点不是陆地就返回if(grid[i][j]!=1)return 0;// 如果当前点已经遍历过,直接返回if(grid[i][j]==-1)return 0;grid[i][j]=-1;// 遍历过就标记为-1// 向四周遍历int size =1;size+=dfs(grid,i-1,j);// 向左遍历size+=dfs(grid,i+1,j);// 向右遍历size+=dfs(grid,i,j-1);// 向上遍历size+=dfs(grid,i,j+1);// 向下遍历return size;}
};
1020. 飞地的数量(中等难度)
输入一个二维网格grid,其中1表示陆地,0表示水域,如果陆地格子水平和垂直方向相连(对角线方向不相连)则形成一个岛屿。一次移动是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过grid的边界。返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。
点评:
该题目和「 1254.统计封闭岛屿的数目」非常相似,这题不求封闭岛屿的数量,而是求封闭岛屿的面积总和。
注意该题中 1
代表陆地,0
代表海水。
class Solution {
public:/*简单讲,本题就是求解所有封闭岛屿的面积,靠近边界的岛屿就不是封闭岛屿*/int m;int n;int numEnclaves(vector<vector<int>>& grid) {m=grid.size();n=grid[0].size();// 遍历二维数组中的每个点int res=0;for(int i=0;i<m;++i){for(int j=0;j<n;++j){bool isClosed=false;int size=dfs(grid,i,j,isClosed);// 如果是非封闭岛屿则if(!isClosed) res+=size;}}return res;}int dfs(vector<vector<int>>& grid,int i,int j,bool& isClosed){if(i<0||i>=m||j<0||j>=n){isClosed=true;return 0;}if(grid[i][j]==0) return 0;grid[i][j]=0;int size=1;size+=dfs(grid,i-1,j,isClosed);size+=dfs(grid,i+1,j,isClosed);size+=dfs(grid,i,j-1,isClosed);size+=dfs(grid,i,j+1,isClosed);return size;}
};
四、求矩阵的最长递增路径
329. 矩阵中的最长递增路径(困难难度)
输入一个二维网格整数矩阵matrix,找出其中最长递增路径的长度,对于每个单元格,你可以向上下左右四个方向移动,矩阵最大阶数为200,(建议记忆化搜索)。
点评:
该题目求矩阵的最长递增路径,我们可以用dfs
函数求出从matrix[i][j]
出发向四个方向搜索的最大连续递增长度,显然该长度是四个方向结果的最大值+1。
注意该题目中的矩阵最大为200阶,其题目计算量较大,容易超时,所以我们需要加入一个记忆化搜索
,
简单说,我们的visited数组直接保存在matrix[i][j]
的最长递增路径,这样面对相同的点就不会重复搜索
class Solution {
public:/* 从一个格子开始向上下左右搜索,如果四周存在小于它的,遍历过需要标记那么其最大连续递增长度即为周围小于它的格子的最大连续递增长度中的最大值+1*/int longestIncreasingPath(vector<vector<int>>& matrix) {int res = 0;vector<vector<int>> visited = vector< vector<int> > (matrix.size(), vector <int> (matrix[0].size()));// 遍历二维数组中的每个点for(int i = 0; i < matrix.size(); i++){for(int j = 0; j <matrix[0].size(); j++){// 如果当前点没有遍历过if(visited[i][j]==0){res=max(res,dfs(matrix,i,j,-1,visited));}}}return res;}int dfs(vector<vector<int>>& matrix,int i,int j,int last,vector<vector<int>>& visited){// 如果当前点超出范围就返回if(i<0||i>=matrix.size()||j<0||j>=matrix[0].size()) return 0;// 如果当前点比上一个点小就返回if(matrix[i][j]<=last)return 0;// 如果当前点已经计算过,就直接返回最大路径if(visited[i][j] != 0) return visited[i][j];// 向四周遍历,取最大方向int size =0;int tmp=matrix[i][j];size=max(size,dfs(matrix,i-1,j,tmp,visited));// 向左遍历size=max(size,dfs(matrix,i+1,j,tmp,visited));// 向右遍历size=max(size,dfs(matrix,i,j-1,tmp,visited));// 向上遍历size=max(size,dfs(matrix,i,j+1,tmp,visited));// 向下遍历// 标记当前点已经遍历过visited[i][j]=size+1;return visited[i][j];}
};
五、在矩阵中匹配字符串
79. 单词搜索(中等难度)
剑指 Offer 12. 矩阵中的路径(中等难度)
输入一个二维网格字符矩阵board和一个字符串word,问word是否存在于board中。找出其中最长递增路径的长度,对于每个单元格,单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
点评:
该题目求字符矩阵中是否存在一个字符串,我们可以用dfs
函数求出从board[i][j]
出发向四个方向搜索如果有一个方向匹配即可,显然dfs
函数要传入字符串word
和它的当前匹配索引k
。
注意本题中求一个点board[i][j]
后需要回溯,因为其遍历过的点之后可能还需要遍历
class Solution {
public:bool exist(vector<vector<char>>& board, string word) {// 遍历二维数组中的每个点for(int i=0;i<board.size();++i){for(int j=0;j<board[0].size();++j){// 如果当前点满足测试条件if(board[i][j]==word[0]){// 遍历测试if(dfs(board,word,i,j,0)) return true;}}}return false;}bool dfs(vector<vector<char>>& board,string& word,int i,int j,int k){// 如果当前点超出范围if(k==word.size()) return true;// 如果当前点超出范围if(i<0||i>=board.size()||j<0||j>=board[0].size()) return false;// 如果当前点不匹配字符if(board[i][j]!=word[k]) return false;// 进入char tmp=board[i][j];board[i][j]='0';// 遍历过就标记为0// 向四周遍历bool res=dfs(board,word,i-1,j,k+1)||// 向左遍历dfs(board,word,i+1,j,k+1)||// 向右遍历dfs(board,word,i,j-1,k+1)||// 向上遍历dfs(board,word,i,j+1,k+1);// 向下遍历// 回溯board[i][j]=tmp;// 下一个点还可以遍历return res;}
};
【深度优先遍历】经典岛屿问题汇总相关推荐
- 1905. 统计子岛屿-深度优先遍历图
1905. 统计子岛屿-深度优先遍历图 给你两个 m x n 的二进制矩阵 grid1 和 grid2 ,它们只包含 0 (表示水域)和 1 (表示陆地).一个 岛屿 是由 四个方向 (水平或者竖直) ...
- 深度优先遍历算法-02最大岛屿问题
最大的岛屿 简介 比较常见的棋盘类型的DFS问题. 问题描述 给定一个二维矩阵,其中0代表海洋,1代表陆地(相邻的1连接形成岛屿),现在要求找到最大岛屿面积. 是不是有一种LeetCode的既视感. ...
- dfs遍历和bfs遍历python_广度优先遍历(BFS)和深度优先遍历(DFS)
BFS: 思想: 对于图中的初始节点,先遍历初始节点的一阶邻居,当初始节点的一阶邻居都被遍历完了之后,再遍历初始节点的二阶邻居,直至所有节点都被遍历完(或找到符合条件的节点) 过程: 三要素:(1)先 ...
- 深度优先遍历算法-03二叉树路径遍历问题
二叉树路径遍历 简述 比较基础的一个DFS的题目,但是确实很多难题的模板.LeetCode很多二叉树的题本质上就是这个路径遍历. 本题为了输出路径,使用DFS的经典结构栈完成. 问题描述 给定一个二叉 ...
- 深度优先遍历算法-01小偷偷东西问题
小偷偷东西问题 前言 深度优先遍历是经典的图论算法,深度优先遍历算法的搜索逻辑和它的名字一样,只要有可能,就尽量深入搜索,直到找到答案,或者尝试了所有可能后确定没有解. 简单来说,深度优先遍历就是按照 ...
- 终于,把十大经典排序算法汇总了!(Java实现版)
转载自 终于,把十大经典排序算法汇总了!(Java实现版) 最近几天在研究排序算法,看了很多博客,发现网上有的文章中对排序算法解释的并不是很透彻,而且有很多代码都是错误的,例如有的文章中在" ...
- 搜索---广度优先遍历、深度优先遍历、回溯法
参考文章:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%9 ...
- 网易校园招聘历年经典面试题汇总:C++研发岗
这个系列计划收集几百份朋友和读者的面经,作者合集方便查看,各位有面经屯着可以联系我哦 这个系列离结束差的还特别多,会更新涵盖所有一线大厂的所有岗位,也可以关注一下. 腾讯校园招聘历年经典面试题汇总:前 ...
- 百度校招历年经典面试题汇总:Java开发岗
这个系列计划收集几百份朋友和读者的面经,作者合集方便查看,各位有面经屯着可以联系我哦 这个系列离结束差的还特别多,会更新涵盖所有一线大厂的所有岗位,也可以关注一下. 百度校园招聘历年经典面试题汇总:C ...
最新文章
- 【转】从Mac/OS和iOS开放源码浅谈UNIX家谱
- 网络架构之争:三大主流架构对决,谁是王者?深入思考CNN、Transformer与MLP
- python迭代是什么意思_python中什么是迭代?
- 雷军宣布红米 Redmi 品牌独立,这对小米意味着什么?
- jenkins执行bat失败_关于批处理文件:即使在BAT脚本中成功执行了ROBOCOPY命令,JENKINS作业也会失败...
- linux宽松模式,SELinux 宽容模式(permissive) 强制模式(enforcing) 关闭(disabled) 几种模式之间的转换...
- XPath 轴 Axes
- 学fpga(组合逻辑和时序逻辑)
- Js 跨域CORS报错 Response for preflight has invalid HTTP status code 405
- Janusec WAF网关安装体验
- Atitit 关于建立知识库体系的方案
- thinkphp6 task异步
- 常用0x000000类型颜色代码表
- django.template.exceptions.TemplateSyntaxError: Invalid block tag on line 12: ‘static‘. Did you forg
- php滚动公告栏,jQuery实现上下滚动公告栏详细代码
- Docker学习,这一篇博客就够了
- 有衬线字体和无衬线字体
- MySQL基础教程——创建数据库并插入数据
- .COMBO勒索病毒解密恢复 .xx4444 勒索病毒数据库恢复 .ALCO勒索病毒解密恢复
- 洛谷1456 Monkey King