Commit fb2465e2 authored by 程序员吴师兄's avatar 程序员吴师兄
Browse files

整理部分文件

parent 1a2d7393
# 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 第 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 第 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)
# LeetCode 第 75 号问题:颜色分类
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.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++;
}
}
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 86 号问题:分割链表
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.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;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 92 号问题:反转链表 II
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 92 号问题:反转链表 II。题目难度为 Medium,目前通过率为 43.8% 。
### 题目描述
反转从位置 *m**n* 的链表。请使用一趟扫描完成反转。
**说明:**
1 ≤ *m**n* ≤ 链表长度。
**示例:**
```
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
```
### 题目解析
**[Reverse Linked List](https://xiaozhuanlan.com/topic/7513064892)**的延伸题。
可以考虑取出需要反转的这一小段链表,反转完后再插入到原先的链表中。
**以本题为例:**
变换的是 2,3,4这三个点,那么我们可以先取出 2 ,用 front 指针指向 2 ,然后当取出 3 的时候,我们把 3 加到 2 的前面,把 front 指针前移到 3 ,依次类推,到 4 后停止,这样我们得到一个新链表 4 -> 3 -> 2 , front 指针指向4。
对于原链表来说,**有两个点的位置很重要**,需要用指针记录下来,分别是 1 和 5 ,把新链表插入的时候需要这两个点的位置。
- 用 pre 指针记录 1 的位置
- 当 4 结点被取走后,5 的位置需要记下来
- 这样我们就可以把倒置后的那一小段链表加入到原链表中
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/rjjr0.gif)
### 代码实现
```
class Solution {
public:
ListNode *reverseBetween(ListNode *head, int m, int n) {
ListNode *dummy = new ListNode(-1);
dummy->next = head;
ListNode *cur = dummy;
ListNode *pre, *front, *last;
for (int i = 1; i <= m - 1; ++i) cur = cur->next;
pre = cur;
last = cur->next;
for (int i = m; i <= n; ++i) {
cur = pre->next;
pre->next = cur->next;
cur->next = front;
front = cur;
}
cur = pre->next;
pre->next = front;
last->next = cur;
return dummy->next;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 94 号问题:二叉树的中序遍历
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 94 号问题:二叉树的中序遍历。题目难度为 Medium,目前通过率为 35.8% 。
### 题目描述
给定一个二叉树,返回它的*中序* 遍历。
**示例:**
```
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
```
**进阶:** 递归算法很简单,你可以通过迭代算法完成吗?
### 题目解析
**栈(Stack)**的思路来处理问题。
中序遍历的顺序为**左-根-右**,具体算法为:
- 从根节点开始,先将根节点压入栈
- 然后再将其所有左子结点压入栈,取出栈顶节点,保存节点值
- 再将当前指针移到其右子节点上,若存在右子节点,则在下次循环时又可将其所有左子结点压入栈中
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/v17b8.gif)
### 代码实现
```
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 101 号问题:对称二叉树
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 第 101 号问题:对称二叉树。
### 题目描述
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 `[1,2,2,3,4,4,3]` 是对称的。
```
1
/ \
2 2
/ \ / \
3 4 4 3
```
### 题目解析
用递归做比较简单:一棵树是对称的**等价**于它的左子树和右子树两棵树是对称的,问题就转变为判断两棵树是否对称。
### 代码实现
```java
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
//把问题变成判断两棵树是否是对称的
return isSym(root.left, root.right);
}
//判断的是根节点为r1和r2的两棵树是否是对称的
public boolean isSym(TreeNode r1, TreeNode r2){
if(r1 == null && r2 == null) return true;
if(r1 == null || r2 == null) return false;
//这两棵树是对称需要满足的条件:
//1.俩根节点相等。 2.树1的左子树和树2的右子树,树2的左子树和树1的右子树都得是对称的
return r1.val == r2.val && isSym(r1.left, r2.right)
&& isSym(r1.right, r2.left);
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 102 号问题:二叉树的层序遍历
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 102 号问题:二叉树的层序遍历。题目难度为 Medium,目前通过率为 55.8% 。
### 题目描述
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: `[3,9,20,null,null,15,7]`,
```
3
/ \
9 20
/ \
15 7
```
返回其层次遍历结果:
```
[
[3],
[9,20],
[15,7]
]
```
### 题目解析
该问题需要用到**队列**
- 建立一个queue
- 先把根节点放进去,这时候找根节点的左右两个子节点
- 去掉根节点,此时queue里的元素就是下一层的所有节点
- 用for循环遍历,将结果存到一个一维向量里
- 遍历完之后再把这个一维向量存到二维向量里
- 以此类推,可以完成层序遍历
### 动画描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/2elr5.gif)
### 代码实现
```
/// BFS
/// Time Complexity: O(n), where n is the number of nodes in the tree
/// Space Complexity: O(n)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root == NULL)
return res;
queue<pair<TreeNode*,int>> q;
q.push(make_pair(root, 0));
while(!q.empty()){
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if(level == res.size())
res.push_back(vector<int>());
assert( level < res.size() );
res[level].push_back(node->val);
if(node->left)
q.push(make_pair(node->left, level + 1 ));
if(node->right)
q.push(make_pair(node->right, level + 1 ));
}
return res;
}
};
```
![](../../Pictures/qrcode.jpg)
\ 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