5.20 图、树

This commit is contained in:
zhangsan 2025-05-20 11:03:47 +08:00
parent a900792e79
commit 353b7c0267
8 changed files with 439 additions and 8 deletions

View File

@ -30,4 +30,31 @@ public class MaxSubArray {
} }
return maxsum; return maxsum;
} }
//假设还要同时返回起始结束下标
public int[] maxSubArray2(int[] nums) {
int tempsum = nums[0]; // 当前子数组的和
int maxsum = nums[0]; // 最大子数组和
int start = 0; // 最大子数组的起始下标
int end = 0; // 最大子数组的结束下标
int tempStart = 0; // 当前子数组的起始下标
for (int i = 1; i < nums.length; i++) {
if (tempsum < 0) {
tempsum = nums[i]; // 如果当前子数组和为负数则重新从当前元素开始
tempStart = i; // 更新当前子数组的起始下标
} else {
tempsum += nums[i]; // 否则继续扩展当前子数组
}
// 如果当前的子数组和大于之前的最大和则更新最大和和区间的下标
if (tempsum > maxsum) {
maxsum = tempsum;
start = tempStart; // 记录最大子数组的起始下标
end = i; // 记录最大子数组的结束下标
}
}
// 返回最大子数组的和以及起始和结束下标
return new int[] {maxsum, start, end};
}
} }

View File

@ -0,0 +1,56 @@
package array;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* 题目 918. 环形子数组的最大和 (maxSubarraySumCircular)
* 描述给定一个长度为 n 的环形整数数组 nums 返回 nums 的非空 子数组 的最大可能和
*
* 环形数组 意味着数组的末端将会与开头相连呈环状形式上 nums[i] 的下一个元素是 nums[(i + 1) % n] nums[i] 的前一个元素是 nums[(i - 1 + n) % n]
*
* 子数组 最多只能包含固定缓冲区 nums 中的每个元素一次形式上对于子数组 nums[i], nums[i + 1], ..., nums[j] 不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n
*
示例 1
输入nums = [1,-2,3,-2]
输出3
解释从子数组 [3] 得到最大和 3
*
* 链接https://leetcode.cn/problems/maximum-sum-circular-subarray/
*/
//不会
public class MaxSubarraySumCircular {
//很巧妙的思路
public int maxSubarraySumCircular(int[] nums) {
int totalSum = 0; // 数组元素总和
int currMax = 0; // Kadane 当前最大子数组和
int maxSum = Integer.MIN_VALUE; // 全局最大子数组和
int currMin = 0; // Kadane 当前最小子数组和
int minSum = Integer.MAX_VALUE; // 全局最小子数组和
for (int x : nums) {
// 累加总和
totalSum += x;
// 求最大子数组和标准 Kadane
// 要么把 x 加到前面的子数组上要么从 x 重新开始
currMax = Math.max(currMax + x, x);
maxSum = Math.max(maxSum, currMax);
// 求最小子数组和相反 Kadane
currMin = Math.min(currMin + x, x);
minSum = Math.min(minSum, currMin);
}
// 如果所有元素都为负数maxSum 会是最大的负数
// totalSum - minSum 会变成 0等价于丢弃整个数组不合法
if (maxSum < 0) {
return maxSum;
}
// 否则比较非环形和环形两种情况
return Math.max(maxSum, totalSum - minSum);
}
}

View File

@ -0,0 +1,55 @@
package graph;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
/**
* 题目 210. 课程表 II (findOrder)
* 描述现在你总共有 numCourses 门课需要选记为 0 numCourses - 1给你一个数组 prerequisites 其中 prerequisites[i] = [ai, bi] 表示在选修课程 ai 必须 先选修 bi
* 例如想要学习课程 0 你需要先完成课程 1 我们用一个匹配来表示[0,1]
* 返回你为了学完所有课程所安排的学习顺序可能会有多个正确的顺序你只要返回 任意一种 就可以了如果不可能完成所有课程返回 一个空数组
*
示例 1
输入numCourses = 2, prerequisites = [[1,0]]
输出[0,1]
解释总共有 2 门课程要学习课程 1你需要先完成课程 0因此正确的课程顺序为 [0,1]
* 链接https://leetcode.cn/problems/course-schedule-ii/
*/
public class FindOrder {
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] indegree=new int[numCourses];
int[] res=new int[numCourses];
int index=0;
// 构建图graph.get(i) 存储课程 i 的后续课程
List<List<Integer>> graph = new ArrayList<>();
// 初始化图
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
Queue<Integer>queue=new ArrayDeque<>();
for (int[] prerequisite : prerequisites) {
int course=prerequisite[0];
int precourse=prerequisite[1];
graph.get(precourse).add(course);
indegree[course]++;
}
for (int i = 0; i < numCourses; i++) {
if(indegree[i]==0)queue.offer(i);
}
while (!queue.isEmpty()){
int cur=queue.poll();
res[index++]=cur;
List<Integer>tp=graph.get(cur);
for (Integer course : tp) {
indegree[course]--;
if(indegree[course]==0) queue.offer(course);
}
}
if(index==numCourses)return res;
else return new int[0];
}
}

View File

@ -0,0 +1,57 @@
package graph;
import java.util.*;
/**
* 题目 127. 单词接龙 (ladderLength)
* 描述字典 wordList 中从单词 beginWord endWord 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk
*
* 每一对相邻的单词只差一个字母
* 对于 1 <= i <= k 每个 si 都在 wordList 注意 beginWord 不需要在 wordList
* sk == endWord
* 给你两个单词 beginWord endWord 和一个字典 wordList 返回 beginWord endWord 最短转换序列 中的 单词数目 如果不存在这样的转换序列返回 0
*
示例 1
输入beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出5
解释一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5
* 链接https://leetcode.cn/problems/word-ladder/
*/
//和433. 最小基因变化雷同
public class LadderLength {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String>set=new HashSet<>(wordList);
Set<String>visited=new HashSet<>();
Queue<String> queue=new ArrayDeque<>();
queue.add(beginWord);
visited.add(beginWord);
if(!set.contains(endWord))return 0;
int steps=1;
while (!queue.isEmpty()){
int sz= queue.size();
for (int i = 0; i < sz; i++) {
String cur=queue.poll();
if(cur.equals(endWord))
return steps;
char[] currArr = cur.toCharArray();
for (int pos = 0; pos < currArr.length; pos++) {
char old=currArr[pos];
for (int j = 0; j < 26; j++) {
char g= (char) ('a'+j);
currArr[pos]=g;
String next=new String(currArr);
if(set.contains(next)&&!visited.contains(next)){
visited.add(next);
queue.offer(next);
}
}
currArr[pos]=old;
}
}
steps++;
}
return 0;
}
}

View File

@ -0,0 +1,100 @@
package graph;
import java.util.*;
/**
* 题目 433. 最小基因变化 (minMutation)
* 描述基因序列可以表示为一条由 8 个字符组成的字符串其中每个字符都是 'A''C''G' 'T' 之一
* 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化一次基因变化就意味着这个基因序列中的一个字符发生了变化
* 例如"AACCGGTT" --> "AACCGGTA" 就是一次基因变化
* 另有一个基因库 bank 记录了所有有效的基因变化只有基因库中的基因才是有效的基因序列变化后的基因必须位于基因库 bank
* 给你两个基因序列 start end 以及一个基因库 bank 请你找出并返回能够使 start 变化为 end 所需的最少变化次数如果无法完成此基因变化返回 -1
* 注意起始基因序列 start 默认是有效的但是它并不一定会出现在基因库中
*
示例 1
输入start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出2
* 链接https://leetcode.cn/problems/minimum-genetic-mutation/
*/
//不会
public class MinMutation {
/**
* 准备工作
*
* 将基因库 bank 中的有效基因串存入一个 Set<String> valid用于 O(1) 检查
* 如果 endGene 不在 valid 说明无法达到目标直接返回 1
* BFS 初始化
*
* 使用队列 Queue<String> 存放当前层当前步数所有可达的基因串
* 另用一个 Set<String> visited 记录已经访问过的基因串避免重复遍历
* 将起点 startGene 入队标记已访问将步数 steps 0
* 层次遍历
*
* 每次遍历一层时取出当前队列大小 sz表示这一层共有 sz 个节点要处理
* 对每个节点基因串进行
* 若它等于 endGene则返回当前 steps
* 否则尝试对这条基因串的 8 个位置逐个替换为 'A' / 'C' / 'G' / 'T' 中另一种字符生成所有 相差 1 的新基因串 next
* 如果 next valid 且未被访问过就将其入队并标记访问
* 步数递增
*
* 当前层的所有节点都处理完后steps++进入下一层
* 直到队列为空仍未找到 endGene返回 1
* @param startGene
* @param endGene
* @param bank
* @return
*/
public int minMutation(String startGene, String endGene, String[] bank) {
// 1. bank 放入 Set便于快速查验
Set<String> valid = new HashSet<>(Arrays.asList(bank));
// 如果目标不在 bank 无法达成
if (!valid.contains(endGene)) {
return -1;
}
// 2. BFS 初始化
char[] genes = new char[] {'A', 'C', 'G', 'T'};
Queue<String> queue = new ArrayDeque<>();
Set<String> visited = new HashSet<>();
queue.offer(startGene);
visited.add(startGene);
int steps = 0;
// 3. 开始层次遍历
while (!queue.isEmpty()) {
int sz = queue.size();
// 处理当前层中的所有节点
for (int i = 0; i < sz; i++) {
String curr = queue.poll();
// 找到目标返回步数
if (curr.equals(endGene)) {
return steps;
}
// 生成所有一位变异的基因串
char[] currArr = curr.toCharArray();
for (int pos = 0; pos < currArr.length; pos++) {
char old = currArr[pos];
for (char g : genes) {
if (g == old) continue; // 跳过相同字符
currArr[pos] = g;
String next = new String(currArr);
// 只有在 bank 且未访问过才入队
if (valid.contains(next) && !visited.contains(next)) {
queue.offer(next);
visited.add(next);
}
}
currArr[pos] = old; // 恢复现场不然每个pos都变了
}
}
// 当前层遍历完步数+1
steps++;
}
// 队列空了也没找到返回 -1
return -1;
}
}

View File

@ -1,7 +1,5 @@
package graph; package graph;
import java.util.Map;
/** /**
* 题目 200. 岛屿数量 (numIslands) * 题目 200. 岛屿数量 (numIslands)
* 描述给你一个由 '1'陆地 '0'组成的的二维网格请你计算网格中岛屿的数量 * 描述给你一个由 '1'陆地 '0'组成的的二维网格请你计算网格中岛屿的数量

View File

@ -12,47 +12,98 @@ package graph;
*/ */
//没搞懂 学习一下 //没搞懂 学习一下
/**
* 节点结构
*
* 每个 Trie 对象代表一棵子树的根节点内部维护一个 children[26] 数组对应 26 个字母
* isEnd 标识该节点是否是某个已插入单词的末尾
* 插入 (insert)
* 从根节点出发依次读取单词每个字符计算索引 ch - 'a'
* 若对应子节点为空则新建最后将游标节点标记为单词结尾
*
* 查找完整单词 (search)
* 调用内部方法 searchPrefix找到单词走到的最后一个节点
* 再检查该节点的 isEnd true 时说明完整单词存在
*
* 前缀判断 (startsWith)
* 仍用 searchPrefix 定位到最后一个节点
* 不需要检查 isEnd只要节点不为 null 就说明有该前缀
*
* 共用方法 (searchPrefix)
* 将插入和查找里的公共逻辑抽取出来遍历路径空指针检查节点推进
*/
//https://leetcode.cn/problems/implement-trie-prefix-tree/solutions/98390/trie-tree-de-shi-xian-gua-he-chu-xue-zhe-by-huwt/?envType=study-plan-v2&envId=top-interview-150
class Trie { class Trie {
// children 数组保存当前节点的所有子节点索引 0-25 分别对应 'a'-'z'
private Trie[] children; private Trie[] children;
// isEnd 标志用于标识当前节点是否为一个完整单词的结尾
private boolean isEnd; private boolean isEnd;
/**
* Initialize your data structure here.
*/
public Trie() { public Trie() {
children = new Trie[26]; //这行代码只是 创建了一个长度为 26 的数组对应 a ~ z但是数组里的元素初始都是 null只有当需要用到某个字符时才会创建对应的 Trie 节点 // 初始化 children 数组初始时所有子节点都为 null
children = new Trie[26];
// 初始化单词结尾标志为 false
isEnd = false; isEnd = false;
} }
/**
* Inserts a word into the trie.
*/
public void insert(String word) { public void insert(String word) {
Trie node = this; Trie node = this; // 从根节点开始
for (int i = 0; i < word.length(); i++) { for (int i = 0; i < word.length(); i++) {
char ch = word.charAt(i); char ch = word.charAt(i); // 当前字符
int index = ch - 'a'; int index = ch - 'a'; // 计算在 children 数组中的索引
// 如果对应字符的子节点不存在则新建一个节点
if (node.children[index] == null) { if (node.children[index] == null) {
node.children[index] = new Trie(); node.children[index] = new Trie();
} }
// 移动到子节点继续处理下一个字符
node = node.children[index]; node = node.children[index];
} }
// 遍历完所有字符后将当前节点标记为单词结尾
node.isEnd = true; node.isEnd = true;
} }
/**
* Returns true if the word is in the trie.
*/
public boolean search(String word) { public boolean search(String word) {
// 尝试查找完整单词对应的节点
Trie node = searchPrefix(word); Trie node = searchPrefix(word);
// 只有节点不为 null isEnd true 才说明单词存在
return node != null && node.isEnd; return node != null && node.isEnd;
} }
/**
* Returns true if there is any word in the trie that starts with the given prefix.
*/
public boolean startsWith(String prefix) { public boolean startsWith(String prefix) {
// 只要能找到 prefix 对应的最后一个节点就说明存在该前缀
return searchPrefix(prefix) != null; return searchPrefix(prefix) != null;
} }
/**
* 查找给定字符串对应的最后一个节点若路径中断则返回 null
*/
private Trie searchPrefix(String prefix) { private Trie searchPrefix(String prefix) {
Trie node = this; Trie node = this; // 从根节点开始
for (int i = 0; i < prefix.length(); i++) { for (int i = 0; i < prefix.length(); i++) {
char ch = prefix.charAt(i); char ch = prefix.charAt(i);
int index = ch - 'a'; int index = ch - 'a'; // 计算字符索引
// 若对应子节点不存在则前缀/单词不在 Trie
if (node.children[index] == null) { if (node.children[index] == null) {
return null; return null;
} }
// 移动到子节点继续检查下一个字符
node = node.children[index]; node = node.children[index];
} }
// 成功遍历所有字符返回最后停留的节点
return node; return node;
} }
} }

View File

@ -0,0 +1,87 @@
package tree;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 添加与搜索单词 - 数据结构设计 (WordDictionary)
* 描述请你设计一个数据结构支持 添加新单词 查找字符串是否与任何先前添加的字符串匹配
*
* 实现词典类 WordDictionary
*
* WordDictionary() 初始化词典对象
* void addWord(word) word 添加到数据结构中之后可以对它进行匹配
* bool search(word) 如果数据结构中存在字符串与 word 匹配则返回 true 否则返回 false word 中可能包含一些 '.' 每个 . 都可以表示任何一个字母
*
示例 1
输入
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
输出
[null,null,null,null,false,true,true,true]
解释
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // 返回 False
wordDictionary.search("bad"); // 返回 True
wordDictionary.search(".ad"); // 返回 True
wordDictionary.search("b.."); // 返回 True
* 链接https://leetcode.cn/problems/design-add-and-search-words-data-structure/
*/
public class WordDictionary {
// Trie 树的节点
private WordDictionary[] children;
private boolean isEnd;
public WordDictionary() {
children = new WordDictionary[26]; // 存储 26 个字母的子节点
isEnd = false; // 默认节点不是单词的结尾
}
// 添加单词
public void addWord(String word) {
WordDictionary node = this;
for (int i = 0; i < word.length(); i++) {
int index = word.charAt(i) - 'a'; // 计算字符的索引
if (node.children[index] == null) {
node.children[index] = new WordDictionary(); // 如果子节点不存在就创建一个新节点
}
node = node.children[index]; // 移动到当前字符的子节点
}
node.isEnd = true; // 单词的结尾标志
}
// 搜索单词支持通配符 "."
public boolean search(String word) {
return searchInNode(word, this); // 从根节点开始搜索
}
// 辅助方法递归遍历 Trie
private boolean searchInNode(String word, WordDictionary node) {
if (word.length() == 0) {
return node.isEnd; // 如果单词已遍历完检查当前节点是否是一个完整单词
}
char firstChar = word.charAt(0);
if (firstChar == '.') {
// 如果是通配符 '.', 检查每个子节点
for (int i = 0; i < 26; i++) {
if (node.children[i] != null && searchInNode(word.substring(1), node.children[i])) { //word.substring(1): word 字符串的第二个字符
return true; // 如果任意一个子节点能匹配返回 true
}
}
return false; // 如果所有子节点都无法匹配返回 false
} else {
int index = firstChar - 'a'; // 计算字母的索引
if (node.children[index] == null) {
return false; // 如果当前字符对应的子节点为空返回 false
}
return searchInNode(word.substring(1), node.children[index]); // 向下递归检查
}
}
}