Commit b6bdf8cc authored by JingmingZhang's avatar JingmingZhang
Browse files

Merge branch 'master' of github.com:zjming/LeetCodeAnimation

these are solved leetcode problem!
parents 5bf978cf 6db598b8
# LeetCode 第 283 号问题:移动零
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 283 号问题:移动零。题目难度为 Easy,目前通过率为 53.8% 。
### 题目描述
给定一个数组 `nums`,编写一个函数将所有 `0` 移动到数组的末尾,同时保持非零元素的相对顺序。
**示例:**
```
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
```
**说明**:
1. 必须在原数组上操作,不能拷贝额外的数组。
2. 尽量减少操作次数。
### 题目解析
设定一个临时变量 k = 0,遍历数组 nums ,将非零元素移动到 nums[k]位 置,同时 k++,而后将【k,….nums.size()】中的元素置零。
### 解法一
创建一个临时数组 nonZeroElements ,遍历 nums ,将 nums 中非 0 元素赋值到 nonZeroElements中,而后按顺序将 nonZeroElements 赋值到 nums 上,未遍历的元素置 0 ;
动画如下:
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/0eeix.gif)
代码如下:
```
// 时间复杂度: O(n)
// 空间复杂度: O(n)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
vector<int> nonZeroElements;
// 将vec中所有非0元素放入nonZeroElements中
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i])
nonZeroElements.push_back(nums[i]);
// 将nonZeroElements中的所有元素依次放入到nums开始的位置
for(int i = 0 ; i < nonZeroElements.size() ; i ++)
nums[i] = nonZeroElements[i];
// 将nums剩余的位置放置为0
for(int i = nonZeroElements.size() ; i < nums.size() ; i ++)
nums[i] = 0;
}
};
```
### 解法二
设定一个临时变量 k = 0,遍历数组 nums ,将非零元素移动到 nums[k] 位置,同时 k++,而后将【k,….nums.size()】中的元素置零。
动画如下:
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/jodp0.gif)
代码如下:
```
// 原地(in place)解决该问题
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int k = 0; // nums中, [0...k)的元素均为非0元素
// 遍历到第i个元素后,保证[0...i]中所有非0元素
// 都按照顺序排列在[0...k)中
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i])
nums[k++] = nums[i];
// 将nums剩余的位置放置为0
for(int i = k ; i < nums.size() ; i ++)
nums[i] = 0;
}
};
```
### 解法三
思路:设定一个临时变量 k = 0,遍历数组 nums,将非零元素与之前的零元素进行交换,维护变量k的值。
动画如下:
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/gcetr.gif)
代码如下:
```
// 原地(in place)解决该问题
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int k = 0; // nums中, [0...k)的元素均为非0元素
// 遍历到第i个元素后,保证[0...i]中所有非0元素
// 都按照顺序排列在[0...k)中
// 同时, [k...i] 为 0
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i]){
if(k != i){
swap(nums[k++] , nums[i]);
}else{
k ++;
}
}
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/o6der.png)
\ No newline at end of file
# LeetCode 第 295 号问题:数据流的中位数
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 295 号问题:数据流的中位数。难度级别为 Hard,目前通过率为 33.5% 。
### 题目描述
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
**示例:**
```java
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
```
### 题目解析
这道题给我们一个数据流,让我们找出中位数。对于数据流这种动态(流动)的数据,如果使用数组存储,那么每次新进来一个数据都进行排序的话,效率很低。
处理动态数据来说一般使用的数据结构是栈、队列、二叉树、堆。
本题中,我们使用 **堆** 这种数据结构。
首先将数据分为两部分,位于 **上边最大堆** 的数据要比 **下边最小堆** 的数据都要小。
为了保证将数据平均分配到两个堆中,在动态的操作的过程中两个堆中数据的数目之差不能超过 1。
为了保证 **最大堆中的所有数据都小于最小堆中的数据**,在操作过程中,新添加进去的数据需要先和最大堆的最大值或者最小堆中的最小值进行比较。
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/slcao.gif)
### 代码实现
```java
class MedianFinder {
public PriorityQueue<Integer> minheap, maxheap;
public MedianFinder() {
//维护较大的元素的最小堆
maxheap = new PriorityQueue<Integer>(Collections.reverseOrder());
//维护较小元素的最大堆
minheap = new PriorityQueue<Integer>();
}
// Adds a number into the data structure.
public void addNum(int num) {
maxheap.add(num);
minheap.add(maxheap.poll());
if (maxheap.size() < minheap.size()) {
maxheap.add(minheap.poll());
}
}
// Returns the median of current data stream
public double findMedian() {
if (maxheap.size() == minheap.size()) {
return (maxheap.peek() + minheap.peek()) * 0.5;
} else {
return maxheap.peek();
}
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/k2ihh.gif)
\ No newline at end of file
# LeetCode 第 2 号问题:两数相加
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 2 号问题:两数相加。题目难度为 Medium,目前通过率为 33.9% 。
### 题目描述
给出两个 **非空** 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 **逆序** 的方式存储的,并且它们的每个节点只能存储 **一位** 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
**示例:**
```
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
```
### 题目解析
设立一个表示进位的变量`carried`,建立一个新链表,把输入的两个链表从头往后同时处理,每两个相加,将结果加上`carried`后的值作为一个新节点到新链表后面。
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/lchmg.gif)
### 代码实现
```
/// 时间复杂度: O(n)
/// 空间复杂度: O(n)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *p1 = l1, *p2 = l2;
ListNode *dummyHead = new ListNode(-1);
ListNode* cur = dummyHead;
int carried = 0;
while(p1 || p2 ){
int a = p1 ? p1->val : 0;
int b = p2 ? p2->val : 0;
cur->next = new ListNode((a + b + carried) % 10);
carried = (a + b + carried) / 10;
cur = cur->next;
p1 = p1 ? p1->next : NULL;
p2 = p2 ? p2->next : NULL;
}
cur->next = carried ? new ListNode(1) : NULL;
ListNode* ret = dummyHead->next;
delete dummyHead;
return ret;
}
};
```
# LeetCode 第 301 号问题:删除无效的括号
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 第 301 号问题:删除无效的括号。有小伙伴后台留言说这题是中山大学计算机考研机试题。
### 题目描述
删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
**说明:** 输入可能包含了除 `(``)` 以外的字符。
**示例 1:**
```
输入: "()())()"
输出: ["()()()", "(())()"]
```
**示例 2:**
```
输入: "(a)())()"
输出: ["(a)()()", "(a())()"]
```
**示例 3:**
```
输入: ")("
输出: [""]
```
### 题目解析
所谓有效的括号,那么字符串中的左右括号数应该相同,而且每个右括号左边一定有其对应的左括号。这里很容易想到使用一个栈来模拟匹配过程,'(' 入栈,')' 出栈,若栈为空说明该串是符合题意的。
首先对括号进行删除,遍历从前往后遍历可以删除不符合的 ')' 括号,从后往前遍历可以删除不符合的'('括号,通过 BFS,不断的对队列的字符串进行 checkLeft 和 checkRight 操作,若遇到 ture,则说明当前的字符串已经是删除最少无效的括号的最优解了,接着就对队列中的其他字符串进行 check 即可。
这道题目的动画与 LeetCode 第 20 号问题--有效的括号很类似,这里就拿出来进行参考理解一下,区别点就在于多了遍历和哈希存储。
### 动画描述
待补充
### 代码实现
```
public class Solution {
public List<String> removeInvalidParentheses(String s) {
queue.offer(s);
vis.add(s);
boolean flag=false;
List<String> ans=new ArrayList<String>();
while (!queue.isEmpty()){
String cur=queue.poll();
if(flag){
check(cur,ans);
}else{
flag=checkLeft(cur,ans)||checkRight(cur,ans);
}
}
if(ans.size()==0){
ans.add("");
}
return new ArrayList<String>(ans);
}
Set<String> vis=new HashSet<String>();
Queue<String> queue=new LinkedList<String>();
public void check(String s,List<String> ans){ //查看是否正确
Stack<Character> st=new Stack<Character>();
for(char c:s.toCharArray()){
if(c=='('){
st.push(c);
}
if(c==')'){
if(st.isEmpty()){
return;
}
st.pop();
}
}
if(st.isEmpty()){
ans.add(s);
}
}
public boolean checkLeft(String s,List<String> ans){ //检查左边
//delete right
Stack<Character> st=new Stack<Character>();
for(int i=0;i<s.length();++i){
if(s.charAt(i)=='('){
st.push(s.charAt(i));
}else if(s.charAt(i)==')'){
if(st.isEmpty()){
for(int j=i;j>=0;--j){ //删除不符合的')' 多种情况
if(s.charAt(j)==')'){
String s1=s.substring(0,j)+s.substring(j+1);
if(!vis.contains(s1)){
vis.add(s1);
queue.offer(s1);
}
}
}
return false;
}else{
st.pop();
}
}
}
if(st.isEmpty()){
ans.add(s);
return true;
}
return false;
}
public boolean checkRight(String s,List<String> ans){ //检查右边
//delete right
Stack<Character> st=new Stack<Character>();
st.clear();
for(int i=s.length()-1;i>=0;--i){
if(s.charAt(i)==')'){
st.push(s.charAt(i));
}else if(s.charAt(i)=='('){
if(st.isEmpty()){
for(int j=i;j<s.length();++j){
if(s.charAt(j)=='('){ //删除不符合的'(' 多种情况
String s1=s.substring(0,j)+s.substring(j+1);
if(!vis.contains(s1)){
vis.add(s1);
queue.add(s1);
}
}
}
return false;
}else{
st.pop();
}
}
}
if(st.isEmpty()){
ans.add(s);
return true;
}
return false;
}
}
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/syhz6.png)
\ No newline at end of file
# LeetCode 第 326 号问题:3 的幂
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 326 号问题:3 的幂。题目难度为 Easy,目前通过率为 43.5% 。
### 题目描述
给定一个整数,写一个函数来判断它是否是 3 的幂次方。
**示例 1:**
```
输入: 27
输出: true
```
**示例 2:**
```
输入: 0
输出: false
```
**进阶:**
你能不使用循环或者递归来完成本题吗?
### 题目解析
正常的思路是不停地去除以 3,看最后的迭代商是否为 1。这种思路的代码使用到了循环,逼格不够高。
这里取巧的方法 **用到了数论的知识:3 的幂次的质因子只有 3**
题目要求输入的是 int 类型,正数范围是 0 - 2<sup>31</sup>,在此范围中允许的最大的 3 的次方数为 3<sup>19 </sup>= 1162261467 ,那么只要看这个数能否被 n 整除即可。
### 动画描述
待补充
### 代码实现
```java
class Solution {
public boolean isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
}
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fzqbe.png)
\ No newline at end of file
# LeetCode 第 328 号问题:奇偶链表
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 328 号问题:奇偶链表。题目难度为 Medium,目前通过率为 52.0% 。
### 题目描述
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
**示例 1:**
```
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
```
**示例 2:**
```
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
```
**说明:**
- 应当保持奇数节点和偶数节点的相对顺序。
- 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
### 题目解析
这道题给了我们一个链表,让我们分开奇偶节点,所有奇节点在前,偶节点在后。
* 设定两个虚拟节点,`dummyHead1 `用来保存奇节点,`dummyHead2 `来保存偶节点;
* 遍历整个原始链表,将奇节点放于`dummyHead1 `中,其余的放置在`dummyHead2 `
* 遍历结束后,将`dummyHead2 `插入到`dummyHead1 `后面
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/xh0aj.gif)
### 代码实现
```
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head == NULL || head->next == NULL || head->next->next == NULL)
return head;
ListNode* dummyHead1 = new ListNode(-1);
ListNode* dummyHead2 = new ListNode(-1);
ListNode* p1 = dummyHead1;
ListNode* p2 = dummyHead2;
ListNode* p = head;
for(int i = 0; p; i ++)
if(i % 2 == 0){
p1->next = p;
p = p->next;
p1 = p1->next;
p1->next = NULL;
}
else{
p2->next = p;
p = p->next;
p2 = p2->next;
p2->next = NULL;
}
p1->next = dummyHead2->next;
ListNode* ret = dummyHead1->next;
delete dummyHead1;
delete dummyHead2;
return ret;
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/ro88e.png)
\ No newline at end of file
# LeetCode 第 342 号问题:4 的幂
题目来源于 LeetCode 上第 342 号问题:4 的幂。题目难度为 Easy,目前通过率为 45.3% 。
### 题目描述
给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方。
**示例 1:**
```
输入: 16
输出: true
```
**示例 2:**
```
输入: 5
输出: false
```
**进阶:**
你能不使用循环或者递归来完成本题吗?
### 题目解析
这道题最直接的方法就是不停的去除以 4 ,看最终结果是否为 1 ,参见代码如下:
```java
class Solution {
public boolean isPowerOfFour(int num) {
while ( (num != 0) && (num % 4 == 0)) {
num /= 4;
}
return num == 1;
}
}
```
不过这段代码使用了 **循环** ,逼格不够高。
对于一个整数而言,如果这个数是 4 的幂次方,那它必定也是 2 的幂次方。
我们先将 2 的幂次方列出来找一下其中哪些数是 4 的幂次方。
| 十进制 | 二进制 |
| ------ | ------------------------------- |
| 2 | 10 |
| 4 | **100** (1 在第 3 位) |
| 8 | 1000 |
| 16 | **10000**(1 在第 5 位) |
| 32 | 100000 |
| 64 | **1000000**(1 在第 7 位) |
| 128 | 10000000 |
| 256 | **100000000**(1 在第 9 位) |
| 512 | 1000000000 |
| 1024 | **10000000000**(1 在第 11 位) |
找一下规律: 4 的幂次方的数的二进制表示 1 的位置都是在**奇数位**
之前在小吴的文章中判断一个是是否是 2 的幂次方数使用的是位运算 `n & ( n - 1 )`。同样的,这里依旧可以使用位运算:将这个数与特殊的数做位运算。
这个特殊的数有如下特点:
* 足够大,但不能超过 32 位,即最大为 1111111111111111111111111111111( 31 个 1)
* 它的二进制表示中奇数位为 1 ,偶数位为 0
符合这两个条件的二进制数是:
```java
1010101010101010101010101010101
```
**如果用一个 4 的幂次方数和它做与运算,得到的还是 4 的幂次方数**
将这个二进制数转换成 16 进制表示:0x55555555 。有没有感觉逼格更高点。。。
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/c0s9n.png)
### 图片描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/ux5pa.jpg)
### 代码实现
```java
class Solution {
public boolean isPowerOfFour(int num) {
if (num <= 0)
return false;
//先判断是否是 2 的幂
if ((num & num - 1) != 0)
return false;
//如果与运算之后是本身则是 4 的幂
if ((num & 0x55555555) == num)
return true;
return false;
}
}
```
# LeetCode 第 344 号问题:反转字符串
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 第 344 号问题:反转字符串。面试官最喜欢让你手写的一道算法题!
### 题目描述
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 [ASCII](https://baike.baidu.com/item/ASCII) 码表中的可打印字符。
**示例 1:**
```
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
```
**示例 2:**
```
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
```
###
### 题目解析
这道题没什么难度,直接从两头往中间走,同时交换两边的字符。注意需要白板编程写出来即可,也注意千万别回答一句使用 reverse() 这种高级函数来解决。。。
### 动画描述
![](<https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/ariy2.gif>)
### 代码实现
```
class Solution {
public:
string reverseString(string s) {
int i = 0, j = s.size() - 1;
while (i < j){
swap(s[i],s[j]);
i++;
j--;
}
return s;
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/bksj7.png)
\ No newline at end of file
# LeetCode 第 347 号问题:前 K 个高频元素
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
今天分享的题目来源于 LeetCode 上第 347 号问题:前 K 个高频元素。题目难度为 Medium,目前通过率为 56.9% 。
## 题目描述
给定一个非空的整数数组,**返回其中出现频率前 k 高**的元素。
**示例 1:**
```
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
```
**示例 2:**
```
输入: nums = [1], k = 1
输出: [1]
```
**说明:**
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
### 题目解析
### 解法一:粗暴排序法
最简单粗暴的思路就是 **使用排序算法对元素按照频率由高到低进行排序**,然后再取前 k 个元素。
以下十种排序算法,任你挑选!
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/qya5e.png)
可以发现,使用常规的诸如 冒泡、选择、甚至快速排序都是不满足题目要求,它们的时间复杂度都是大于或者等于 O(n log⁡n) ,而题目要求算法的时间复杂度必须优于 O(n log n) 。
#### 复杂度分析
- **时间复杂度**:O(nlogn),n 表示数组长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,排序算法时间复杂度为O(nlogn) ;因此整体时间复杂度为 O(nlogn) 。
- **空间复杂度**:O(n),最极端的情况下(每个元素都不同),用于存储元素及其频率的 Map 需要存储 n 个键值对。
### 解法二:最小堆
题目最终需要返回的是前 k 个频率最大的元素,可以想到借助堆这种数据结构,对于 k 频率之后的元素不用再去处理,进一步优化时间复杂度。
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/nloow.jpg)
具体操作为:
- 借助 **哈希表** 来建立数字和其出现次数的映射,遍历一遍数组统计元素的频率
- 维护一个元素数目为 k 的最小堆
- 每次都将新的元素与堆顶元素(堆中频率最小的元素)进行比较
- 如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中
- 最终,堆中的 k 个元素即为前 k 个高频元素
![堆中的元素就是前 k 个频率最大的元素](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/xged1.gif)
代码如下:
```java
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res;
}
}
```
#### 复杂度分析
- **时间复杂度**:O(nlogk), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,**这里维护堆的数目是 k **,所以这一系列操作的时间复杂度是 O(nlogk)的;因此,总的时间复杂度是 O(nlog⁡k) 。
- **空间复杂度**:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k个元素,因此,空间复杂度是 O(n)。
### 解法三:桶排序法
首先依旧使用哈希表统计频率,统计完成后,创建一个数组,将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标即可。
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/6tge2.jpg)
代码实现如下:
```java
//基于桶排序求解「前 K 个高频元素」
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> res = new ArrayList();
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
//桶排序
//将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标
List<Integer>[] list = new List[nums.length+1];
for(int key : map.keySet()){
// 获取出现的次数作为下标
int i = map.get(key);
if(list[i] == null){
list[i] = new ArrayList();
}
list[i].add(key);
}
// 倒序遍历数组获取出现顺序从大到小的排列
for(int i = list.length - 1;i >= 0 && res.size() < k;i--){
if(list[i] == null) continue;
res.addAll(list[i]);
}
return res;
}
}
```
#### 复杂度分析
- **时间复杂度**:O(n), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);桶的数量为 n + 1,所以桶排序的时间复杂度为 O(n);因此,总的时间复杂度是 O(n)。
- **空间复杂度**:很明显为 O(n)
# LeetCode 第 349 号问题:两个数组的交集
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 349 号问题:两个数组的交集。题目难度为 Easy,目前通过率为 62.3% 。
### 题目描述
给定两个数组,编写一个函数来计算它们的交集。
**示例 1:**
```
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
```
**示例 2:**
```
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
```
**说明:**
- 输出结果中的每个元素一定是唯一的。
- 我们可以不考虑输出结果的顺序。
### 题目解析
容器类 [set](https://zh.cppreference.com/w/cpp/container/set) 的使用。
- 遍历 num1,通过 set 容器 record 存储 num1 的元素
- 遍历 num2 ,在 record 中查找是否有相同的元素,如果有,用 set 容器 resultSet 进行存储
- 将 resultSet 转换为 vector 类型
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/xfx1k.gif)
### 代码实现
```
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> record;
for(int i = 0; i < nums1.size(); i ++){
record.insert(nums1[i]);
}
set<int> resultSet;
for(int i = 0; i < nums2.size();i++){
if(record.find(nums2[i]) != record.end()){
resultSet.insert(nums2[i]);
}
}
vector<int> resultVector;
for(set<int>::iterator iter=resultSet.begin(); iter != resultSet.end();iter++){
resultVector.push_back(*iter);
}
return resultVector;
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/y7jcl.png)
\ No newline at end of file
# LeetCode 第 350 号问题:两个数组的交集 II
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 350 号问题:两个数组的交集 II。题目难度为 Easy,目前通过率为 41.8% 。
### 题目描述
给定两个数组,编写一个函数来计算它们的交集。
**示例 1:**
```
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
```
**示例 2:**
```
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
```
**说明:**
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
- 我们可以不考虑输出结果的顺序。
**进阶:**
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果 *nums1* 的大小比 *nums2* 小很多,哪种方法更优?
- 如果 *nums2* 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
### 题目解析
容器类 [map](https://zh.cppreference.com/w/cpp/container/map) 的使用。
- 遍历 num1,通过map容器 record 存储 num1 的元素与频率
- 遍历 num2 ,在 record 中查找是否有相同的元素(该元素的存储频率大于0),如果有,用map容器resultVector 进行存储,同时该元素的频率减一
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/3kc4w.gif)
### 代码实现
```
// 350. Intersection of Two Arrays II
// https://leetcode.com/problems/intersection-of-two-arrays-ii/description/
// 时间复杂度: O(nlogn)
// 空间复杂度: O(n)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
map<int, int> record;
for(int i = 0 ; i < nums1.size() ; i ++){
record[nums1[i]] += 1;
}
vector<int> resultVector;
for(int i = 0 ; i < nums2.size() ; i ++){
if(record[nums2[i]] > 0){
resultVector.push_back(nums2[i]);
record[nums2[i]] --;
}
}
return resultVector;
}
};
```
#### 执行结果
![img](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/xdsii.png)
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/3zqhi.png)
\ No newline at end of file
# LeetCode 第 3 号问题:无重复字符的最长子串
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 3 号问题:无重复字符的最长子串。题目难度为 Medium,目前通过率为 29.0% 。
### 题目描述
给定一个字符串,请你找出其中不含有重复字符的 **最长子串** 的长度。
**示例 1:**
```java
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc"所以其长度为 3
```
### 题目解析
建立一个256位大小的整型数组 freg ,用来建立字符和其出现位置之间的映射。
维护一个滑动窗口,窗口内的都是没有重复的字符,去尽可能的扩大窗口的大小,窗口不停的向右滑动。
- (1)如果当前遍历到的字符从未出现过,那么直接扩大右边界;
- (2)如果当前遍历到的字符出现过,则缩小窗口(左边索引向右移动),然后继续观察当前遍历到的字符;
- (3)重复(1)(2),直到左边索引无法再移动;
- (4)维护一个结果res,每次用出现过的窗口大小来更新结果 res,最后返回 res 获取结果。
### 动画描述
![动画描述](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/20ahe.gif)
### 代码实现
```c++
// 滑动窗口
// 时间复杂度: O(len(s))
// 空间复杂度: O(len(charset))
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int freq[256] = {0};
int l = 0, r = -1; //滑动窗口为s[l...r]
int res = 0;
// 整个循环从 l == 0; r == -1 这个空窗口开始
// 到l == s.size(); r == s.size()-1 这个空窗口截止
// 在每次循环里逐渐改变窗口, 维护freq, 并记录当前窗口中是否找到了一个新的最优值
while(l < s.size()){
if(r + 1 < s.size() && freq[s[r+1]] == 0){
r++;
freq[s[r]]++;
}else { //r已经到头 || freq[s[r+1]] == 1
freq[s[l]]--;
l++;
}
res = max(res, r-l+1);
}
return res;
}
};
```
# LeetCode 第 445 号问题:两数相加 II
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 445 号问题:两数相加 II。题目难度为 Medium,目前通过率为 48.8% 。
### 题目描述
给定两个**非空**链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
**进阶:**
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
**示例:**
```
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
```
### 题目解析
由于计算时要保证最右边的数对齐,那么很自然的想到先用**栈**存放链表中的每个值,然后依次计算。由于相加时可能产生进位,所以使用一个flag表示是否有进位。
提示:若栈中元素相加结束之后仍有进位,则需要新加入一个头结点。
### 动画描述
![](https://diycode.b0.upaiyun.com/photo/2019/3b0e95a2e5c00ab1071a7232ca329e62.gif)
### 代码实现
```python
class Solution:
def addTwoNumbers(self, l1, l2):
# 分别入栈
stack1 = []
stack2 = []
while l1:
stack1.append(l1.val)
l1 = l1.next
while l2:
stack2.append(l2.val)
l2 = l2.next
flag = 0
head = None
while stack1 or stack2 or flag != 0:
if stack1:
flag += stack1.pop()
if stack2:
flag += stack2.pop()
node = ListNode(flag % 10)
node.next = head
head = node
flag = flag // 10
return head
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fdvu1.png)
\ No newline at end of file
# LeetCode第447号问题:回旋镖的数量
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 447 号问题:回旋镖的数量。题目难度为 Easy,目前通过率为 45.8% 。
### 题目描述
给定平面上 *n* 对不同的点,“回旋镖” 是由点表示的元组 `(i, j, k)` ,其中 `i``j` 之间的距离和 `i``k` 之间的距离相等(**需要考虑元组的顺序**)。
找到所有回旋镖的数量。你可以假设 *n* 最大为 **500**,所有点的坐标在闭区间 **[-10000, 10000]** 中。
**示例:**
```
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]
```
### 题目解析
n 最大为 500,可以使用时间复杂度为 O(n^2)的算法。
- 遍历所有的点,让每个点作为一个锚点
- 然后再遍历其他的点,统计和锚点距离相等的点有多少个
- 然后分别带入 n(n-1) 计算结果并累加到res中
##### Tips:
###### Tip1
- 如果有一个点 a,还有两个点 b 和 c ,如果 ab 和 ac 之间的距离相等,那么就有两种排列方法 abc 和 acb;
- 如果有三个点 b,c,d都分别和a之间的距离相等,那么有六种排列方法,abc, acb, acd, adc, abd, adb;
- 如果有 n 个点和点 a 距离相等,那么排列方式为 n(n-1)。
###### Tip2
- 计算距离时不进行开根运算, 以保证精度;
- 只有当n大于等于2时,res值才会真正增加,因为当n=1时,增加量为`1*(1-1)=0`
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/0ys9o.gif)
### 代码实现
```
// 447. Number of Boomerangs
// https://leetcode.com/problems/number-of-boomerangs/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n)
class Solution {
public:
int numberOfBoomerangs(vector<pair<int, int>>& points) {
int res = 0;
for( int i = 0 ; i < points.size() ; i ++ ){
// record中存储 点i 到所有其他点的距离出现的频次
unordered_map<int, int> record;
for(int j = 0 ; j < points.size() ; j ++){
if(j != i){
// 计算距离时不进行开根运算, 以保证精度
record[dis(points[i], points[j])] += 1;
}
}
for(unordered_map<int, int>::iterator iter = record.begin() ; iter != record.end() ; iter ++){
res += (iter->second) * (iter->second - 1);
}
}
return res;
}
private:
int dis(const pair<int,int> &pa, const pair<int,int> &pb){
return (pa.first - pb.first) * (pa.first - pb.first) +
(pa.second - pb.second) * (pa.second - pb.second);
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/s95kv.png)
\ No newline at end of file
# LeetCode 第 454 号问题:四数相加 II
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 454 号问题:四数相加 II。题目难度为 Medium,目前通过率为 50.8% 。
### 题目描述
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 `(i, j, k, l)` ,使得 `A[i] + B[j] + C[k] + D[l] = 0`
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
**例如:**
```
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
```
### 题目解析
[Two Sum](https://xiaozhuanlan.com/topic/7923618450)类似,需要用哈希表来解决问题。
- 把 A 和 B 的两两之和都求出来,在哈希表中建立两数之和与其出现次数之间的映射
- 遍历 C 和 D 中任意两个数之和,只要看哈希表存不存在这两数之和的相反数就行了
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/dgth9.gif)
### 代码实现
```
// 454. 4Sum II
// https://leetcode.com/problems/4sum-ii/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2)
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
unordered_map<int,int> hashtable;
for(int i = 0 ; i < A.size() ; i ++){
for(int j = 0 ; j < B.size() ; j ++){
hashtable[A[i]+B[j]] += 1;
}
}
int res = 0;
for(int i = 0 ; i < C.size() ; i ++){
for(int j = 0 ; j < D.size() ; j ++){
if(hashtable.find(-C[i]-D[j]) != hashtable.end()){
res += hashtable[-C[i]-D[j]];
}
}
}
return res;
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/sx6gy.png)
\ No newline at end of file
# LeetCode 第 642 号问题:设计一个搜索自动完成系统
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 642 号问题:设计一个搜索自动完成系统。题目难度为 Hard,目前通过率为 37.8% 。
### 题目描述
为搜索引擎设计一个搜索自动完成系统。用户可以输入一个句子(至少一个单词,并以一个特殊的字符'#'结尾)。对于除'#'之外的每个字符,您需要返回与已输入的句子部分前缀相同的前3个历史热门句子。具体规则如下:
一个句子的热度定义为用户输入完全相同句子的次数。
返回的前3个热门句子应该按照热门程度排序(第一个是最热的)。如果几个句子的热度相同,则需要使用ascii代码顺序(先显示较小的一个)。
如果少于3个热门句子,那么就尽可能多地返回。
当输入是一个特殊字符时,它意味着句子结束,在这种情况下,您需要返回一个空列表。
您的工作是实现以下功能:
构造函数:
AutocompleteSystem(String[] sentence, int[] times):这是构造函数。输入是历史数据。句子是由之前输入的句子组成的字符串数组。Times是输入一个句子的相应次数。您的系统应该记录这些历史数据。
现在,用户想要输入一个新句子。下面的函数将提供用户类型的下一个字符:
List<String> input(char c):输入c是用户输入的下一个字符。字符只能是小写字母(“a”到“z”)、空格(“”)或特殊字符(“#”)。另外,前面输入的句子应该记录在系统中。输出将是前3个历史热门句子,它们的前缀与已经输入的句子部分相同。
例子:
操作:AutocompleteSystem(["i love you", "island","ironman", "i love leetcode"], [5,3,2,2])
系统已经追踪到以下句子及其对应的时间:
"i love you" : 5 times
"island" : 3 times
"ironman" : 2 times
"i love leetcode" : 2 times
现在,用户开始另一个搜索:
操作:输入(“i”)
输出:["i love you", "island","i love leetcode"]
解释:
有四个句子有前缀“i”。其中,《ironman》和《i love leetcode》有着相同的热度。既然“ ” ASCII码为32,“r”ASCII码为114,那么“i love leetcode”应该在“ironman”前面。此外,我们只需要输出前3个热门句子,所以“ironman”将被忽略。
操作:输入(' ')
输出:[“i love you”,“i love leetcode”]
解释:
只有两个句子有前缀“i”。
操作:输入(' a ')
输出:[]
解释:
没有以“i a”为前缀的句子。
操作:输入(“#”)
输出:[]
解释:
用户完成输入后,在系统中将句子“i a”保存为历史句。下面的输入将被计算为新的搜索。
注意:
输入的句子总是以字母开头,以“#”结尾,两个单词之间只有一个空格。
要搜索的完整句子不会超过100个。包括历史数据在内的每句话的长度不会超过100句。
在编写测试用例时,即使是字符输入,也请使用双引号而不是单引号。
请记住重置在AutocompleteSystem类中声明的类变量,因为静态/类变量是跨多个测试用例持久化的。详情请点击这里。
### 题目解析
设计一个搜索自动补全系统,它需要包含如下两个方法:
#### 构造方法:
AutocompleteSystem(String[] sentences, int[] times): 输入句子sentences,及其出现次数times
#### 输入方法:
List<String> input(char c): 输入字符c可以是26个小写英文字母,也可以是空格,以'#'结尾。返回输入字符前缀对应频率最高的至多3个句子,频率相等时按字典序排列。
### 思路解析:
核心点:Trie(字典树)
利用字典树记录所有出现过的句子集合,利用字典保存每个句子出现的次数。
#### 解题思路
题目的要求是补全的句子是按之前出现的频率排列的,高频率的出现在最上面,如果频率相同,就按字母顺序来显示。
频率 这种要求很容易想到 堆、优先队列、树、Map等知识点,这里涉及到 字典 与 树,那肯定使用 字典树 能解决。
所以首先构造 Trie 的 trieNode 结构以及 insert 方法,构造完 trieNode 类后,再构造一个树的根节点。
由于每次都要输入一个字符,我们可以用一个私有的 Node:curNode 来追踪当前的节点。
curNode 初始化为 root ,在每次输入完一个句子时,即输入的字符为‘#’时,我们需要将其置为root。
同时还需要一个 string 类型 stn 来表示当前的搜索的句子。
每输入一个字符,首先检查是不是结尾标识“#”,如果是的话,将当前句子加入trie树,重置相关变量,返回空数组。
* 如不是,检查当前 TrieNode 对应的 child 是否含有 c 的对应节点。如果没有,将 curNode 置为 NULL 并且返回空数组。
* 若存在,将curNode 更新为c对应的节点,并且对curNode进行dfs。
dfs 时,我们首先检查当前是不是一个完整的句子,如果是,将句子与其次数同时加入 priority_queue 中,然后对其 child 中可能存在的子节点进行 dfs 。
进行完 dfs 后,只需要取出前三个,需要注意的是,可能可选择的结果不满3个,所以要在 while 中多加入检测 q 为空的条件语句。
最后要将 q 中的所有元素都弹出。
### 动画描述
动画是使用 AE 制作,体积比较大,有 32 M,无法使用GIF播放,因此采取视频播放形式,手机党慎点:)
感谢 **Jun Chen** 大佬提供动画技术支持,笔芯。
https://v.qq.com/x/page/m08267nr4fv.html
### 代码实现
#### C++
```
class TrieNode{
public:
string str;
int cnt;
unordered_map<char, TrieNode*> child;
TrieNode(): str(""), cnt(0){};
};
struct cmp{
bool operator() (const pair<string, int> &p1, const pair<string, int> &p2){
return p1.second < p2.second || (p1.second == p2.second && p1.first > p2.first);
}
};
class AutocompleteSystem {
public:
AutocompleteSystem(vector<string> sentences, vector<int> times) {
root = new TrieNode();
for(int i = 0; i < sentences.size(); i++){
insert(sentences[i], times[i]);
}
curNode = root;
stn = "";
}
vector<string> input(char c) {
if(c == '#'){
insert(stn, 1);
stn.clear();
curNode = root;
return {};
}
stn.push_back(c);
if(curNode && curNode->child.count(c)){
curNode = curNode->child[c];
}else{
curNode = NULL;
return {};
}
dfs(curNode);
vector<string> ret;
int n = 3;
while(n > 0 && !q.empty()){
ret.push_back(q.top().first);
q.pop();
n--;
}
while(!q.empty()) q.pop();
return ret;
}
void dfs(TrieNode* n){
if(n->str != ""){
q.push({n->str, n->cnt});
}
for(auto p : n->child){
dfs(p.second);
}
}
void insert(string s, int cnt){
TrieNode* cur = root;
for(auto c : s){
if(cur->child.count(c) == 0){
cur->child[c] = new TrieNode();
}
cur = cur->child[c];
}
cur->str = s;
cur->cnt += cnt;
}
private:
TrieNode *root, *curNode;
string stn;
priority_queue<pair<string,int>, vector<pair<string, int>>, cmp > q;
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/gxmux.png)
\ No newline at end of file
# LeetCode 第 66 号问题:加一
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
今天分享的题目来源于 LeetCode 上第 66 号问题:加一。题目难度为 Easy,目前通过率为 39.0% 。
### 题目描述
给定一个由**整数**组成的**非空**数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
**示例 1:**
```
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
```
**示例 2:**
```
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
```
**示例 3:**
```
//为了更好理解题意,根据 LeetCode 评论区评论新增一个示例
输入: [9,9]
输出: [1,0,0]
解释: 输入数组表示数字 100。
```
### 题目解析
本题很简单,题目意思也很好理解,注意的点就是 **进位问题**
* 如果数组末位(个位)小于 9 ,直接个位加 1 返回即可
* 如果数组末位(个位)等于 9,将该位(个位)设置为 0 ,并且产生了进位,接下来观察前一位(十位)
* * 如果前一位(十位)小于 9 ,直接十位加 1 返回即可
* 如果前一位(十位)等于 9,将该位(十位)设置为 0 ,并且产生了进位,接下来观察前一位(百位)
* 以此类推,最后观察运算完的第一位是否为 0 ,如果为 0 ,则在最前面加 1 (**示例 3**
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/iejo0.gif)
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/15na7.gif)
### 代码实现
```java
public class Solution {
public int[] plusOne(int[] digits) {
int n = digits.length;
//从数组末尾开始向前遍历
for (int i = digits.length - 1; i >= 0; --i) {
if (digits[i] < 9) {
digits[i]++;
//没有进位,直接返回
return digits;
}
//产生进位,需要将该位赋值为 0
digits[i] = 0;
}
//整体产生了进位,数组长度需要变化加 1
int[] res = new int[n + 1];
res[0] = 1;
return res;
}
}
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/r5cpx.png)
# LeetCode 第 690 号问题:员工的重要性
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 第 690 号问题:员工的重要性。
### 题目描述
给定一个保存员工信息的数据结构,它包含了员工**唯一的id****重要度****直系下属的id**
比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15, 10, 5 。那么员工 1 的数据结构是[1, 15, [2]],员工 2 的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。注意虽然员工 3 也是员工 1 的一个下属,但是由于**并不是直系**下属,因此没有体现在员工1的数据结构中。
现在输入一个公司的所有员工信息,以及单个员工 id,返回这个员工和他所有下属的重要度之和。
**示例 1:**
```
输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出: 11
解释:
员工 1 自身的重要度是 5,他有两个直系下属 2 和 3 ,而且 2 和 3 的重要度均为 3 。因此员工 1 的总重要度是 5 + 3 + 3 = 11。
```
**注意:**
1. 一个员工最多有一个**直系**领导,但是可以有多个**直系**下属
2. 员工数量不超过 2000。
###
### 题目解析
利用哈希表来存储员工的信息,找到指定 id 的员工后,采用广度优先遍历算法来遍历编号为 id 的员工及其下属员工。
### 动画描述
待补充
### 代码实现
```
public int getImportance(List<Employee> employees, int id) {
Employee emp = null;
//重要度
int sum = 0;
//存储员工信息
HashMap<Integer,Employee> map=new HashMap<Integer,Employee>(); /
for(Employee e:employees) {
map.put(e.id, e);
}
//使用广度优先遍历员工
ArrayDeque<Employee> queue=new ArrayDeque<Employee>();
queue.addLast(map.get(id));
while(!queue.isEmpty()) {
emp=queue.removeFirst();
sum+=emp.importance;
for(int i:emp.subordinates) {
queue.addLast(map.get(i));
}
}
return sum;
}
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/wvk3e.png)
\ No newline at end of file
# LeetCode 第 75 号问题:颜色分类
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 75 号问题:颜色分类。题目难度为 Medium,目前通过率为 51.8% 。
### 题目描述
给定一个包含红色、白色和蓝色,一共 *n* 个元素的数组,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
**注意:**
不能使用代码库中的排序函数来解决这道题。
**示例:**
```
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
```
**进阶:**
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
### 题目解析
结合三路快排 partition 思路的应用。
设定两个索引,一个从左往右滑动`zero`,一个从右往左滑动`two`
* 遍历`nums`,当`nums[i]`的值为1时,`i++`
*`nums[i]`的值为2时,`two`的值先减1,而后交换`nums[i]``nums[two]`,此时在观察`nums[i]`的值;
*`nums[i]`的值为0时,`zero++`,而后交换`nums[i]``nums[zero]``i++`;当 `i = two`时,结束循环。
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/6g5tm.gif)
### 代码实现
```
// 三路快速排序的思想
// 对整个数组只遍历了一遍
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
void sortColors(vector<int> &nums) {
int zero = -1; // [0...zero] == 0
int two = nums.size(); // [two...n-1] == 2
for(int i = 0 ; i < two ; ){
if(nums[i] == 1){
i ++;
}else if (nums[i] == 2){
two--;
swap( nums[i] , nums[two]);
}else{ // nums[i] == 0
zero++;
swap(nums[zero] , nums[i]);
i++;
}
}
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/r5e2r.png)
\ No newline at end of file
# LeetCode 第 86 号问题:分割链表
> 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
题目来源于 LeetCode 上第 86 号问题:分割链表。题目难度为 Easy,目前通过率为 47.8% 。
### 题目描述
给定一个链表和一个特定值 *x*,对链表进行分隔,使得所有小于 *x* 的节点都在大于或等于 *x* 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
**示例:**
```
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
```
### 题目解析
这道题要求我们划分链表,把所有小于给定值的节点都移到前面,大于该值的节点顺序不变,相当于一个局部排序的问题。
- 设定两个虚拟节点,`dummyHead1 `用来保存小于于该值的链表,`dummyHead2 `来保存大于等于该值的链表
- 遍历整个原始链表,将小于该值的放于`dummyHead1 `中,其余的放置在`dummyHead2 `
- 遍历结束后,将`dummyHead2 `插入到`dummyHead1 `后面
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/t96zg.gif)
### 代码实现
```
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* dummyHead1 = new ListNode(-1);
ListNode* dummyHead2 = new ListNode(-1);
ListNode* prev1 = dummyHead1;
ListNode* prev2 = dummyHead2;
for(ListNode* cur = head ; cur != NULL ;){
if(cur->val < x){
prev1->next = cur;
cur = cur->next;
prev1 = prev1->next;
prev1->next = NULL;
}
else{
prev2->next = cur;
cur = cur->next;
prev2 = prev2->next;
prev2->next = NULL;
}
}
prev1->next = dummyHead2->next;
ListNode* ret = dummyHead1->next;
delete dummyHead1;
delete dummyHead2;
return ret;
}
};
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/5a3tl.png)
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment