观前提醒:
笔试所有系列文章均是记录本人的笔试题思路与代码,从中得到的启发和从别人题解的学习到的地方,所以关于题目的解答,只是以本人能读懂为目标,如果大家觉得看不懂,那是正常的。如果对本文的某些知识有不同的观点,欢迎讨论。
题目链接:
第一题:孩子们的游戏(圆圈中最后剩下的数)_牛客题霸_牛客网
第二题:腐烂的苹果_牛客题霸_牛客网
第三题:游游的you__牛客网
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
第一题 (重做)
思路:
本题是一道经典的约瑟夫环问题,引申出的解法思路有两种
1)一是借助STL的vector与list模拟圆圈循环,不断模拟下去,一个个删除元素,最后的就是想要的那个孩子。
这里建议使用链表模拟,因为vector的删除节点还是有点费事的。不过关于vector解法也有相应的优化策略,例如可以额外创建一个表,专门记录这个节点有没有删除。
2)利用动态规划,迭代等,核心:找到规律,只要找到规律使用那个方法都可以
我们观察下图,如果说黑笔是第一次循环,在我们第一次将某个值排除后,那我们是不是可以将排除位置后的那个看成新的起点,那我们是不是可以将蓝色笔记看成第二次查找。
但是我们既然将“5”看成新的起点,那我们是不是可以把第二次查找看成是n-1个孩子的查找问题,并且n-1与n个孩子的查找结果应该是一样的
对不对,那我们是不是就找到一种符合动态规划的关系,那么我们就可以使用动态规划解决问题。
那我们接下来的问题是找到dp[n-1]与dp[n]的关系。
在上面的分析中,我们意识到两种问题的结果是一样的,我们要找到的是两种情况的下标,
因为我们的下标在外面设置新起点的时候就已经改变了。
我们开始分析下标的映射
对应黑蓝两种情况,
【m,0】,【m+1,1】,【n-3,n-3-m】,【n-2-m】,【n-1-m】,【】(零点是一个重要节点按照前面的递推,我们是吧它映射为0-m吗?
(不是,我们要记住它是一个环,按照环的推理,0点也是n点,1点也是n+1点,所以从“0”后的点都要加上 n)
所以经过上面的讨论,我们是不是发现了两种情况的下标映射关系,
dp[n]=dp[n-1]+m,想一想对不对,是不是我们没有考虑到成环的0点后面的问题,
所以我们要减去一个n,对不对再加分类讨论有没有过0点,那岂不是太麻烦了
我们想一想在数组中是如何模拟成环的,模上一个数组大小不就好了,还不用分类讨论。
所以我们最后关系如下
dp[n]=(dp[n-1]+m)%n【由于不断在删除数据,所以n的值是在减少的】;
代码:
//解法一,使用数组模拟解决
class Solution {
public:
int LastRemaining_Solution(int n, int m) {
vector<int> nums(n);
int i=0;
for(int i=0;i<n;i++) nums[i]=i;
while( nums.size() != 1)
{
int count=m;
while(--count)
{
i++;
i%=nums.size();
}
nums.erase(nums.begin()+i,nums.begin()+i+1);
}
return nums[0];
}
};
//解法二,找规律
class Solution {
public:
int LastRemaining_Solution(int n, int m) {
if (n == 0 || m == 0) return -1;
//实际上我们根本没有必要专门开辟一个dp表,
//我们发现dp[n]只会使用dp[n-1]一种状态值我们使用单个变量记录一下就好
int f=0;
for(int i=2;i<=n;i++)
f=(f+m)%i;
return f;
}
};
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
第二题
思路:
一道多源dfs,大家只要记住模板了,解决就不难了。
需要注意的是,我们记录的时间,但是在dfs中我们只能记录下层数,并且层数还会多一层,那是我们最后感染完了,但是还要向外遍历一遍,判断有没有要感染的。
代码:
class Solution {
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int m=0,n=0;
public:
int rotApple(vector<vector<int> >& grid) {
int count=0;
queue<pair<int,int>> dp;
m=grid.size(),n=grid[0].size();
int level=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j] == 1)
count++;
if(grid[i][j] == 2) dp.push({i,j});
}
}
while(dp.size())
{
// cout<<"ss"<<endl;
int z=dp.size();
for(int j=0;j<z;j++)
{
auto& [a,b]=dp.front();
dp.pop();
for(int i=0;i<4;i++)
{
// cout<<"ss"<<endl;
int x=dx[i]+a,y=dy[i]+b;
// cout<<x<<":"<<y<<endl;
if(x >=0 && x < m && y >= 0 && y < n && grid[x][y] == 1)
{
grid[x][y]=2;
count--;
dp.push({x,y});
}
}
}
level++;
}
// printf("%d\n",count);
return count>0 ? -1:level-1;
}
};
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
---------------------------------------------------我是分割线---------------------------------------------------------------
第三题
思路:
一道简单的贪心,要想分数最大那我们首先要组成最多的“you”,剩下的“o”排在一起组成最多的“oo”组合,值得注意的是,“ooo”是算作两对“oo”计两分,所以我们有n个o,就最多有n-1对“oo”。
代码:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n=0;
cin>>n;
for(int i=0;i<n;i++)
{
int count=0;
int a=0,b=0,c=0;
cin>>a>>b>>c;
int x=min(a,min(b,c));
count=x*2;
a-=x,b-=x,c-=x;
if(b)
{
count+=(b-1);
}
printf("%d\n",count);
}
return 0;
}