This commit is contained in:
youngyangyang04
2020-09-01 15:22:06 +08:00
parent c7eaa2148a
commit c5c9be763c
10 changed files with 398 additions and 9 deletions

View File

@ -44,6 +44,8 @@
* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
* [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg) * [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg)
* [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg) * [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)
* [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg)
* [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
* 精选链表相关的面试题 * 精选链表相关的面试题
* 精选字符串相关的面试题 * 精选字符串相关的面试题
* 精选栈与队列相关的面试题 * 精选栈与队列相关的面试题
@ -428,6 +430,7 @@ int countNodes(TreeNode* root) {
|[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**| |[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**|
|[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| |[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**|
|[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| |[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**|
|[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**|
|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**| |[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**|
|[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**| |[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**|
|[0575.分糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0575.分糖果.md) |哈希表 |简单|**哈希**| |[0575.分糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0575.分糖果.md) |哈希表 |简单|**哈希**|
@ -437,6 +440,7 @@ int countNodes(TreeNode* root) {
|[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |树 |简单|**递归** **迭代**| |[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |树 |简单|**递归** **迭代**|
|[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**| |[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**|
|[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**| |[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**|
|[0841.钥匙和房间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0841.钥匙和房间.md) |孤岛问题 |中等|**bfs** **dfs**|
|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |栈 |简单|**栈**| |[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |栈 |简单|**栈**|
|[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**| |[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**|
|[面试题02.07.链表相交](https://github.com/youngyangyang04/leetcode/blob/master/problems/面试题02.07.链表相交.md) |链表 |简单|**模拟**| |[面试题02.07.链表相交](https://github.com/youngyangyang04/leetcode/blob/master/problems/面试题02.07.链表相交.md) |链表 |简单|**模拟**|

BIN
pics/486.预测赢家.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
pics/486.预测赢家1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
pics/486.预测赢家2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
pics/486.预测赢家3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
pics/486.预测赢家4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,279 @@
## 题目地址
## 思路
在做这道题目的时候最直接的想法就是计算出player1可能得到的最大分数然后用数组总和减去player1的得分就是player2的得分然后两者比较一下就可以了。
那么问题是如何计算player1可能得到的最大分数呢。
## 单独计算玩家得分
以player1选数字的过程画图如下
<img src='../pics/486.预测赢家2.png' width=600> </img></div>
可以发现是一个递归的过程。
按照递归三部曲来:
1. 确定递归函数的含义,参数以及返回值。
定义函数getScore就是用来获取玩家1的最大得分。 参数为start 和 end 代表获取[start, end]这个区间的最大值当然还需要传入nums。
返回值就是玩家1的最大得分。
代码如下:
```
int getScore(vector<int>& nums, int start, int end) {
```
2. 确定终止条件
当start == end的时候玩家A的得分就是nums[start],代码如下:
```
if (start == end) {
return nums[start];
}
```
3. 确定单层递归逻辑
玩家1的得分等于集合左元素的数值+ 玩家2选择后集合的最小值因为玩家2也是最聪明的
而且剩余集合中的元素数量为2或者大于2的处理逻辑是不一样的
如图当集合中的元素数量大于2那么玩家1先选玩家2依然有选择的权利。
<img src='../pics/486.预测赢家3.png' width=600> </img></div>
所以代码如下:
```
if ((end - start) >= 2) {
selectLeft = nums[start] + min(getScore(nums, start + 2, end), getScore(nums, start + 1, end - 1));
selectRight = nums[end] + min(getScore(nums, start + 1, end - 1), getScore(nums, start, end - 2));
}
```
如图当集合中的元素数量等于2那么玩家1先选玩家2没得选。
<img src='../pics/486.预测赢家4.png' width=600> </img></div>
所以代码如下:
```
if ((end - start) == 1) {
selectLeft = nums[start];
selectRight = nums[end];
}
```
单层递归逻辑代码如下:
```
int selectLeft, selectRight;
if ((end - start) >= 2) {
selectLeft = nums[start] + min(getScore(nums, start + 2, end), getScore(nums, start + 1, end - 1));
selectRight = nums[end] + min(getScore(nums, start + 1, end - 1), getScore(nums, start, end - 2));
}
if ((end - start) == 1) {
selectLeft = nums[start];
selectRight = nums[end];
}
return max(selectLeft, selectRight);
```
这些可以写出这道题目整体代码如下:
```
class Solution {
private:
int getScore(vector<int>& nums, int start, int end) {
if (start == end) {
return nums[start];
}
int selectLeft, selectRight;
if ((end - start) >= 2) {
selectLeft = nums[start] + min(getScore(nums, start + 2, end), getScore(nums, start + 1, end - 1));
selectRight = nums[end] + min(getScore(nums, start + 1, end - 1), getScore(nums, start, end - 2));
}
if ((end - start) == 1) {
selectLeft = nums[start];
selectRight = nums[end];
}
return max(selectLeft, selectRight);
}
public:
bool PredictTheWinner(vector<int>& nums) {
int sum = 0;
for (int i : nums) {
sum += i;
}
int player1 = getScore(nums, 0, nums.size() - 1);
int player2 = sum - player1;
return player1 >= player2;
}
};
```
可以有一个优化,就是把重复计算的数值提取出来,如下:
```
class Solution {
private:
int getScore(vector<int>& nums, int start, int end) {
int selectLeft, selectRight;
int gap = end - start;
if (gap == 0) {
return nums[start];
} else if (gap == 1) { // 此时直接取左右的值就可以
selectLeft = nums[start];
selectRight = nums[end];
} else if (gap >= 2) { // 如果gap大于2递归计算selectLeft和selectRight
// 计算的过程为什么用min因为要按照对手也是最聪明的来计算。
int num = getScore(nums, start + 1, end - 1);
selectLeft = nums[start] +
min(getScore(nums, start + 2, end), num);
selectRight = nums[end] +
min(num, getScore(nums, start, end - 2));
}
return max(selectLeft, selectRight);
}
public:
bool PredictTheWinner(vector<int>& nums) {
int sum = 0;
for (int i : nums) {
sum += i;
}
int player1 = getScore(nums, 0, nums.size() - 1);
int player2 = sum - player1;
// 如果最终两个玩家的分数相等,那么玩家 1 仍为赢家,所以是大于等于。
return player1 >= player2;
}
};
```
## 计算两个玩家的差值
以上是单独计算出两个选手的得分,逻辑上直观,但是代码确实比较冗余。
因为就我们要求的结果其实就是两个选手的胜负,那么不用两个选手的得分,而是把问题转换为两个选手所拿元素的差值。
代码如下:
```
class Solution {
private:
int getScore(vector<int>& nums, int start, int end) {
if (end == start) {
return nums[start];
}
int selectLeft = nums[start] - getScore(nums, start + 1, end);
int selectRight = nums[end] - getScore(nums, start, end - 1);
return max(selectLeft, selectRight);
}
public:
bool PredictTheWinner(vector<int>& nums) {
return getScore(nums, 0, nums.size() - 1) >=0 ;
}
};
```
计算的过程有一些是冗余的在递归的过程中可以使用一个memory数组记录一下中间结果代码如下
```
class Solution {
private:
int getScore(vector<int>& nums, int start, int end, int memory[21][21]) {
if (end == start) {
return nums[start];
}
if (memory[start][end]) return memory[start][end];
int selectLeft = nums[start] - getScore(nums, start + 1, end, memory);
int selectRight = nums[end] - getScore(nums, start, end - 1, memory);
memory[start][end] = max(selectLeft, selectRight);
return memory[start][end];
}
public:
bool PredictTheWinner(vector<int>& nums) {
int memory[21][21] = {0}; // 记录递归中中间结果
return getScore(nums, 0, nums.size() - 1, memory) >= 0 ;
}
};
```
此时效率已经比较高了
<img src='../pics/486.预测赢家.png' width=600> </img></div>
那么在看一下动态规划的思路。
## 动态规划
定义一个二维数组先明确是用来干什么的dp[i][j] 表示两个玩家在数组 i 到 j 区间内游戏能赢对方的差值i <= j
假如玩家1先取左端 nums[i]那么玩家2能赢对方的差值是dp[i+1][j] 如果玩家1先取取右端 nums[j]玩家2能赢对方的差值就是dp[i][j-1]
那么 不难理解如下公式:
`dp[i][j] = max((nums[i] - dp[i + 1][j]), (nums[j] - dp[i][j - 1])); `
确定了状态转移公式之后,就要想想如何遍历。
一些同学确定的方程,却不知道该如何遍历这个遍历推算出方程的结果,我们来看一下。
首先要给dp[i][j]进行初始化首先当i == j的时候nums[i]就是dp[i][j]的值。
代码如下:
```
// 当i == j的时候nums[i]就是dp[i][j]
for (int i = 0; i < nums.size(); i++) {
dp[i][i] = nums[i];
}
```
接下来就要推导公式了首先要知道最终求是dp[0][nums.size() - 1]是否大于等于0也就是求dp[0][nums.size() - 1] 至关重要。
从下图中,可以看出在推导方程的时候一定要从右上角向下推导,而且矩阵左半部分根本不用管!
<img src='../pics/486.预测赢家1.png' width=600> </img></div>
按照上图中的规则,不难列出推导公式的循环方式如下:
```
for(int i = nums.size() - 2; i >= 0; i--) {
for (int j = i + 1; j < nums.size(); j++) {
dp[i][j] = max((nums[i] - dp[i + 1][j]), (nums[j] - dp[i][j - 1]));
}
}
```
最后整体动态规划的代码:
##
```
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
// dp[i][j] 表示两个玩家在数组 i 到 j 区间内游戏能赢对方的差值i <= j
int dp[22][22] = {0};
// 当i == j的时候nums[i]就是dp[i][j]
for (int i = 0; i < nums.size(); i++) {
dp[i][i] = nums[i];
}
for(int i = nums.size() - 2; i >= 0; i--) {
for (int j = i + 1; j < nums.size(); j++) {
dp[i][j] = max((nums[i] - dp[i + 1][j]), (nums[j] - dp[i][j - 1]));
}
}
return dp[0][nums.size() - 1] >= 0;
}
};
```

View File

@ -1,20 +1,43 @@
## 题目地址 # 题目地址
https://leetcode-cn.com/problems/reverse-string-ii/ https://leetcode-cn.com/problems/reverse-string-ii/
## 思路 > 简单的反转还不够,我要花式反转
先做0344.反转字符串,在做这道题目更好一些 # 题目541. 反转字符串II
for循环中i 每次移动 2 * k然后判断是否需要有反转的区间 给定一个字符串 s 和一个整数 k你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例:
输入: s = "abcdefg", k = 2
输出: "bacdfeg"
# 思路
这道题目其实也是模拟,实现题目中规定的反转规则就可以了。
一些同学可能为了处理逻辑每隔2k个字符的前k的字符写了一堆逻辑代码或者再搞一个计数器来统计2k再统计前k个字符。
其实在遍历字符串的过程中,只要让 i += (2 * k)i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。
**所以当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。**
性能如下: 性能如下:
<img src='../pics/541_反转字符串II.png' width=600> </img></div> <img src='../pics/541_反转字符串II.png' width=600> </img></div>
## C++代码 那么这里具体反转的逻辑我们要不要使用库函数呢其实用不用都可以使用reverse来实现反转也没毛病毕竟不是解题关键部分。
使用C++库里的反转函数reverse # C++代码
使用C++库函数reverse的版本如下
``` ```
class Solution { class Solution {
@ -35,13 +58,14 @@ public:
}; };
``` ```
自己实现反转函数 那么我们也可以实现自己的reverse函数其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)道理是一样的。
下面我实现的reverse函数区间是左闭右闭区间代码如下
``` ```
class Solution { class Solution {
public: public:
void reverse(string& s, int start, int end) { void reverse(string& s, int start, int end) {
int offset = (end - start + 1) / 2; for (int i = start, j = end; i < j; i++, j--) {
for (int i = start, j = end; i < start + offset; i++, j--) {
swap(s[i], s[j]); swap(s[i], s[j]);
} }
} }

View File

@ -0,0 +1,82 @@
# 题目地址
https://leetcode-cn.com/problems/keys-and-rooms/
## 思路
其实这道题的本质就是判断各个房间所连成的有向图,是否存在孤岛,如果有孤岛,说明不用访问所有的房间。
如图所示:
<img src='../pics/841.钥匙和房间.png' width=600> </img></div>
示例1就可以访问所有的房间因为通过房间里的key将房间连在了一起。
示例2中就不能访问所有房间从图中就可以看出房间2是一个孤岛我们从0出发无论怎么遍历都访问不到房间2。
认清本质问题之后,**就知道孤岛问题,使用 广度优先搜索(BFS) 还是 深度优先搜索(DFS) 都是可以的。**
代码如下:
## BFS C++代码
```
class Solution {
bool bfs(const vector<vector<int>>& rooms) {
vector<int> visited(rooms.size(), 0); // 标记房间是否被访问过
visited[0] = 1; // 0 号房间开始
queue<int> que;
que.push(0); // 0 号房间开始
// 广度优先搜索的过程
while (!que.empty()) {
int key = que.front(); que.pop();
vector<int> keys = rooms[key];
for (int key : keys) {
if (!visited[key]) {
que.push(key);
visited[key] = 1;
}
}
}
// 检查房间是不是都遍历过了
for (int i : visited) {
if (i == 0) return false;
}
return true;
}
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
return bfs(rooms);
}
};
```
## DFS C++代码
```
class Solution {
private:
void dfs(int key, const vector<vector<int>>& rooms, vector<int>& visited) {
if (visited[key]) {
return;
}
visited[key] = 1;
vector<int> keys = rooms[key];
for (int key : keys) {
// 深度优先搜索遍历
dfs(key, rooms, visited);
}
}
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
vector<int> visited(rooms.size(), 0);
dfs(0, rooms, visited);
//检查是否都访问到了
for (int i : visited) {
if (i == 0) return false;
}
return true;
}
};
```