mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-07 15:45:40 +08:00
Update
This commit is contained in:
@ -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) |链表 |简单|**模拟**|
|
||||
|
BIN
pics/486.预测赢家.png
Normal file
BIN
pics/486.预测赢家.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
pics/486.预测赢家1.png
Normal file
BIN
pics/486.预测赢家1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
BIN
pics/486.预测赢家2.png
Normal file
BIN
pics/486.预测赢家2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
BIN
pics/486.预测赢家3.png
Normal file
BIN
pics/486.预测赢家3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
pics/486.预测赢家4.png
Normal file
BIN
pics/486.预测赢家4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
pics/841.钥匙和房间.png
Normal file
BIN
pics/841.钥匙和房间.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
279
problems/0486.预测赢家.md
Normal file
279
problems/0486.预测赢家.md
Normal 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;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -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循环的表达式上做做文章。**
|
||||
|
||||
性能如下:
|
||||
<img src='../pics/541_反转字符串II.png' width=600> </img></div>
|
||||
|
||||
## 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]);
|
||||
}
|
||||
}
|
||||
|
82
problems/0841.钥匙和房间.md
Normal file
82
problems/0841.钥匙和房间.md
Normal 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;
|
||||
}
|
||||
};
|
||||
```
|
Reference in New Issue
Block a user