diff --git a/README.md b/README.md
index d844c3b4..24ef765b 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,8 @@
* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
* [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg)
* [数组:这个循环可以转懵很多人!](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) |树 |中等|**递归**|
|[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**|
+|[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.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) |字符串 |简单| **模拟**|
|[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) |树 |简单|**递归** **迭代**|
|[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.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) |栈 |简单|**栈**|
|[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**|
|[面试题02.07.链表相交](https://github.com/youngyangyang04/leetcode/blob/master/problems/面试题02.07.链表相交.md) |链表 |简单|**模拟**|
diff --git a/pics/486.预测赢家.png b/pics/486.预测赢家.png
new file mode 100644
index 00000000..03ddfaa8
Binary files /dev/null and b/pics/486.预测赢家.png differ
diff --git a/pics/486.预测赢家1.png b/pics/486.预测赢家1.png
new file mode 100644
index 00000000..dbddef26
Binary files /dev/null and b/pics/486.预测赢家1.png differ
diff --git a/pics/486.预测赢家2.png b/pics/486.预测赢家2.png
new file mode 100644
index 00000000..7d26de9a
Binary files /dev/null and b/pics/486.预测赢家2.png differ
diff --git a/pics/486.预测赢家3.png b/pics/486.预测赢家3.png
new file mode 100644
index 00000000..506778c4
Binary files /dev/null and b/pics/486.预测赢家3.png differ
diff --git a/pics/486.预测赢家4.png b/pics/486.预测赢家4.png
new file mode 100644
index 00000000..8a679b74
Binary files /dev/null and b/pics/486.预测赢家4.png differ
diff --git a/pics/841.钥匙和房间.png b/pics/841.钥匙和房间.png
new file mode 100644
index 00000000..3bfdeea4
Binary files /dev/null and b/pics/841.钥匙和房间.png differ
diff --git a/problems/0486.预测赢家.md b/problems/0486.预测赢家.md
new file mode 100644
index 00000000..0cbc0752
--- /dev/null
+++ b/problems/0486.预测赢家.md
@@ -0,0 +1,279 @@
+## 题目地址
+
+## 思路
+
+在做这道题目的时候,最直接的想法,就是计算出player1可能得到的最大分数,然后用数组总和减去player1的得分就是player2的得分,然后两者比较一下就可以了。
+
+那么问题是如何计算player1可能得到的最大分数呢。
+
+## 单独计算玩家得分
+
+以player1选数字的过程,画图如下:
+
+
+
+可以发现是一个递归的过程。
+
+按照递归三部曲来:
+
+1. 确定递归函数的含义,参数以及返回值。
+
+定义函数getScore,就是用来获取玩家1的最大得分。 参数为start 和 end 代表获取[start, end]这个区间的最大值,当然还需要传入nums。
+
+返回值就是玩家1的最大得分。
+
+代码如下:
+
+```
+int getScore(vector& 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依然有选择的权利。
+
+所以代码如下:
+```
+ 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没得选。
+
+
+所以代码如下:
+```
+ 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& 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& 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& 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& 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& 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& nums) {
+ return getScore(nums, 0, nums.size() - 1) >=0 ;
+ }
+};
+```
+
+计算的过程有一些是冗余的,在递归的过程中,可以使用一个memory数组记录一下中间结果,代码如下:
+
+```
+class Solution {
+private:
+int getScore(vector& 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& nums) {
+ int memory[21][21] = {0}; // 记录递归中中间结果
+ return getScore(nums, 0, nums.size() - 1, memory) >= 0 ;
+ }
+};
+```
+
+此时效率已经比较高了
+
+
+那么在看一下动态规划的思路。
+
+
+## 动态规划
+
+
+定义一个二维数组,先明确是用来干什么的,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] 至关重要。
+
+从下图中,可以看出在推导方程的时候一定要从右上角向下推导,而且矩阵左半部分根本不用管!
+
+
+
+按照上图中的规则,不难列出推导公式的循环方式如下:
+
+```
+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& 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;
+ }
+};
+```
+
+
diff --git a/problems/0541.反转字符串II.md b/problems/0541.反转字符串II.md
index f0406b43..ca31a744 100644
--- a/problems/0541.反转字符串II.md
+++ b/problems/0541.反转字符串II.md
@@ -1,20 +1,43 @@
-## 题目地址
+# 题目地址
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循环的表达式上做做文章。**
性能如下:
-## C++代码
+那么这里具体反转的逻辑我们要不要使用库函数呢,其实用不用都可以,使用reverse来实现反转也没毛病,毕竟不是解题关键部分。
-使用C++库里的反转函数reverse
+# C++代码
+
+使用C++库函数reverse的版本如下:
```
class Solution {
@@ -35,13 +58,14 @@ public:
};
```
-自己实现反转函数
+那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)道理是一样的。
+
+下面我实现的reverse函数区间是左闭右闭区间,代码如下:
```
class Solution {
public:
void reverse(string& s, int start, int end) {
- int offset = (end - start + 1) / 2;
- for (int i = start, j = end; i < start + offset; i++, j--) {
+ for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
diff --git a/problems/0841.钥匙和房间.md b/problems/0841.钥匙和房间.md
new file mode 100644
index 00000000..9a9e6a79
--- /dev/null
+++ b/problems/0841.钥匙和房间.md
@@ -0,0 +1,82 @@
+# 题目地址
+https://leetcode-cn.com/problems/keys-and-rooms/
+
+## 思路
+
+其实这道题的本质就是判断各个房间所连成的有向图,是否存在孤岛,如果有孤岛,说明不用访问所有的房间。
+
+如图所示:
+
+
+
+示例1就可以访问所有的房间,因为通过房间里的key将房间连在了一起。
+
+示例2中,就不能访问所有房间,从图中就可以看出,房间2是一个孤岛,我们从0出发,无论怎么遍历,都访问不到房间2。
+
+认清本质问题之后,**就知道孤岛问题,使用 广度优先搜索(BFS) 还是 深度优先搜索(DFS) 都是可以的。**
+
+代码如下:
+
+## BFS C++代码
+
+```
+class Solution {
+bool bfs(const vector>& rooms) {
+ vector visited(rooms.size(), 0); // 标记房间是否被访问过
+ visited[0] = 1; // 0 号房间开始
+ queue que;
+ que.push(0); // 0 号房间开始
+
+ // 广度优先搜索的过程
+ while (!que.empty()) {
+ int key = que.front(); que.pop();
+ vector 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>& rooms) {
+ return bfs(rooms);
+ }
+};
+```
+
+## DFS C++代码
+
+```
+class Solution {
+private:
+ void dfs(int key, const vector>& rooms, vector& visited) {
+ if (visited[key]) {
+ return;
+ }
+ visited[key] = 1;
+ vector keys = rooms[key];
+ for (int key : keys) {
+ // 深度优先搜索遍历
+ dfs(key, rooms, visited);
+ }
+ }
+public:
+ bool canVisitAllRooms(vector>& rooms) {
+ vector visited(rooms.size(), 0);
+ dfs(0, rooms, visited);
+ //检查是否都访问到了
+ for (int i : visited) {
+ if (i == 0) return false;
+ }
+ return true;
+ }
+};
+```