"src/main/vscode:/vscode.git/clone" did not exist on "5b46f34bfe47185e2d35868db64eb0e1eb6fe4f2"
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
*.js linguist-language=java
*.css linguist-language=java
*.html linguist-language=java
*.md linguist-language=java
# LeetCode 第 1 号问题:两数之和
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
>
题目来源于 LeetCode 上第 1 号问题:两数之和。题目难度为 Easy,目前通过率为 45.8% 。
### 题目描述
给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那 **两个** 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
**示例:**
```
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
```
### 题目解析
使用查找表来解决该问题。
设置一个 map 容器 record 用来记录元素的值与索引,然后遍历数组 nums。
* 每次遍历时使用临时变量 complement 用来保存目标值与当前值的差值
* 在此次遍历中查找 record ,查看是否有与 complement 一致的值,如果查找成功则返回查找值的索引值与当前变量的值 i
* 如果未找到,则在 record 保存该元素与索引值 i
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 1. Two Sum
// https://leetcode.com/problems/two-sum/description/
// 时间复杂度:O(n)
// 空间复杂度:O(n)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> record;
for(int i = 0 ; i < nums.size() ; i ++){
int complement = target - nums[i];
if(record.find(complement) != record.end()){
int res[] = {i, record[complement]};
return vector<int>(res, res + 2);
}
record[nums[i]] = i;
}
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 2 号问题:两数相加
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 2 号问题:两数相加。题目难度为 Medium,目前通过率为 33.9% 。
### 题目描述
给出两个 **非空** 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 **逆序** 的方式存储的,并且它们的每个节点只能存储 **一位** 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
**示例:**
```
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
```
### 题目解析
设立一个表示进位的变量`carried`,建立一个新链表,把输入的两个链表从头往后同时处理,每两个相加,将结果加上`carried`后的值作为一个新节点到新链表后面。
### 动画描述
![](../Animation/Animation.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;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 3 号问题:无重复字符的最长子串
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.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;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 6 号问题:蛇形矩阵
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
>
题目来源于 LeetCode 上第 6 号问题:蛇形矩阵。题目难度为 Medium,目前通过率为 35.1% 。
## 题目描述
给定一个字符串,和一个整数n,将它排列成一个n行的蛇形返回。
## 示例
```
P A H N
A P L S I I G
Y I R
And then read line by line: "PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
string convert(string s, int numRows);
Example 1:
Input: s = "PAYPALISHIRING", numRows = 3
Output: "PAHNAPLSIIGYIR"
Example 2:
Input: s = "PAYPALISHIRING", numRows = 4
Output: "PINALSIGYAHRPI"
Explanation:
P I N
A L S I G
Y A H R
P I
```
给定字符串和蛇形排列占据的行数,要求返回重新排列后的串
## 题目解析
这道题是一道模拟题,题目的要求就是答案,我们只需要读懂题意就很容易实现。
我们最终要输出的是以蛇形摆放之后的字符串再按行串联在一起之后的结果,也就是说每一个字母摆放的列并不重要,重要的是摆放的行号。我们可以很容易想到通过数组维护每一行当中摆放的字母,最后将每一行的结果串联即可。所以问题就只剩下了,我们如何知道每一个字母应该摆放在哪一行?
其实这也是有规律的,我们通过观察样例可以发现,我们每一个字母摆放的行号先是从0递增到n-1,再从n-1递减到0。我们就模拟这个过程,一个字符一个字符的放置即可。
比如字符串是“PAYPALISHIRING ”,rowNum=4。我们可以创建四个空串:
“”
“”
“”
“”
然后我们按照蛇形一个字母一个字母地放进这些空串当中:
当放了第一个字母p之后,变成:
“p”
“”
“”
“”
接着放第二个:
“p”
“a”
“”
“”
接着第三个:
“p”
“a”
“y”
“”
当我们把所有字母都放完了之后,可以得到这样的四个串:
“PIN”
“ALSIG”
“YAHR”
“PI”
然后把这四串拼接在一起就行了。
#### 动画描述
![](../Animation/LeetCode6.gif)
#### 代码实现
```python
class Solution:
def convert(self, s: str, numRows: int) -> str:
# 记录每一行摆放的字母
rows = ['' for _ in range(numRows)]
# 记录当前行号
cur_row = 0
# 记录当前摆放顺序是否从上往下, False代表从下往上
forward = True
# numRows = 1直接返回
if numRows == 1:
return s
for i, c in enumerate(s):
rows[cur_row] += c
# 根据顺序变更行号
if forward:
cur_row += 1
else:
cur_row -= 1
# 根据行号和当前顺序判断需不需要转向
if cur_row == numRows - 1 and forward:
forward = False
if cur_row == 0 and not forward:
forward = True
ret = ''
for sc in rows:
ret += sc
return ret
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 9 号问题:回文数
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 第 9 号问题:回文数。题目难度为 Easy,目前通过率为 56.0%。
## 题目描述
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
**示例 1:**
```
输入: 121
输出: true
```
**示例 2:**
```
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
```
**示例 3:**
```
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
```
**进阶:**
你能不将整数转为字符串来解决这个问题吗?
## 题目解析
### 解法一:普通解法
最好理解的一种解法就是先将 **整数转为字符串** ,然后将字符串分割为数组,只需要循环数组的一半长度进行判断对应元素是否相等即可。
#### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/ods8b.gif)
#### 代码实现
```java
///简单粗暴,看看就行
class Solution {
public boolean isPalindrome(int x) {
String reversedStr = (new StringBuilder(x + "")).reverse().toString();
return (x + "").equals(reversedStr);
}
}
```
### 解法二:进阶解法---数学解法
通过取整和取余操作获取整数中对应的数字进行比较。
举个例子:1221 这个数字。
- 通过计算 1221 / 1000, 得首位1
- 通过计算 1221 % 10, 可得末位 1
- 进行比较
- 再将 22 取出来继续比较
#### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/v3tkl.gif)
#### 代码实现
```java
class Solution {
public boolean isPalindrome(int x) {
//边界判断
if (x < 0) return false;
int div = 1;
//
while (x / div >= 10) div *= 10;
while (x > 0) {
int left = x / div;
int right = x % 10;
if (left != right) return false;
x = (x % div) / 10;
div /= 100;
}
return true;
}
}
```
### 解法三:进阶解法---巧妙解法
直观上来看待回文数的话,就感觉像是将数字进行对折后看能否一一对应。
所以这个解法的操作就是 **取出后半段数字进行翻转**
这里需要注意的一个点就是由于回文数的位数可奇可偶,所以当它的长度是偶数时,它对折过来应该是相等的;当它的长度是奇数时,那么它对折过来后,有一个的长度需要去掉一位数(除以 10 并取整)。
具体做法如下:
- 每次进行取余操作 ( %10),取出最低的数字:`y = x % 10`
- 将最低的数字加到取出数的末尾:`revertNum = revertNum * 10 + y`
- 每取一个最低位数字,x 都要自除以 10
- 判断 `x` 是不是小于 `revertNum` ,当它小于的时候,说明数字已经对半或者过半了
- 最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。
#### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/0siv7.png)
#### 代码实现
```java
class Solution {
public boolean isPalindrome(int x) {
//思考:这里大家可以思考一下,为什么末尾为 0 就可以直接返回 false
if (x < 0 || (x % 10 == 0 && x != 0)) return false;
int revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x /= 10;
}
return x == revertedNumber || x == revertedNumber / 10;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 15 号问题:三数之和
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 15 号问题:三数之和。
### 题目描述
给定一个包含 *n* 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 *a,b,c ,*使得 *a + b + c =* 0 ?找出所有满足条件且不重复的三元组。
### 题目解析
题目需要我们找出三个数且和为 0 ,那么除了三个数全是 0 的情况之外,肯定会有负数和正数,所以一开始可以先选择一个数,然后再去找另外两个数,这样只要找到两个数且和为第一个选择的数的相反数就行了。也就是说需要枚举 a 和 b ,将 c 的存入 map 即可。
需要注意的是返回的结果中,不能有有重复的结果。这样的代码时间复杂度是 O(n^2)。在这里可以先将原数组进行排序,然后再遍历排序后的数组,这样就可以使用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。
### 动画描述
待补充
### 代码实现
###
```c++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
for (int k = 0; k < nums.size(); ++k) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] == nums[k - 1]) continue;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.push_back({nums[k], nums[i], nums[j]});
while (i < j && nums[i] == nums[i + 1]) ++i;
while (i < j && nums[j] == nums[j - 1]) --j;
++i; --j;
} else if (nums[i] + nums[j] < target) ++i;
else --j;
}
}
return res;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 19 号问题:删除链表的倒数第 N 个节点
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 19 号问题:删除链表的倒数第 N 个节点。题目难度为 Medium,目前通过率为 34.4% 。
### 题目描述
给定一个链表,删除链表的倒数第 *n* 个节点,并且返回链表的头结点。
**示例:**
```
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
```
**说明:**
给定的 *n* 保证是有效的。
**进阶:**
你能尝试使用一趟扫描实现吗?
### 题目解析
采取双重遍历肯定是可以解决问题的,但题目要求我们一次遍历解决问题,那我们的思路得发散一下。
我们可以设想假设设定了双指针`p``q`的话,当`q`指向末尾的`NULL``p``q`之间相隔的元素个数为`n`时,那么删除掉`p`的下一个指针就完成了要求。
- 设置虚拟节点`dummyHead`指向`head`
- 设定双指针`p``q`,初始都指向虚拟节点`dummyHead`
- 移动`q`,直到`p``q`之间相隔的元素个数为`n`
- 同时移动`p``q`,直到`q`指向的为`NULL`
-`p`的下一个节点指向下下个节点
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/r04hv.gif)
### 代码实现
```
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* p = dummyHead;
ListNode* q = dummyHead;
for( int i = 0 ; i < n + 1 ; i ++ ){
q = q->next;
}
while(q){
p = p->next;
q = q->next;
}
ListNode* delNode = p->next;
p->next = delNode->next;
delete delNode;
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 20 号问题:有效的括号
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 20 号问题:有效的括号。题目难度为 Easy,目前通过率为 37.8% 。
### 题目描述
给定一个只包括 `'('``')'``'{'``'}'``'['``']'` 的字符串,判断字符串是否有效。
有效字符串需满足:
1. 左括号必须用相同类型的右括号闭合。
2. 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
**示例 1:**
```
输入: "()"
输出: true
```
**示例 2:**
```
输入: "()[]{}"
输出: true
```
**示例 3:**
```
输入: "(]"
输出: false
```
**示例 4:**
```
输入: "([)]"
输出: false
```
**示例 5:**
```
输入: "{[]}"
输出: true
```
### 题目解析
这道题让我们验证输入的字符串是否为括号字符串,包括大括号,中括号和小括号。
这里我们使用**栈**
- 遍历输入字符串
- 如果当前字符为左半边括号时,则将其压入栈中
- 如果遇到右半边括号时,**分类讨论:**
- 1)如栈不为空且为对应的左半边括号,则取出栈顶元素,继续循环
- 2)若此时栈为空,则直接返回false
- 3)若不为对应的左半边括号,反之返回false
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/xu55u.gif)
### 代码实现
```
class Solution {
public:
bool isValid(string s) {
stack<char> stack;
for( int i = 0 ; i < s.size() ; i ++ )
if( s[i] == '(' || s[i] == '{' || s[i] == '[')
stack.push(s[i]);
else{
if( stack.size() == 0 )
return false;
char c = stack.top();
stack.pop();
char match;
if( s[i] == ')' ){
match = '(';
}
else if( s[i] == ']' ){
match = '[';
}
else{
match = '{';
}
if(c != match) return false;
}
if( stack.size() != 0 )
return false;
return true;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 21 号问题:合并两个有序链表
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 21 号问题:合并两个有序链表。题目难度为 Easy,目前通过率为 45.8% 。
### 题目描述
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
**示例:**
```
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
```
### 题目解析
#### 一般方案
##### 1.1 解题思想
> (1)对空链表存在的情况进行处理,假如 pHead1 为空则返回 pHead2 ,pHead2 为空则返回 pHead1。(两个都为空此情况在pHead1为空已经被拦截)
> (2)在两个链表无空链表的情况下确定第一个结点,比较链表1和链表2的第一个结点的值,将值小的结点保存下来为合并后的第一个结点。并且把第一个结点为最小的链表向后移动一个元素。
> (3)继续在剩下的元素中选择小的值,连接到第一个结点后面,并不断next将值小的结点连接到第一个结点后面,直到某一个链表为空。
> (4)当两个链表长度不一致时,也就是比较完成后其中一个链表为空,此时需要把另外一个链表剩下的元素都连接到第一个结点的后面。
##### 1.2 代码实现
```c++
ListNode* mergeTwoOrderedLists(ListNode* pHead1, ListNode* pHead2){
ListNode* pTail = NULL;//指向新链表的最后一个结点 pTail->next去连接
ListNode* newHead = NULL;//指向合并后链表第一个结点
if (NULL == pHead1){
return pHead2;
}else if(NULL == pHead2){
return pHead1;
}else{
//确定头指针
if ( pHead1->data < pHead2->data){
newHead = pHead1;
pHead1 = pHead1->next;//指向链表的第二个结点
}else{
newHead = pHead2;
pHead2 = pHead2->next;
}
pTail = newHead;//指向第一个结点
while ( pHead1 && pHead2) {
if ( pHead1->data <= pHead2->data ){
pTail->next = pHead1;
pHead1 = pHead1->next;
}else {
pTail->next = pHead2;
pHead2 = pHead2->next;
}
pTail = pTail->next;
}
if(NULL == pHead1){
pTail->next = pHead2;
}else if(NULL == pHead2){
pTail->next = pHead1;
}
return newHead;
}
```
#### 2 递归方案
##### 2.1 解题思想
> (1)对空链表存在的情况进行处理,假如 pHead1 为空则返回 pHead2 ,pHead2 为空则返回 pHead1。
> (2)比较两个链表第一个结点的大小,确定头结点的位置
> (3)头结点确定后,继续在剩下的结点中选出下一个结点去链接到第二步选出的结点后面,然后在继续重复(2 )(3) 步,直到有链表为空。
##### 2.2 代码实现
```c++
ListNode* mergeTwoOrderedLists(ListNode* pHead1, ListNode* pHead2){
ListNode* newHead = NULL;
if (NULL == pHead1){
return pHead2;
}else if(NULL ==pHead2){
return pHead1;
}else{
if (pHead1->data < pHead2->data){
newHead = pHead1;
newHead->next = mergeTwoOrderedLists(pHead1->next, pHead2);
}else{
newHead = pHead2;
newHead->next = mergeTwoOrderedLists(pHead1, pHead2->next);
}
return newHead;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 23 号问题:合并 K 个排序链表
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 23 号问题:合并 K 个排序链表。题目难度为 Hard,目前通过率为 45.8% 。
### 题目描述
合并 *k* 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
**示例:**
```
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
```
**输入**
![图一](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/u2jnp.jpg)
**输出**
![图二](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/yc4ac.jpg)
### 题目解析
### 题目分析一
这里需要将这 *k* 个排序链表整合成一个排序链表,也就是说有多个输入,一个输出,类似于漏斗一样的概念。
因此,可以利用最小堆的概念。如果你对堆的概念不熟悉,可以戳这先了解一下~
取每个 Linked List 的最小节点放入一个 heap 中,排序成最小堆。然后取出堆顶最小的元素,放入输出的合并 List 中,然后将该节点在其对应的 List 中的下一个节点插入到 heap 中,循环上面步骤,以此类推直到全部节点都经过 heap。
由于 heap 的大小为始终为 k ,而每次插入的复杂度是 logk ,一共插入了 nk 个节点。时间复杂度为 O(nklogk),空间复杂度为O(k)。
### 动画演示
![动画演示](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/iuxmh.gif)
### 代码实现
```java
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
//用heap(堆)这种数据结构,也就是 java 里面的 PriorityQueue
PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
public int compare(ListNode a, ListNode b) {
return a.val-b.val;
}
});
ListNode ret = null, cur = null;
for(ListNode node: lists) {
if(null != node) {
pq.add(node);
}
}
while(!pq.isEmpty()) {
ListNode node = pq.poll();
if(null == ret) {
ret = cur = node;
}
else {
cur = cur.next = node;
}
if(null != node.next) {
pq.add(node.next);
}
}
return ret;
}
}
```
### 题目分析二
这道题需要合并 k 个有序链表,并且最终合并出来的结果也必须是有序的。如果一开始没有头绪的话,可以先从简单的开始:**合并 两 个有序链表**
合并两个有序链表:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
**示例:**
```
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
```
这道题目按照题目描述做下去就行:新建一个链表,比较原始两个链表中的元素值,把较小的那个链到新链表中即可。需要注意的一点时由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。
所以代码实现很容易写:
```java
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//新建链表
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
cur = cur.next;
l1 = l1.next;
} else {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
}
}
// 注意点:当有链表为空时,直接连接另一条链表
if (l1 == null) {
cur.next = l2;
} else {
cur.next = l1;
}
return dummyHead.next;
}
```
现在回到一开始的题目:合并 K 个排序链表。
**合并 K 个排序链表****合并两个有序链表** 的区别点在于操作有序链表的数量上,因此完全可以按照上面的代码思路来实现合并 K 个排序链表。
这里可以参考 **归并排序 **的分治思想,将这 K 个链表先划分为两个 K/2 个链表,处理它们的合并,然后不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。
![归并-分治](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/74ush.gif)
### 代码实现
根据上面的动画,实现代码非常简单也容易理解,先划分,直到不能划分下去,然后开始合并。
```java
class Solution {
public ListNode mergeKLists(ListNode[] lists){
if(lists.length == 0)
return null;
if(lists.length == 1)
return lists[0];
if(lists.length == 2){
return mergeTwoLists(lists[0],lists[1]);
}
int mid = lists.length/2;
ListNode[] l1 = new ListNode[mid];
for(int i = 0; i < mid; i++){
l1[i] = lists[i];
}
ListNode[] l2 = new ListNode[lists.length-mid];
for(int i = mid,j=0; i < lists.length; i++,j++){
l2[j] = lists[i];
}
return mergeTwoLists(mergeKLists(l1),mergeKLists(l2));
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode head = null;
if (l1.val <= l2.val){
head = l1;
head.next = mergeTwoLists(l1.next, l2);
} else {
head = l2;
head.next = mergeTwoLists(l1, l2.next);
}
return head;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 24 号问题:两两交换链表中的节点
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 24 号问题:两两交换链表中的节点。题目难度为 Medium,目前通过率为 45.8% 。
### 题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
**你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。
**示例:**
```
给定 1->2->3->4, 你应该返回 2->1->4->3.
```
### 题目解析
该题属于基本的链表操作题。
- 设置一个虚拟头结点`dummyHead `
- 设置需要交换的两个节点分别为`node1 ``node2`,同时设置`node2`的下一个节点`next`
##### 在这一轮操作中
-`node2`节点的next设置为`node1`节点
-`node1`节点的next设置为`next `节点
-`dummyHead `节点的next设置为`node2 `
- 结束本轮操作
接下来的每轮操作都按照上述进行。
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/6kpyu.gif)
### 代码实现
```
// 24. Swap Nodes in Pairs
// https://leetcode.com/problems/swap-nodes-in-pairs/description/
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* p = dummyHead;
while(p->next && p->next->next){
ListNode* node1 = p->next;
ListNode* node2 = node1->next;
ListNode* next = node2->next;
node2->next = node1;
node1->next = next;
p->next = node2;
p = node1;
}
ListNode* retHead = dummyHead->next;
delete dummyHead;
return retHead;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 26 号问题:删除排序数组中的重复项
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 26 号问题:删除排序数组中的重复项。题目难度为 Easy,目前通过率为 48.8% 。
### 题目描述
给定一个排序数组,你需要在**原地**删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在**原地修改输入数组**并在使用 O(1) 额外空间的条件下完成。
**示例 1:**
```
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
```
**示例 2:**
```
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
```
**说明:**
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**“引用”**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
```
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
```
### 题目解析
使用快慢指针来记录遍历的坐标。
- 开始时这两个指针都指向第一个数字
- 如果两个指针指的数字相同,则快指针向前走一步
- 如果不同,则两个指针都向前走一步
- 当快指针走完整个数组后,慢指针当前的坐标加1就是数组中不同数字的个数
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/4y1ec.gif)
### 代码实现
```
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int pre = 0, cur = 0, n = nums.size();
while (cur < n) {
if (nums[pre] == nums[cur]){
cur++;
} else{
++pre;
nums[pre] = nums[cur];
cur++;
}
}
return pre + 1;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 图解 | 48. 旋转图像
## 题目描述
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
**说明:**
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。**请不要**使用另一个矩阵来旋转图像。
**示例 1:**
```
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
```
**示例 2:**
```
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
```
## 题目解析
这道题的主要难点在于如何**原地**旋转矩阵。
我们发现,矩阵中的一个元素旋转四次之后会回到原先的位置。也就是说,这四个元素在旋转时位置互相交换了。例如元素 `(i, j)` 对应的四个位置分别是:
+ `(i, j)`
+ `(N-1-j, i)`
+ `(N-1-i, N-1-j)`
+ `(j, N-1-i)`
为了旋转这四个元素,我们可以用一个临时变量保存其中一个元素,然后让几个元素依次赋值。
那么,一共有多少个这样的四元素组呢?这要分情况来看。如果 $n$ 是偶数的话,这相当于把矩阵均分成四块,每块的元素个数是 $ (n/2) \times (n/2)$。如果 $n$ 是奇数,矩阵的中心元素是不随旋转移动的,而剩下的元素均分成四块,每块的元素个数是 $\lfloor n/2 \rfloor \times \lceil n/2 \rceil$。我们对一块中的所有元素做一次四元素旋转即可。
## 动画理解
![](../Animation/Animation.gif)
## 参考代码
```Java
class Solution {
public void rotate(int[][] matrix) {
int N = matrix.length;
for (int i = 0; i < N/2; i++) {
for (int j = 0; j < (N+1)/2; j++) {
int t = matrix[i][j];
matrix[i][j] = matrix[N-1-j][i];
matrix[N-1-j][i] = matrix[N-1-i][N-1-j];
matrix[N-1-i][N-1-j] = matrix[j][N-1-i];
matrix[j][N-1-i] = t;
}
}
}
}
```
## 复杂度分析
+ 时间复杂度:$O(n^2)$。
+ 空间复杂度:$O(1)$。
# LeetCode 第 66 号问题:加一
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.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;
}
}
```
![](../../Pictures/qrcode.jpg)
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