整理不易留个小心心呗🥰
如果有更好的或者是我有错的地方还请各位大佬指出哦
有些是copy的还望不要介意
单词接龙
题目描述:
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
示例:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
-
广度优先遍历
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
//将字典存放在哈希表中,便于查询某个单词是否存在
Set<String> wordSet = new HashSet<>(wordList);
if(wordSet.size() == 0 || !wordSet.contains(endWord)){
return 0;
}
wordSet.remove(beginWord);
//广度优先遍历,利用队列以及存储是否访问过的哈希表
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
Set<String> visited = new HashSet<>();
visited.add(beginWord);
//起点步数为1
int step = 1;
while(!queue.isEmpty()){
int currentSize = queue.size();
//遍历当前队列的元素
for(int i=0;i<currentSize;i++){
String currentWord = queue.poll();
//如果currentWord修改一个字符后与endWord相同,则返回步数+1
if(changeWordEveryOneLetter(currentWord,endWord,queue,visited,wordSet)){
return step+1;
}
}
step++;
}
return 0;
}
/**
*修改每个字符,是否跟endWord匹配
*/
private boolean changeWordEveryOneLetter(String currentWord,String endWord,Queue<String> queue,Set<String> visited,Set<String> wordSet){
char[] charArray = currentWord.toCharArray();
for(int i=0;i<endWord.length();i++){
//先保存,稍后恢复
char originChar = charArray[i];
for(char k='a';k<='z';k++){
//更换字符后查看字典中是否存在
if(k == originChar){
continue;
}
charArray[i] = k;
String nextWord = String.valueOf(charArray);
if(wordSet.contains(nextWord)){
if(nextWord.equals(endWord)){
return true;
}
if(!visited.contains(nextWord)){
queue.add(nextWord);
//标记访问过
visited.add(nextWord);
}
}
}
charArray[i] = originChar;
}
return false;
}
}
- 双向广度优先遍历
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
//将字典存放在哈希表中,便于查询某个单词是否存在
Set<String> wordSet = new HashSet<>(wordList);
if(wordSet.size() == 0 || !wordSet.contains(endWord)){
return 0;
}
//将已经访问过的元素放入visited
Set<String> visited = new HashSet<>();
//分别用左边和右边扩散的哈希表代替单向BFS里的队列
Set<String> beginVisited = new HashSet<>();
beginVisited.add(beginWord);
Set<String> endVisited = new HashSet<>();
endVisited.add(endWord);
int step = 1;
while(!beginVisited.isEmpty() && !endVisited.isEmpty()){
//优先选择少的哈希表进行扩散,考虑到的情况更少
if(beginVisited.size() > endVisited.size()){
Set<String> temp = beginVisited;
beginVisited = endVisited;
endVisited = temp;
}
//保证beginVisited是相对较少元素的集合,nextLevelVisited扩散完后,会成为新的beginVisited
Set<String> nextLevelVisited = new HashSet<>();
for(String word:beginVisited){
if(changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)){
return step+1;
}
}
beginVisited = nextLevelVisited;
step++;
}
return 0;
}
/**
*修改每个字符,是否跟endWord匹配
*/
private boolean changeWordEveryOneLetter(String word,Set<String> endVisited,Set<String> visited,Set<String> wordSet,Set<String> nextLevelVisited){
char[] charArray = word.toCharArray();
for(int i=0;i<word.length();i++){
char orginChar = charArray[i];
for(char k='a';k<='z';k++){
if(orginChar==k){
continue;
}
charArray[i] = k;
String nextWord = String.valueOf(charArray);
if(wordSet.contains(nextWord)){
if(endVisited.contains(nextWord)){
return true;
}
if(!visited.contains(nextWord)){
nextLevelVisited.add(nextWord);
visited.add(nextWord);
}
}
}
charArray[i] = orginChar;
}
return false;
}
}
岛屿数量
题目描述:
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
-
dfs
- 该题的图dfs可以类比二叉树的dfs
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}
二叉树的dfs两个要素:访问相邻节点和判断base case
对于图来说相邻节点便是上下左右四个格子
而base case便是超出网格范围
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1'){
dfs(grid,i,j);
count++;
}
}
}
return count;
}
public void dfs(char[][] grid,int i,int j){
if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]=='0' || grid[i][j]=='2'){
return;
}
//遍历过的陆地标记为2,也可以直接为0
grid[i][j]='2';
dfs(grid,i,j-1);
dfs(grid,i,j+1);
dfs(grid,i-1,j);
dfs(grid,i+1,j);
//这样的顺序会稍快一丢丢
//dfs(grid,i+1,j);
//dfs(grid,i-1,j);
//dfs(grid,i,j-1);
//dfs(grid,i,j+1);
}
}
-
bfs
如果是陆地则加入队列,进行广度优先搜索,直到队列为空则遍历完此岛屿
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
bfs(grid, i, j);
count++;
}
}
}
return count;
}
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>();
list.add(new int[] { i, j });
while(!list.isEmpty()){
int[] cur = list.remove();
i = cur[0]; j = cur[1];
if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
grid[i][j] = '0';
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
课程表
题目描述:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。这是可能的。
-
深度优先
先寻找拓扑排序的最后节点
将每一门课看成一个节点,在学习1前要学习0,则从0出发指向1的一条有向边
若存在环则代表不能完成
class Solution {
List<List<Integer>> edges;
int[] visited;
boolean valid = true;
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<Integer>());
}
visited = new int[numCourses];
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
}
for(int i=0;i<numCourses && valid;i++){
//未搜索的科目进行深搜
if(visited[i]==0){
dfs(i);
}
}
return valid;
}
private void dfs(int u){
//将该科目标记为搜索中
visited[u] = 1;
//遍历该科目指向的所有课程
for(int v:edges.get(u)){
//未搜索过的科目进行深搜
if(visited[v]==0){
dfs(v);
if(!valid){
return;
}
}else if(visited[v]==1){
//遍历的科目中存在搜索中的科目则表示有环
valid = false;
return;
}
}
//标记为搜索完
visited[u]=2;
}
}
-
广度优先
先寻找拓扑排序中最前面的节点
先将入度为0的存入栈,然后找到该节点指向的节点,并将它的入度-1,若此时它的入度为0则存入栈
计算存入栈的元素个数,若等于课程数则表示可以,若不等于则表示存在环
class Solution {
List<List<Integer>> edges;
int[] indeg; //存放入度数
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<>());
}
indeg = new int[numCourses];
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
++indeg[info[0]];
}
//存放入度为0的课程
Queue<Integer> queue = new LinkedList<>();
for(int i=0;i<numCourses;i++){
if(indeg[i]==0){
queue.offer(i);
}
}
//记录有多少个课程存入栈
int visited=0;
while(!queue.isEmpty()){
++visited;
int u = queue.poll();
for(int v:edges.get(u)){
//入度减1
--indeg[v];
if(indeg[v]==0){
queue.offer(v);
}
}
}
return visited==numCourses;
}
}
课程表Ⅱ
题目描述:
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
-
深度优先
先寻找拓扑排序的最后节点
将每一门课看成一个节点,在学习1前要学习0,则从0出发指向1的一条有向边
若存在环则代表不能完成
class Solution {
List<List<Integer>> edges;
int[] visited;
boolean valid = true;
int[] result;
int index;
public int[] findOrder(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<Integer>());
}
visited = new int[numCourses];
result = new int[numCourses];
//从拓扑
index = numCourses-1;
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
}
for(int i=0;i<numCourses && valid;i++){
//未搜索的科目进行深搜
if(visited[i]==0){
dfs(i);
}
}
if(!valid){
return new int[0];
}
return result;
}
private void dfs(int u){
//将该科目标记为搜索中
visited[u] = 1;
//遍历该科目指向的所有课程
for(int v:edges.get(u)){
//未搜索过的科目进行深搜
if(visited[v]==0){
dfs(v);
if(!valid){
return;
}
}else if(visited[v]==1){
//遍历的科目中存在搜索中的科目则表示有环
valid = false;
return;
}
}
//标记为搜索完
visited[u]=2;
result[index--]=u;
}
}
-
广度优先
先寻找拓扑排序中最前面的节点
先将入度为0的存入栈,然后找到该节点指向的节点,并将它的入度-1,若此时它的入度为0则存入栈
计算存入栈的元素个数,若等于课程数则表示可以,若不等于则表示存在环
class Solution {
List<List<Integer>> edges;
int[] indeg; //存放入度数
int[] result;
int index;
public int[] findOrder(int numCourses, int[][] prerequisites) {
edges = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<>());
}
indeg = new int[numCourses];
result = new int[numCourses];
index = 0;
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
++indeg[info[0]];
}
//存放入度为0的课程
Queue<Integer> queue = new LinkedList<>();
for(int i=0;i<numCourses;i++){
if(indeg[i]==0){
queue.offer(i);
}
}
//记录有多少个课程存入栈
int visited=0;
while(!queue.isEmpty()){
++visited;
int u = queue.poll();
result[index++]=u;
for(int v:edges.get(u)){
//入度减1
--indeg[v];
if(indeg[v]==0){
queue.offer(v);
}
}
}
if(index!=numCourses){
return new int[0];
}
return result;
}
}