- 写于2019年6月22日
文章目录
- [207. 课程表](https://leetcode.com/problems/course-schedule/)
- [208. 实现 Trie (前缀树)](https://leetcode.com/problems/implement-trie-prefix-tree/)
- [215. 数组中的第K个最大元素](https://leetcode.com/problems/kth-largest-element-in-an-array/)
- [287. 寻找重复数](https://leetcode.com/problems/find-the-duplicate-number/)
- [238. 除自身以外数组的乘积](https://leetcode.com/problems/product-of-array-except-self/)
- [347. 前K个高频元素](https://leetcode.com/problems/top-k-frequent-elements/)
207. 课程表
① 题目描述
中文题目:https://leetcode-cn.com/problems/course-schedule/
② 拓扑排序——查找有向图中是否存在环
- 课程的学习条件,使用二元数组表示。比如
[[1,0]]
,如果用有向图表示,则应该是:
- 其中课程数表示有向图中的节点数,学习条件数用于构造有向图。
[[1, 0], [2, 0], [3, 1], [3, 2], [2, 1]]
绘图如下:
- 判断有向图中是否存在环: 寻找入度为0的节点,删除该节点以及从该节点出发的边。直至所有节点都被删除,或者不存在入度为0的节点。
- 上图中,删除节点0,再删除节点1,然后删除节点2,最后删除节点3。没有节点剩余,说明该有向图中不存在环。
- 程序思想:
① 用一个数组统计所有节点的入度,用一个数组记录该节点是否已经被删除。
② 循环查找入度为0的节点,更新其他相连节点的入度,并记录该节点已经删除。直至无法找到入度为0的节点。
③ 查找每个节点的入度,如果存在入度不为零的节点,则说明存在环;否则,不存在环。 - 代码如下,运行时间
34ms
:
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] inDegree = new int[numCourses];// 记录每个节点的入度
boolean[] visited = new boolean[numCourses];// 记录该节点是否已经从有向图中删除
// 统计每个节点的入度
for (int i = 0; i < prerequisites.length; i++) {
inDegree[prerequisites[i][0]]++;
}
while (true) {
int i = 0;
for (; i < numCourses; i++) {
if (!visited[i] && inDegree[i] == 0) {
break;
}
}
if (i == numCourses) {// 没有找到入度为零的节点
break;
}
// 删除入度为零的节点,更新其他节点的入度
for (int k = 0; k < prerequisites.length; k++) {
if (prerequisites[k][1] == inDegree[i]) {
inDegree[prerequisites[k][0]]--;
}
}
visited[i] = true;
}
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] > 0) {
return false;
}
}
return true;
}
208. 实现 Trie (前缀树)
① 题目描述
- 中文题目:https://leetcode-cn.com/problems/implement-trie-prefix-tree/
- 前缀树,又称字典树。结构如下:
- 对于每一个节点,从根遍历至某个节点,所有经过的字母形成一个单词。如果这个节点被标记为红色,就表示这个单词存在,否则不存在。
- 对于一个单词,只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。
- 插入一个单词时,如果字母对应的节点存在,则继续遍历下一个字母;如果不存在,则创建新节点。并把最后一个字母对应对应的标记为红色,就相当于插入了这个单词。
- 这样一来我们查询和插入可以一起完成,所用时间仅仅为单词长度,在这一个样例,便是10。
- 前缀树每一层的节点数是
2
6
n
26^n
26n级别,其中n为0 , 1, …。
② 构造前缀树
- 前缀树的结构如下,使用
isEnd
表示该节点是否一个单词的结尾;使用长度为26的child
数组,模拟每个节点后的下一个节点有26种可能。直接使用数组下标与字母对应,如child[0]表示字母a,child[25]表示字母z,无需开辟单独的空间存储字母。
class TreeNode {
boolean isEnd;// 记录该节点是否为一个单词的结束字母
TreeNode[] child;// 孩子节点为数组,child[0]表示字母a,初始时每个元素为null
public TreeNode() {
child = new TreeNode[26];
isEnd = false;// 默认不是孩子节点
}
}
- 代码如下,运行时间
78ms
:
public class Trie {
private TreeNode root;
/**
* Initialize your data structure here.
*/
public Trie() {
root = new TreeNode();
}
/**
* Inserts a word into the trie.
*/
public void insert(String word) {
TreeNode cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.child[c - 'a'] == null) {// 孩子节点不存在,创建孩子节点
cur.child[c - 'a'] = new TreeNode();
}
cur = cur.child[c - 'a'];// 更新指针
}
cur.isEnd = true;// 到达单词末尾
}
/**
* Returns if the word is in the trie.
*/
public boolean search(String word) {
TreeNode cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.child[c - 'a'] == null) {
return false;
}
cur = cur.child[c - 'a'];
}
return cur.isEnd;// 是否为单词结尾
}
/**
* Returns if there is any word in the trie that starts with the given prefix.
*/
public boolean startsWith(String prefix) {
TreeNode cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.child[c - 'a'] == null) {
return false;
}
cur = cur.child[c - 'a'];
}
return true;
}
}
215. 数组中的第K个最大元素
① 题目描述
中文描述:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/
② 自己的想法:先排序,直接取对应位置的数
- 代码如下,使用了
Arrays.sort
实现数组排序,运行时间2ms
:
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
③ 冒泡排序:第k次的结果,就是第k大的数
- 冒泡排序:① 将序列中相邻元素两两比较,将最大的放在最后面。② 将剩余序列中相邻元素两两比较,将最大的放在最后面重复第② 步,直到只剩下一个数。
- 代码如下,(用的不是冒泡排序,而是交换排序每次比较都交换元素),运行时间
49ms
:
public int findKthLargest(int[] nums, int k) {
for (int i = 0; i < k; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i]<nums[j]){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
}
return nums[k-1];
}
287. 寻找重复数
① 题目描述
中文题目:https://leetcode-cn.com/problems/find-the-duplicate-number/
② 使用哈希表
- 代码如下,运行时间
4ms
:
public int findDuplicate(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (!map.containsKey(nums[i])) {
map.put(nums[i], 1);
} else {
return nums[i];
}
}
return -1;
}
238. 除自身以外数组的乘积
① 题目描述
中文题目:https://leetcode-cn.com/problems/product-of-array-except-self/
② 暴力法(Time Limit Exceeded
)
- 遍历数组,将数组除去自身的其他数相乘。
- 代码如下,运行超时:
public int[] productExceptSelf(int[] nums) {
int[] result = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
int total = 1;
for (int j = 0; j < nums.length; j++) {
if (j != i) {
total = total * nums[j];
}
}
result[i] = total;
}
return result;
}
③ 先算乘积,再做除法
- 注意: 当当前数不为零时,目标乘积为所有元素的乘积除以它自身;否则,需要重新其计算剩余元素的乘积。
- 借助了额外的空间存储结果,空间复杂度 O ( n ) O(n) O(n)。
- 代码如下,运行时间
1ms
:
public int[] productExceptSelf(int[] nums) {
int total = 1;
int[] result = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
total = total * nums[i];
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
result[i] = total / nums[i];
} else {
int temp = 1;
for (int j = 0; j < nums.length; j++) {
if (j != i) {
temp = temp * nums[j];
}
}
result[i] = temp;
}
}
return result;
}
347. 前K个高频元素
① 题目描述
中文题目:https://leetcode-cn.com/problems/top-k-frequent-elements/
② 自己的想法:迭代查找当前的top1
- 先使用哈希表统计每个元素出现的个数,然后遍历哈希表k次,每次均查找出当前的top1,将其存入List中并删除哈希表中的该项。
- 代码如下,运行时间
51ms
:
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> list = new ArrayList<>();
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
for (int i = 0; i < k; i++) {
int max = Integer.MIN_VALUE;
int num = 0;
for (Integer key : map.keySet()) {// 寻找top1
if (max < map.get(key)) {
max = map.get(key);
num = key; // 记录Top1对应的key
}
}
list.add(num);
map.remove(num);// 除去当前的top1
}
return list;
}