笔试强训:Week-4

目录

一*、小易的升级之路(数学)

二、礼物的最大价值(路径dp)

三*、对称之美(双指针/哈希)

四、经此一役小红所向无敌

五*、连续子数组最大和(线性dp)

六*、非对称之美(贪心+找规律)

七*、爱丽丝的人偶(贪心)

[八*、集合(排序+双指针 / set去重)](#八*、集合(排序+双指针 / set去重))

九、最长回文子序列(区间dp)

十*、添加字符(枚举)

十一*、数组变换(贪心+位运算+数学)

十二、装箱问题(01背包)

十三*、打怪(模拟+数学)

十四、字符串分类(排序+哈希)

十五*、城市群数量(floodfill)

十六、判断是不是平衡二叉树(递归)

十七、最大子矩阵(二维前缀和)

十八*、小葱的01串(定长滑动窗口)


一*、小易的升级之路(数学)

小易的升级之路_牛客题霸_牛客网

就是求最大公约数,直接用gcd函数,不过得记得头文件 #include<numeric>

如果是暴力的话会超时,或者手搓一个gcd函数,这里推荐使用欧几里得算法,时间复杂度最低。

可以进一步优化,说实话还是感觉上面的代码更好理解。

记得这种算法可以用装修用固定大小的瓷砖填地板解释,或者该用切蛋糕来解释吧,比如我有一块24x16(cm)的蛋糕,要求切成大小相同的正方形蛋糕,但不能剩余,问切出的蛋糕最大边长是多少?答案就是24和16的最大公约数。而欧几里得算法得到这个最大公约数的求解过程,就可以理解为:

第一次用较小的边尝试,切出一块16x16的蛋糕,但是有剩余,不符合题意,那么我们就要去更小的那部分里边找边长更小的正方形(这里边找出的结果,最终在更大的那部分比如这里是16x16,一定能切出很多结果边长大小的正方形,并且一定不会有剩余),然后继续在小的里边继续切,思路依然是用小的那条边当作大小来切。然后找到8,8可以,且较大边%8==0,a=temp(8),b=a%b也就是等于0,然后下一次循环,b==0退出循环,a==8就是最终结果。也就是说,我们每次都选择较小边当作切成的蛋糕大小,如果没有剩余,那么此时就是最大公约数。如果有剩余,那么我们以剩下那部分的两条边长作为参数,不断进行这种操作,直到出现结果。妙啊。

代码实现如下:

cpp 复制代码
#include <iostream>
#include <numeric>
using namespace std;
using LL=long long;
int main() {
    LL n,x;
    cin>>n>>x;
    LL Mlevel;
    for(int i=0;i<n;++i)
    {
        cin>>Mlevel;
        if(x>=Mlevel)x+=Mlevel;
        else
        {
            x+=gcd(x, Mlevel);
        }
    }
    cout<<x;
    return 0;
}

二、礼物的最大价值(路径dp)

礼物的最大价值_牛客题霸_牛客网

相当基础的路径dp题,不知道是不是第一天所以这么简单?

cpp 复制代码
#include <vector>
class Solution {
public:
    int maxValue(vector<vector<int> >& grid) {
        int m=grid.size(),n=grid[0].size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        for(int i=1;i<=m;++i)
        {
            for(int j=1;j<=n;++j)
            {
                dp[i][j]=grid[i-1][j-1]+max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return dp[m][n];
    }
};

三*、对称之美(双指针/哈希)

对称之美

解法一:双指针

开始还以为要dfs全排列,然后判断是不是回文,心想这太复杂了,然后写出dfs全排列+判断回文的代码,运行过了ok提交,果不其然超时了。然后想着想着不就是判断1对应n-1,2对应n-2以此类推判断是不是有重复元素嘛,这样现将字符串存进数组,然后用双指针就能搞定了,写一个判断回文的函数不就是判断有没有Same

代码实现如下:

cpp 复制代码
#include <iostream>
#include <ostream>
using namespace std;
#include<vector>
#include<algorithm>
bool HasSameChar(string s1,string s2)
{
    sort(s1.begin(),s1.end());
    sort(s2.begin(),s2.end());
    int cur1=0,cur2=0;
    while(cur1<s1.size()&&cur2<s2.size())
    {
        if(s1[cur1]<s2[cur2])++cur1;
        else if(s1[cur1]>s2[cur2])++cur2;
        else return true;
    } 
    return false; 
}
int main() {
    int t;
    cin>>t;
    int n;
    while(t--)
    {
        cin>>n;
        vector<string> s(n);
        for(int i=0;i<n;++i)cin>>s[i];
        //因为输出的二元性,所以我用bool变量来标识
        bool flag=true;
        int left=0,right=n-1;
        while(left<right)
        {
            if(!HasSameChar(s[left], s[right]))
            {
                flag=false;
                break;
            }
            ++left;
            --right;
        }
        if(flag)cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}

解法二:哈希

判重判重,第一个想到的不就是哈希嘛

我们用一个二维数组来模拟哈希表的思路,第一个位置表示第几个字符串,第二个位置用于表示哪个字母,所以我们开[101][26]这样的大小

cpp 复制代码
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
bool vis[110][26];//用来标记每一行 字母是否存在
int t,n;
string s;
bool check(int left,int right){
    //26个字母暴力检查有没有
    for(int i=0;i<26;++i)
        if(vis[left][i]&&vis[right][i]) return true;
    return false;
}
int main(){
    cin>>t;
    while(t--){
        //先清空hash表
        memset(vis,0,sizeof vis);
        cin>>n;//知道了有多少个字符串
        for(int i=0;i<n;++i){
            cin>>s;
            for(auto&ch:s) vis[i][ch-'a']=true;
        }
        //双指针向中间靠
        int left=0,right=n-1;
        for(;left<right;++left,--right)
            if(!check(left,right)) break;
        //left<right为假说明只是越界导致结束,输出Yes
        cout<<(left<right?"No":"Yes")<<endl;
    }
    return 0;
}

今天三道都写出来了,希望明天也能都写出来hhh\(^o^)/~

实则不然

四、经此一役小红所向无敌

经此一役小红所向无敌

模拟:会超时

cpp 复制代码
#include <iostream>
using namespace std;
using LL=long long;
int main() {
    LL a,h,b,k;
    cin>>a>>h>>b>>k;
    LL sum=0;
    while(h&&k)
    {
        sum+=(a+b);
        h-=b;
        k-=a;
        if(h<=0&&k<=0)break;
        if(h<=0&&k)
        {
            sum+=(b*10);
            break;
        }
        if(k<=0&&h)
        {
            sum+=(a*10);
            break;
        }
    }
    cout<<sum;
    return 0;
}

优化一下就是,提前统计一下互砍的次数,取min

cpp 复制代码
#include<iostream>
using namespace std;
typedef long long LL;
LL a,h,b,k;
int main(){
    cin>>a>>h>>b>>k;
    LL ret=0;//累计伤害
    //看看两个人能互砍多少回合
    LL n=min(h/b,k/a);
    ret+=n*(a+b);
    //计算剩余血量
    h-=b*n;
    k-=a*n;
    //看看是不是都活着 如果都活着就再砍一下
    if(h>0&&k>0){
        h-=b;
        k-=a;
        ret+=a+b;
    }
    //这时候至少死了一个  或者两个都死了
    if(h>0||k>0)  ret+=10*(h>0?a:b);
    cout<<ret<<endl;
    return 0;
}

五*、连续子数组最大和(线性dp)

连续子数组最大和(ACM版本)_牛客题霸_牛客网

没有做出来,看来近期要复习dp了。。。

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2e5+10;
int nums[N],dp[N];
int n;
int main() {
   cin>>n;
   int ret=-101;
   for(int i=1;i<=n;++i) cin>>nums[i];
   for(int i=1;i<=n;++i){
     dp[i]=max(dp[i-1],0)+nums[i];
     ret=max(dp[i],ret);
   }
   cout<<ret<<endl;
   return 0;
}

六*、非对称之美(贪心+找规律)

非对称之美

所谓题干越短,杀伤力越强?

情况1:所以字符都一样 return 0

情况2:整个串都是回文 return n-1

情况3:整个串不是回文 return n

佩服。。。。这就是非对称之美吗

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
string s;
int func(){
    int n=s.size();

    //首先要判断是否全都相同
    int i=1;
    for(;i<n;++i)
        if(s[i]!=s[0]) break;
    if(i==n) return 0;

    //接下来双指针往中间靠,判断整个字符串是否都是回文串
    int left=0,right=n-1;
    for(;left<right;++left,--right)
        if(s[left]!=s[right]) break;

    return left<right?n:n-1;
}
int main(){
    cin>>s;
    cout<<func()<<endl;
    return 0;
}

七*、爱丽丝的人偶(贪心)

爱丽丝的人偶

没有思路

看完解答后发现只要让数组的数据是波形的就行

cpp 复制代码
#include<iostream>
using namespace std;
int n;
int main(){
    cin>>n;
    //必须有单调性,所以就一高一矮 这样放
    int left=1,right=n;
    while(left<=right){
        cout<<left++<<" ";
        if(left<=right) cout<<right--<<" ";
    }
    return 0;
}

八*、集合(排序+双指针 / set去重)

集合_牛客题霸_牛客网

解法一:排序+双指针

因为使用双指针算法,不要忘记排序。此外,注意可能会有重复元素!

其实这也是一种归并思路

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
int n,m;
const int N=1e4+10;
int a[N],b[N];
int main() {
    cin>>n>>m;
    for(int i=0;i<n;++i) cin>>a[i];
    for(int i=0;i<m;++i) cin>>b[i];
    sort(a,a+n);
    sort(b,b+m);
    //双指针归并 小的插入后++  
    int cur1=0,cur2=0;
    while(cur1<n&&cur2<m){
        if(a[cur1]<b[cur2]){
            cout<<a[cur1++]<<" ";
            while(a[cur1]==a[cur1-1]) ++cur1;
        } 
        else if(a[cur1]>b[cur2]){
            cout<<b[cur2++]<<" ";
            while(b[cur2]==a[cur2-1]) ++cur2;
        }
        else{
            cout<<a[cur1++]<<" ";
            ++cur2;
            while(a[cur1]==a[cur1-1]) ++cur1;
            while(b[cur2]==a[cur2-1]) ++cur2;
        }
    }
    //有一组还没走完
    while(cur1<n){
        if(cur1==0||a[cur1]!=a[cur1-1]) cout<<a[cur1]<<" ";
        ++cur1;
    }
     while(cur2<m){
        if(cur2==0||b[cur2]!=b[cur2-1]) cout<<b[cur2]<<" ";
        ++cur2;
    }
    return 0;
}
// 64 位输出请用 printf("%lld")

解法二:set(set容器默认对数据升序排序,而且会去重)

cpp 复制代码
#include <iostream>
#include <set>
using namespace std;
int n,m;
set<int> s;
int main() {
    cin>>n>>m;
    int x;
    while(n--){
        cin>>x;
        s.insert(x);
    }
    while(m--){
        cin>>x;
        s.insert(x);
    }
    for(auto&e:s) cout<<e<<" ";
    return 0;
}
// 64 位输出请用 printf("%lld")

九、最长回文子序列(区间dp)

最长回文子序列_牛客题霸_牛客网

没想到自己一遍做出来了,开心啊

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    //dp[i][j]表示i~j区域内的最长回文子序列
    string s;
    cin>>s;
    int n=s.size(),len=1;
    vector<vector<int>> dp(n,vector<int>(n,1));
    //因为第一个位置会使用后续的位置,所以倒着填
    for(int i=n-1;i>=0;--i)
    {
        for(int j=i+1;j<n;++j)
        {
            if(s[i]==s[j])
            {
                dp[i][j]=i+1<j?dp[i+1][j-1]+2:2;
            }
            else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
            len=max(len,dp[i][j]);
        }
    }   
    cout<<len;
    return 0;
}

十*、添加字符(枚举)

添加字符_牛客笔试题_牛客网

本来以为是匹配最长的连续的长度,然后字符串长度减去求得的最长长度来做,但是匹配可以在A串的任何一个位置匹配,如果第一个位置不同但后续相同,代码就出错了,比如xello和hello,最终输出5因为求得最长长度为0,但答案应该为1所以还是枚举吧

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
string a,b;
int main() {
  cin>>a>>b;
  int m=a.size(),n=b.size();
  //不相等的最多就是m个
  int ret=m;
  for(int i=0;i<=n-m;++i){//枚举b的起始位置
      int tmp=0;//统计不同位数的个数
      for(int j=0;j<m;++j)
        if(a[j]!=b[i+j]) ++tmp;
      ret=min(ret,tmp);
  }
  cout<<ret<<endl;
}
// 64 位输出请用 printf("%lld")

十一*、数组变换(贪心+位运算+数学)

数组变换__牛客网

贪心:因为我们每次都需要在一个大一个小的数里面,看小的数不断x2操作能不能达到更大的那个数,因为如果两个数通过乘2操作能相等,那么小的数乘以一定次数的2就能等于大的数。那么现在这个"更大的数"可能会有更大的数,那么就要看这个大的数能不能不断x2操作等于它的更大的数。所以我们只需要找到数组里的最大值,然后看其他数能不能通过x2操作达到这个值即可。那么我们只需要判断MAXNUM/arr[i]能不能被2整除即可。

如何判断被2整除?

x&(x-1)==0就代表最高位是1其他全是0,这就是2的倍数。

或者 x-(x&-x)==0,x&-x得到x低位到高位第一个1,如果减去后为0,就说明只有最高位是1,其他位是0,是2的倍数。

cpp 复制代码
#include <iostream>
using namespace std;
int a[51];
int n,maxval;
bool func(){
    for(int i=0;i<n;++i){
        if(maxval%a[i]) return false;
        int x=maxval/a[i];
        //if(x&(x-1)) return false;
        if(x-(x&-x)) return false;
    }
    return true;
}
int main() {
    cin>>n;
    for(int i=0;i<n;++i){
        cin>>a[i];
        maxval=max(maxval,a[i]);
    }
    cout<<(func()?"YES":"NO")<<endl;
}
// 64 位输出请用 printf("%lld")

十二、装箱问题(01背包)

装箱问题

cpp 复制代码
#include<iostream>
using namespace std;
//dp[i][j]表示选前i个物品 容积不超过j  所占的最大容积
const int N=2e4+1;
int arr[31];
int dp[N];
int v,n;
int main(){
    cin>>v>>n;
    for(int i=1;i<=n;++i) cin>>arr[i];
    for(int i=1;i<=n;++i)
        for(int j=v;j>=arr[i];--j)
            dp[j]=max(dp[j],dp[j-arr[i]]+arr[i]);
    cout<<v-dp[v]<<endl;
}

十三*、打怪(模拟+数学)

打怪

monimonimoni,想这个公式模拟了好久,估计是没睡饱

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int t;
    cin>>t;
    while(t--)
    {
        int h,a,H,A;
        cin>>h>>a>>H>>A;
        if(A==0||a>=H){cout<<-1<<endl;continue;}
        //times每次杀死怪需要几次攻击
        //times-1对应杀死一只怪受到几次伤害
        int times=H/a+(H%a?1:0);
        //blood是杀一次怪扣的血
        //判断最后一次是否符合,如果刚好可以取模,代表第x个怪的时候,勇者先死,减去这一次
        int blood=(times-1)*A;
        int x=h/blood+(h%blood==0?-1:0);
        cout<<x<<endl;
    }
    return 0;
}

十四、字符串分类(排序+哈希)

字符串分类_牛客笔试题_牛客网

很简单的题目

cpp 复制代码
#include <iostream>
using namespace std;
#include<algorithm>
#include<unordered_map>
int main() {
    int n;
    string s;
    unordered_map<string, int> hash;
    cin>>n;
    while(n--)
    {
        cin>>s;
        sort(s.begin(),s.end());
        hash[s]++;
    }
    cout<<hash.size();
    return 0;
}

十五*、城市群数量(floodfill)

城市群数量_牛客题霸_牛客网

原本以为是遍历下三角,然后统计一下入度,再从1~n里查找入度为0的数量,这样就是城市群的数量。但是这是不正确的。因为:

该题为无向边,没有出度还有入度的概念,所以不能这样统计。

比如2指向1,3指向1,那么此时城市群的数量应该是1.但是按照我们统计入度为0的个数,算出的是2错误

正确解法是floodfill问题的思路,用vis标记连接的城市群

cpp 复制代码
class Solution {
public:
//联通块问题
bool vis[201]={0};//标记每个城市是否被搜索过
int n;
    int citys(vector<vector<int>>& nums) {
        n=nums.size();
        int ret=0;
        for(int i=0;i<n;++i)
          if(!vis[i]){
            ++ret;
            dfs(nums,i);//没被搜索过 就从该点开始找联通块
         }
        return ret;
    }
    void dfs(vector<vector<int>>& nums,int pos){
        vis[pos]=true;
        for(int i=0;i<n;++i)
           if(!vis[i]&&nums[pos][i]) dfs(nums,i);
    }
};

今天三道题也是稳稳拿下

十六、判断是不是平衡二叉树(递归)

判断是不是平衡二叉树_牛客题霸_牛客网

很简单

cpp 复制代码
class Solution {
public:
    int GetDepth(TreeNode*root)
    {
        if(!root)return 0;
        return 1+max(GetDepth(root->left),GetDepth(root->right));
    }
    bool IsBalanced_Solution(TreeNode* root) {
        if(!root)return true;
        if(!IsBalanced_Solution(root->left))return false;
        if(!IsBalanced_Solution(root->right))return false;
        int LeftDepth=GetDepth(root->left),RightDepth=GetDepth(root->right);
        if(abs(LeftDepth-RightDepth)>1)return false;
        return true;
    }
};

十七、最大子矩阵(二维前缀和)

最大子矩阵_牛客题霸_牛客网

一遍过

cpp 复制代码
#include <climits>
#include <iostream>
using namespace std;
#include<vector>
int main() {
    int N;cin>>N;
    vector<vector<int>> martix(N+1,vector<int>(N+1));
    auto FrontSum=martix;
    int res=INT_MIN;
    for(int i=1;i<=N;++i)
    {
        for(int j=1;j<=N;++j)
        {
            cin>>martix[i][j];
            //计算前缀和
            FrontSum[i][j]=FrontSum[i-1][j]+FrontSum[i][j-1]-FrontSum[i-1][j-1]+martix[i][j];
            res=max(res,FrontSum[i][j]);
            for(int x=1;x<=i;++x)
            {
                for(int y=1;y<=j;++y)
                {
                    //计算区间前缀和
                    int Sum=FrontSum[i][j]-FrontSum[i][y-1]-FrontSum[x-1][j]+FrontSum[x-1][y-1];
                    res=max(res,Sum);
                }
            }
            
        }
    }
    cout<<res;
    return 0;
}

十八*、小葱的01串(定长滑动窗口)

小葱的01串

看完题目觉得就是求定长窗口0,1数量如果等于0,1总数的一半就++计数器,然后最后输出计数器乘以2?应该不对,如果头位置和尾位置在窗口里且符合的时候,其实是同一种情况。只有窗口在不包含头尾的中间位置的时候的解决方案才是需要*2的。

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int n;cin>>n;
    string s;
    cin>>s;
    int Num0=0,Num1=0,count0=0,count1=0,solves=0;
    for(auto&ch:s)
    {
        if(ch-'0')++Num1;
        else ++Num0;
    }
    for(int i=0;i<n/2;++i)
    {
        if(s[i]=='0')++count0;
        else ++count1;
    }
    int right=n/2,left=0;
    int flag=0;
    if(count0==Num0/2&&count1==Num1/2){++solves;flag=2;}
    while(right<n)
    {

        if(s[right]=='0')++count0;
        else ++count1;
        if(s[left]=='0')--count0;
        else --count1;

        if(count0==Num0/2&&count1==Num1/2)++solves;
           
        ++right;
        ++left;
    }
    cout<<(solves-flag)*2+flag;
    return 0;
}

或者说,因为1和n-1包含在窗口里时是属于同一种情况,我们只需要从0遍历到n-2的位置即可

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int n;cin>>n;
    string s;
    cin>>s;
    int Num0=0,Num1=0,count0=0,count1=0,solves=0;
    for(auto&ch:s)
    {
        if(ch-'0')++Num1;
        else ++Num0;
    }
    for(int i=0;i<n/2-1;++i)
    {
        if(s[i]=='0')++count0;
        else ++count1;
    }
    int left=0,right=n/2-1;
    while(right<n-1)
    {

        if(s[right]=='0')++count0;
        else ++count1;
        
        if(count0==Num0/2&&count1==Num1/2)++solves;
        
        if(s[left]=='0')--count0;
        else --count1;

        ++right;
        ++left;
    }
    cout<<solves*2;
    return 0;
}

本周结束!完结撒花✿✿ヽ(°▽°)ノ✿

相关推荐
星释2 小时前
Rust 练习册 :Nucleotide Codons与生物信息学
开发语言·算法·rust
BeingACoder2 小时前
【SAA】SpringAI Alibaba学习笔记(二):提示词Prompt
java·人工智能·spring boot·笔记·prompt·saa·springai
Acrelhuang2 小时前
覆盖全场景需求:Acrel-1000 变电站综合自动化系统的技术亮点与应用
大数据·网络·人工智能·笔记·物联网
寂静山林2 小时前
UVa 1366 Martian Mining
算法
liu****2 小时前
12.线程(二)
linux·开发语言·c++·1024程序员节
DKPT2 小时前
如何设置JVM参数避开直接内存溢出的坑?
java·开发语言·jvm·笔记·学习
一 乐3 小时前
智慧党建|党务学习|基于SprinBoot+vue的智慧党建学习平台(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习
海边夕阳20063 小时前
MVCC核心原理解密:从隐藏字段到版本链的完整解析
经验分享·学习·数据库架构·mvcc
陌路203 小时前
S12 简单排序算法--冒泡 选择 直接插入 希尔排序
数据结构·算法·排序算法