Unverified Commit 3a802a7c authored by chilimyan's avatar chilimyan Committed by GitHub
Browse files

Merge pull request #3 from MisterBooo/master

更新代码
parents 53cc7424 6a20215c
## LeetCode第11号问题:盛水最多的容器
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步个人博客:https://www.zhangxiaoshuai.fun
> 同步个人博客:www.zhangxiaoshuai.fun
**本题选自leetcode的第11题,medium级别,目前通过率:61.3%**
......
# LeetCode 第 22 号问题:生成所有的括号对
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 22 号问题:生成所有的括号对。题目难度为 Medium,目前通过率为 60.9% 。
### 题目描述
给定数字n,要求使用n对()括号生成所有合法的组合情况。
**示例**
```
3
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
```
### 题目解析
#### 解法一: 暴力
暴力的解法应该最容易想到,因为n是确定的,也就是说一共有n个左括号和n个右括号。很容易想到,我们可以对这2n个字符进行排列组合,之后再对所有的组合进行过滤。留下合法的且不重复的即可。
伪代码很容易写:
```python
def brute_force(str, l, r):
if l == n and r == n:
ans.append(str)
if l < n:
brute_force(str+'(', l+1, r)
if r < n:
brute_force(str+')', l, r+1)
```
写完了再根据结果判断是否合法,留下合法的所有情况即可。
这样编码的确不难,而且也很容易想到,但是计算n个字符的排列组合复杂度是 $O(2^n)$ 是一个指数级的算法,复杂度是我们不能接受的。而且根据上一题当中的结论,在匹配括号的时候是可以取巧的,我们其实没必要把所有的情况都枚举到。因为想要括号匹配合法,必须有一条,对于字符串当中的任何一个位置i,都必须有:前i个字符中所有左括号的数量大于等于右括号的数量。
,否则就是非法的。
也就是说必须要保证任意一个位置右括号的数量小于等于左括号的数量,不然的话,多余的右括号永远也无法匹配。
#### 解法二: 回溯
既然左括号的数量必须大于右括号的数量,我们完全可以据此进行优化。我们在递归的时候对l和r进行大小判断,保证所有时刻都有l >= r即可。
代码:
```C++
class Solution {
public:
void dfs(int n, int l, int r, string str, vector<string>& vt) {
if (l+r == 2 *n) {
vt.push_back(str);
return ;
}
if (l < n) dfs(n, l+1, r, str+"(", vt);
if (r < l) dfs(n, l, r+1, str+")", vt);
}
vector<string> generateParenthesis(int n) {
vector<string> vt;
dfs(n, 0, 0, "", vt);
return vt;
}
};
```
#### 解法三: 构造
这个方法是我原创的,官方的题解当中没有收录。
我们直接求解n的答案的时候是比较困难的,这个时候我们可以把问题拆解,大问题变成小问题,通过小问题的答案构造大问题的答案。上述的两种方法本质上也是一样的思路,不过递归替我们做了问题的拆分。
实际上我们可以自己拆分问题,n的时候我们一下子不清楚答案。我们可以先从简单的观察一下结果:比如当n==1的时候,答案就是”()”。n==2有两种:”()()”, “(())”。n==3的时候是5种:”((()))”, “()(())”, “()()()”, “(()())”, “(())()”。
细心的读者已经可以总结出规律了,其实并不难想到。
solution(n) = solution(i) + solution(n-i) + '(' solution(n-1) ')'
解释一下这个公式,这里的solution(n)表示n的所有答案串,也就是说n个括号的答案串是可以通过小于n的答案串进行组合的。比如n=1时答案是(),n=2则有两种,一种是用两个n=1拼接,第二种是在n=1的答案外层加上一个括号:
solution(2) = [()(), (())]
我们再来看solution(3),它可以用n=1和n=2拼接,以及通过n=2外层加上单独的括号得到所有答案:
solution(3) = [()()(), ()(()), (())(), (()()), ((()))]
前面3种是通过solution(2)和solution(1)拼接得到的,后面两种则是在solution(2)外面直接加上括号得到的,这种情况是无法通过拼接得到的情况。我们把这些情况全部汇总,然后去除掉重复的情况就是答案了。
这样我们就用小于n的所有结果构造出了n的结果,由于n=0 和n=1的情况是已知的。我们只需要把中间结果存储下来,通过递推就可以获取所有的答案。
### 动画描述
![](../Animation/0022-Generate_Parentheses.gif)
### 代码实现
```C++
class Solution {
public:
vector<string> generateParenthesis(int n) {
// 存储所有中间结果
map<int, set<string>> mp;
set<string> st;
st.insert("()");
mp[1] = st;
for (int i = 2; i <= n; i++) {
// 使用set来去重
set<string> cur;
for (int j = 1; j <= i-1; j++) {
// 取出所有solution(j)和solution(i-j)
set<string> vj = mp[j];
set<string> vk = mp[i-j];
for (string str:vj) {
for (string stj : vk) {
cur.insert(str + stj);
}
}
}
// solution(i-1)最外层套上括号
set<string> vj = mp[i-1];
for (string str : vj) {
cur.insert("(" + str + ")");
}
// 得到solution(i)
mp[i] = cur;
}
vector<string> vt;
st = mp[n];
for (string str : st) vt.push_back(str);
return vt;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 24 号问题:两两交换链表中的节点
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 24 号问题:两两交换链表中的节点。
### 题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
**你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。
**示例:**
```
给定 1->2->3->4, 你应该返回 2->1->4->3.
```
### 题目解析 - 迭代法
由题目描述可知需要两两交换, 那么以两个为一组子链表交换指针即可, 在设置一个 **哨兵** 指向交换后的子链表 (或者哨兵提前指向子链表的第二个节点,因为第二个节点交换后就成了第一个节点); 然后让哨兵指向下一组子链表,继续交换,直至最后.
**哨兵** 为 节点 `prev`, 子链表第一个节点为 `A`, 第二个节点为 `B`, 第三个节点为 `C`, 那么操作流程如下:
- 终止条件 `head == null && head -> next == null`
1. prev -> B ( A -> B -> C )
2. A - > C
3. B -> A ( prev -> B -> A -> C )
4. prev -> A
5. head -> C
6. 循环以上步骤
### 动画描述
<img src="../Animation/Animation1.gif" alt="Animation1" style="zoom:150%;" />
### 代码实现
```javascript
/**
* JavaScript描述
* 迭代法
*/
var swapPairs = function(head) {
let dummy = new ListNode(0);
dummy.next = head;
let prevNode = dummy;
while (head !== null && head.next !== null) {
// Nodes to be swapped
let firstNode = head,
secondNode = head.next;
// Swapping
prevNode.next = secondNode; // 放到交换前后都可以
firstNode.next = secondNode.next;
secondNode.next = firstNode;
// Reinitializing the head and prevNode for next swap
prevNode = firstNode;
head = firstNode.next;
}
return dummy.next;
};
```
### 复杂度分析
- 时间复杂度:**O(N)**,其中 *N* 指的是链表的节点数量
- 空间复杂度:**O(1)**
### 题目解析 - 递归
递归的思路和迭代类似, 都是分组交换. 具体来说这里的递归不是针对一个问题深入进去,而是不断向后推进.
- 每次递归只交换一对节点
- 下一次递归则是交换下一对节点
- 交换完成后返回第二个节点, 因为它是交换后的子链表新头
- 递归完成后返回第一次递归的第二个节点, 这就是新链表的头结点
**注意:** 不要人肉递归, 更多关注整体逻辑
示例执行大致流程为:
- 终止条件: `(head == null) || (head.next == null)`
1. 1 -> 2 -> 3 -> 4 ( 原始链表 )
2. 1 -> 3 -> 4
3. ( 2 -> 1 ) -> 3 -> 4 ( 第一次递归完成后返回原来的第二个节点, 也就是值为 2 的节点 )
4. 2 -> 1 -> 3 -> null
5. 2 -> 1 -> ( 4 -> 3 ) ( 第二次递归完成后返回原来的第二个节点, 也就是值为 4 的节点 )
### 动画描述
<img src="../Animation/Animation2.gif" alt="Animation2" style="zoom:150%;" />
### 代码实现
```javascript
/**
* JavaScript描述
* 递归法
*/
var swapPairs = function(head) {
if (head == null || head.next == null) {
return head;
}
// Nodes to be swapped
let firstNode = head,
secondNode = head.next;
// Swapping
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;
return secondNode;
};
```
### 复杂度分析
- 时间复杂度:**O(N)**,其中 *N* 指的是链表的节点数量
- 空间复杂度:**O(N)**, 递归过程使用的堆栈空间
![](../../Pictures/qrcode.jpg)
# LeetCode 第 34 号问题:在排序数组中查找元素的第一个和最后一个位置
题目来源于 LeetCode 上第 34 号问题:find-first-and-last-position-of-element-in-sorted-array。题目难度为 中等。
### 题目描述
给定一个按照升序排列的整数数组 **nums**,和一个目标值 **target**。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 **O(log n)** 级别。
如果数组中不存在目标值,返回 [-1, -1]。
**示例:**
```
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
```
```
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
```
### 题目解析
题目中要求了时间复杂度为O(log n),这就很清楚要使用二分查找法了。
首先定义两个指针变量,分别存储左右两个位置的索引。首先去找目标值的最左面的索引,通过循环为了防止元素丢失,每次保留最右面的元素,左侧的指针移动时+1。在循环结束的时候判断一下数组中是否包括目标值,不包括的话直接退出。
右面的跟左侧相同,只不过正好相反。
### 动画描述
![](..\Animation\在排序数组中查找元素的第一个和最后一个位置.gif)
### 代码实现
```java
// 34. 下一个排列
// https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = new int[] { -1, -1 };
int left = 0;
int right = nums.length - 1;
int l = left;
int r = right;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
if (left>right||nums[left]!=target) {
return new int[]{-1,-1};
}
while (l < r) {
int mid = (l + r) / 2 + 1;
if (nums[mid] > target) {
r = mid - 1;
} else {
l = mid;
}
}
if (left > right || left > r) {
return new int[] { -1, -1 };
} else {
return new int[] { left, r };
}
}
}
```
![](../../Pictures/qrcode.jpg)
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = new int[] { -1, -1 };
int left = 0;
int right = nums.length - 1;
int l = left;
int r = right;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
if (left>right||nums[left]!=target) {
return new int[]{-1,-1};
}
while (l < r) {
int mid = (l + r) / 2 + 1;
if (nums[mid] > target) {
r = mid - 1;
} else {
l = mid;
}
}
if (left > right || left > r) {
return new int[] { -1, -1 };
} else {
return new int[] { left, r };
}
}
}
\ No newline at end of file
# LeetCode 第 35 号问题:搜索插入位置
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 第 35 号问题:搜索插入位置.
## 题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
```
输入: [1,3,5,6], 5
输出: 2
```
示例 2:
```
输入: [1,3,5,6], 2
输出: 1
```
示例 3:
```
输入: [1,3,5,6], 7
输出: 4
```
示例 4:
```
输入: [1,3,5,6], 0
输出: 0
```
## 思路解析
### 暴力循环法
这个题看起来就是很简单的,就是一道考验查找算法的题目。最简单的就是暴力查找了。
#### 思路
遍历这个数组,然后如果当前值和目标值target一致或小于目标值target,那么就return 当前下标。这种解法的时间复杂度是O(N)
### 动画理解
![](../Animation/暴力查找.gif)
#### 代码实现
```java
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public int searchInsert(int[] nums, int target) {
int i=0;
for(;i<nums.length;i++){
if (nums[i]>=target){
break;
}
}
return i;
}
}
```
### 二分法
#### 思路
除了暴力法,我们在排序数组中查找值还可以用的一种方法是二分法,思路还是和改良的暴力循环法一样,先找到左右边界,然后计算,每次可以省出一半的时间。时间复杂度为O(logn)
#### 代码实现
```java
//时间复杂度:O(lon(n))
//空间复杂度:O(1)
class Solution {
public int searchInsert(int[] nums, int target) {
if (target>nums[nums.length-1]) {
return nums.length;
}
int left=0;
int right=nums.length-1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
class Solution1 {
public int searchInsert(int[] nums, int target) {
int i=0;
for(;i<nums.length;i++){
if (nums[i]>=target){
break;
}
}
return i;
}
}
\ No newline at end of file
//时间复杂度:O(lon(n))
//空间复杂度:O(1)
class Solution2 {
public int searchInsert(int[] nums, int target) {
if (target>nums[nums.length-1]) {
return nums.length;
}
int left=0;
int right=nums.length-1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
\ 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