C++日常刷题积累
- 今日刷题汇总 - day005
- 1、游游的you
- 1.1、题目
- 1.2、思路
- 1.3、程序实现 - 蛮力法
- 1.4、程序实现 - 贪心(优化)
- 2、腐烂的苹果
- 2.1、题目
- 2.2、思路
- 2.3、程序实现 - bfs
- 3、孩子们的游戏(圆圈中最后剩下的数)
- 3.1、题目
- 3.2、思路
- 3.3、程序实现 -- 环形链表
- 3.4、程序实现 -- 约瑟夫环(动态规划法)
- 4、题目链接
今日刷题汇总 - day005
1、游游的you
1.1、题目
1.2、思路
读完题知道,属于是贪心加模拟类的题型,那么肯定要满足you次数最多,再拼接oo满足最大分数的思想。其次,蛮力法我也写了,不过超出题目限制的空间范围了,思路就是按照题目模拟即可直接贴出来就不多说了。接下来,结合预处理方式,实现贪心的思路,既然我们需要you最大化,那么贪心的思想就是求you的最大次数,观察示例可知,you的次数是同时消耗a,b,c的次数,所以you的最大次数等于a,b,c中的最小值,其次,值得注意的是oo是1分,而ooo是2分,oooo是3分,可以推出oo的最大分数为b-1次数。然后,我们会先拼接you,会同时消耗b,那么oo最大分数就变成了b减去拼接you用掉o的次数,再-1即可。所以,最后的最大分数就等于you + oo的分数。接下来,具体看程序实现。
1.3、程序实现 - 蛮力法
就是模拟实现,过程略。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> y;
stack<int> o;
stack<int> u;
int q = 0;
int a = 0;
int b = 0;
int c = 0;
cin >> q;
while (q--) {
cin >> a >> b >> c;
while (a) {
y.push(a--);
}
while (b) {
o.push(b--);
}
while (c) {
u.push(c--);
}
int you = 0;
while (!y.empty() && !o.empty() && !u.empty()) {
y.pop();
o.pop();
u.pop();
you += 2;
}
while (!y.empty()) {
y.pop();
}
int oo = 0;
int m = 0;
int flag = 0;
while (!o.empty()) {
flag = 1;
o.pop();
m++;
}
if (flag) oo = m - 1;
else oo = m;
while (!u.empty()) {
u.pop();
}
cout << (you + oo) << endl;
}
return 0;
}
1.4、程序实现 - 贪心(优化)
首先,按照题目要求,编写输入q表示访问次数,也就是a,b,c的输入次数,那么采用预处理方法,输入一次处理一次。
#include <iostream>
using namespace std;
int main()
{
int q = 0;
cin >> q;
int a,b,c;
while(q--)
{
}
return 0;
}
接着,处理a,b,c和you与oo的次数与分数的关系。定义变量you统计当前预处理的a,b,c中you的分数,通过上述思路分析思考得知,you的最大分数就等于a,b,c的最小次数乘以2(2表示的是它的分之),即:you最大分数 = 次数 X 分值即可。同理,思路中分析得知,oo具有b-1的性质,且you会消耗o的次数即b的次数,即:oo的最大分数,定义变量ooo = b - 1 - (you/2)即可。最后,输出每一次预处理的结果you + ooo即可。
#include <iostream>
using namespace std;
int main()
{
int q = 0;
cin >> q;
int a,b,c;
while(q--)
{
cin >> a >> b >> c;
int you = min(a,min(b,c)) * 2;
int ooo = max(b-(you/2)-1,0);
cout << (you + ooo) << endl;
}
return 0;
}
2、腐烂的苹果
2.1、题目
2.2、思路
读完题,第一反应就是之前写过的dfs算法思路,再次理解题目要求,0,1,2分别表示空格、好苹果、坏苹果且是随机的(多个2,多个0,多个1),然后一样四个方向每分钟(理解为一次操作)进行传播(可以理解为搜索),然后求多少次传播操作后没有好苹果,或遍历完后传播不到的好苹果的情况返回-1即可。然后,再根据示例分析,这里适用dfs是不适用的,属于是一圈一圈的传播,而不是一个一个的,那么这里需要用到多源bfs思路。也就是需要满足同时一次操作对于多个坏苹果2进行它周围传播的情况,那么我们可以把同一次操作中所有的2坏苹果放进一个队列里,就可以实现每一次出队列就可同时操作多个2坏苹果,其次,每一次操作就是一次传播所以用一个变量count记录坏苹果传播的次数,但是注意的是外层传播时注意边界控制。然后,传播到空格0,不管,传播到1好苹果则,将1置为2或者标记为已传播(我这里才用标记),然后继续把这个被传播的好苹果标记后,进入坏苹果队列,继续排序传播即可。遍历传播结束后,最后再遍历二维数组判断是否还有1好苹果,则返回-1即可,否则输出count传播次数。
由于不好理解,我简单画个图,便于理解:
接下来,就是程序实现。
2.3、程序实现 - bfs
首先,按照采用bfs思路的需求,编写需要的方向数组dx和dy,定义m,n计算二维数组的大小方便执行遍历(访问)操作,其次,这里采用布尔类型数组vis标记法表示是否已传播,然后定义坏苹果队列badapple,这里pair<int,int>因为需要的下标所以是用pair存放坏苹果的行和列即可。然后,遍历二维数组将坏苹果进队列。
class Solution
{
int m,n;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
bool vis[1001][1001] = {false};
queue<pair<int,int>> badapple;
public:
int rotApple(vector<vector<int> >& grid)
{
n = grid.size();
m = grid[0].size();
for(int i = 0;i < n;i++)
{
for(int j = 0;j<m;j++)
{
if(grid[i][j] == 2)
badapple.push({i,j});
}
}
};
接着,创建count计数传播次数,因为是相邻之间的传播所以可以利用传播次数就是输出结果。
然后,只要队列中有坏苹果则执行传播,即出一层坏苹果且用sz保存当前这一层有几个坏苹果,以便依次执行上下左右的传播操作,即有多少个坏苹果就执行多少次传播,实现模拟同时一层坏苹果的传播。然后if(x >=0 && x < n && y >=0 && y < m && grid[x][y] == 1 && !vis[x][y]),进行边界控制防止出界且为好苹果且未被标记,则将它标记为已传播,再将它进队列,成为理解的下一层将要传播得坏苹果之一,等待sz次后,完成所有得第二层坏苹果,依次类推,直到传播结束。
class Solution
{
int m,n;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
bool vis[1001][1001] = {false};
queue<pair<int,int>> badapple;
public:
int rotApple(vector<vector<int> >& grid)
{
n = grid.size();
m = grid[0].size();
for(int i = 0;i < n;i++)
{
for(int j = 0;j<m;j++)
{
if(grid[i][j] == 2)
badapple.push({i,j});
}
}
int count = 0;
while(badapple.size())
{
count++;
int sz = badapple.size();
while(sz--)
{
auto [a,b] = badapple.front();
badapple.pop();
for(int k = 0; k < 4;k++)
{
int x = a + dx[k];
int y = b + dy[k];
if(x >=0 && x < n && y >=0 && y < m && grid[x][y] == 1 && !vis[x][y])
{
vis[x][y] = true;
badapple.push({x,y});
}
}
}
}
}
};
传播结束有两种情况,要么当前层的所有坏苹果的下一次传播单元(数组下标)都是空格0,要么就是全部坏苹果被传播完毕(遍历完)。最后,只需要再次遍历这个二维数组判断是都还有未被感染的好苹果且为被标记,有则返回-1,否则就是输出count-1即可。
class Solution
{
int m,n;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
bool vis[1001][1001] = {false};
queue<pair<int,int>> badapple;
public:
int rotApple(vector<vector<int> >& grid)
{
n = grid.size();
m = grid[0].size();
for(int i = 0;i < n;i++)
{
for(int j = 0;j<m;j++)
{
if(grid[i][j] == 2)
badapple.push({i,j});
}
}
int count = 0;
while(badapple.size())
{
count++;
int sz = badapple.size();
while(sz--)
{
auto [a,b] = badapple.front();
badapple.pop();
for(int k = 0; k < 4;k++)
{
int x = a + dx[k];
int y = b + dy[k];
if(x >=0 && x < n && y >=0 && y < m && grid[x][y] == 1 && !vis[x][y])
{
vis[x][y] = true;
badapple.push({x,y});
}
}
}
}
for(int i = 0;i < n;i++)
{
for(int j = 0;j<m;j++)
{
if(grid[i][j] == 1 && !vis[i][j])
return -1;
}
}
return count - 1;
}
};
3、孩子们的游戏(圆圈中最后剩下的数)
3.1、题目
3.2、思路
读完题,立马知道了属于是之前写过的杨辉三角问题同样经典的约瑟夫环问题了。题目意思跟大家熟悉的数字炸弹游戏类似的规则,首先需要一个随机数m,然后这里是按照从0开始报数,让其后报数为m-3的小朋友退出,直到剩下最后一个小朋友即可。那么,对于约瑟夫问题可以采用蛮力法模拟实现,我这里就是使用环形链表来实现,其次还可以通过像利用杨辉三角的性质(状态方程)一样,结合约瑟夫环的规律递推(动态规划)求解。首先,链表方法,由于题目中已知小朋友的编号是有序的0~n-1,所以直接先让其进链表即可,然后判断m-1位置时,让其该位置释放掉即可,直到剩下最后一个小朋友的编号输出即可。值得注意的是需要处理一些细节等,如需要让被释放的位置的下一个位置作为计数的开始,所以需要一个it变量记录,变化的计数位置(可以巧妙的利用迭代器)。
然后,对于递推(动态规划法),跟之前写过的最小花费爬楼梯套路是一样的需要确定动态规划的状态表示以及状态转移方程。那么就来推理dp[i]和dp[n],为了方便理解画个图理解:
3.3、程序实现 – 环形链表
首先,我们建立链表,将编号插入链表。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
list<int> lt;
for (int i = 0; i < n; i++)
{
lt.push_back(i);
}
}
};
然后,我们利用迭代器变量it,记录按照规则走的位置,走到m-1位置处,直接用it.erase删除释放即可,值得注意的是,当调用 lt.erase(it) 删除元素后,返回的迭代器是指向被删除元素之后的那个元素的迭代器。因此,在删除元素后,不需要再手动将 it 设置为 lt.begin(),因为 it 已经自动更新为指向下一个元素的迭代器(如果存在的话)。所以,在删除元素后直接进行下一次循环即可。
另外,如果实现的循环呢?判断it遍历到end处就使它置begin位置即可。所以链表模拟相对于数组模拟,迭代器相对更好用。依次类推,遇见m-1就删除,直到链表中剩下一个编号,返回即可。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
list<int> lt;
for (int i = 0; i < n; i++)
{
lt.push_back(i);
}
auto it = lt.begin();
while (lt.size() > 1)
{
for (int count = 1; count < m; ++count)
{
++it;
if (it == lt.end())
{
it = lt.begin();
}
}
it = lt.erase(it);
if (it == lt.end())
it = lt.begin();
}
return *it;
}
};
3.4、程序实现 – 约瑟夫环(动态规划法)
接着,动态规划重要的在于分析和理解,状态表示以及状态转移方程,程序就相对简单了。
接着按照推导的方程写好程序即可。只需要说一下为什么这里%上的 i,其实就是推导出的dp[n] = (dp[n-1] + m)%n;只是把n换成每一次循环求的 i 而已,表示求每一步的映射关系。
class Solution {
public:
int dp[5001];
int LastRemaining_Solution(int n, int m)
{
dp[1] = 0;
for(int i = 2;i <= n;i++)
dp[i] = (dp[i-1] + m) % i;
return dp[n];
}
};
还可以进一步空间优化,直接控制变量即可。总结模拟就是老实的遍历删除,动态规划就是找规律,推导方程求解。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
int f = 0;
for(int i = 2;i <= n;i++)
f = (f + m) % i;
return f;
}
};
4、题目链接
游游的you
腐烂的苹果
孩子们的游戏(圆圈中最后剩下的数)