• CSDN竞赛7期题解


    总结

    这次竞赛的题目质量相对之前竞赛来说是有明显进步的,由两道经典面试题加上两道中等难度题目构成。前两道的受众可能是初学算法的同学吧,对于学算法的同学来说,前两道题没有在五分钟内AC都是不合格的。当然,偷懒这么久没学算法的我,也花了数倍的时间才ac前两道。

    T3主要考察问题的分析能力,实现不难。T4考察数论基础,容斥原理和GCD,注意下细节也是不难ac的。

    题目列表

    1.奇偶排序

    题目描述

    给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。

    分析

    经典面试题,由于思维惯性,往往第一反应是不使用额外空间,使用双指针来处理。但是最快的做法显然是使用额外空间,而且题目模板给的返回值已经是额外空间了。所以,不妨遍历两遍数组,第一遍将奇数加入vector,第二遍放入偶数。

    有一点需要注意的是,本题没有说明重新排列后是否要维持奇偶元素内部的顺序,但是从用例看是要求维持相对顺序的。所以使用双指针的话也要注意维持奇偶元素内部的顺序。

    代码

    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    std::vector<int> solution(int n, std::vector<int>& vec){
    	vector<int> res;
    	for(auto x : vec) {
    		if(x & 1) res.push_back(x);
    	}
    	for(auto x : vec) {
    		if(!(x & 1)) res.push_back(x);
    	}
    	return res;
    }
    int main() {
    	int n;
    	std::vector<int> vec;
    	std::cin>>n;
    	std::string line_0, token_0;
    	getline(std::cin >> std::ws,line_0);
        std::stringstream tokens_0(line_0);
        while(std::getline(tokens_0, token_0, ' ')){
    		vec.push_back(std::stoi(token_0));
    	}
        std::vector<int> result = solution(n,vec);
        for(auto it=result.begin();it!=result.end();++it){
        	std::cout<<*it<<" ";
        }
        std::cout<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2.小艺照镜子

    题目描述

    已知字符串str。 输出字符串str中最长回文串的长度。

    分析

    还是经典的面试题,问题的规模是1w,平方级别以内的算法都可以接受。

    首先分析下暴力做法,二重循环分别来枚举子串的首尾位置,第三重循环来判断子串是否是回文串,时间复杂度是立方级别的。最方便的优化办法就是不去枚举子串的首尾位置,枚举子串的中间位置。

    比如abba,以第一个a为中间的子串是a,以ab中间位置为中心的子串是ab,以下标为1的b为中心的子串有b,abb等等。

    所以在遍历输入字符串时候,可以枚举以第i个字符为中心的和以i、i+1个字符中间位置为中心的最长回文子串的长度,最后取其中的最大值就是本题的解了。

    当然本题还有更高效的线性时间复杂度的解法,可以参考AcWing 139 回文子串的最大长度

    代码

    #include 
    #include 
    #include 
    #include 
    using namespace std;
    int solution(std::string &s){
        int res = 0, n = s.size();
        for(int i = 0;i < n;i++) {
        	int p = i, q = i + 1;
        	while(p >= 0 && q < n && s[p] == s[q]) {
        		res = max(res, q - p + 1);
        		p--,q++;
        	}
        	p = i, q = i;
        	while(p >= 0 && q < n && s[p] == s[q]) {
        		res = max(res, q - p + 1);
        		p--,q++;
        	}
        }
        return res;
    }
    int main() {
        std::string s;
        std::cin>>s;
        int result = solution(s);
        std::cout<<result<<std::endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. 交换后的or

    题目描述

    给定两组长度为n的二进制串,请问有多少种方法在第一个串中交换两个不同位置上的数字,使得这两个二进制串“或”的 结果发生改变?

    分析

    本题也是一类经典问题的变形,第一组二进制串上的一些位置的数字改变,可能会改变两个二进制串或的结果。

    如果第二个二进制串对应位置上是1,第一个二进制串上对应位置上的数字不管怎么改变,最终或的结果都是1,所以能够让或的结果发生改变的,只能是第二个二进制串上为0的位置。第一个二进制串上的数是0,则与为1位置上的数交换下就可以改变该位置的或结果;第一个二进制串上的数是1,则与为0位置上的数交换下也可以改变结果。

    举个例子:

    10010011
    01010100
    
    • 1
    • 2

    第一个串有4个0和4个1,第二个串有5个0,并且5个0对应的第一个串位置上有3个1和2个0,我们改变这3个1或者2个0的数值都可以改变或的结果。改变1的数值可以去与第一个串中剩下4个0的位置交换,一共有3 * 4 = 12种交换方法;同理改变2个0的数值一共有2 * 4 = 8种方法。要注意的是,改变1的数值的交换方案中包含与2个0交换的方案,改变0的数值的交换方案中包含与3个1交换的方案,因此这部分是重复统计的,需要减去3 * 2 = 6个重复方案,因此,改变这两个串或结果的交换办法一共有12 + 8 - 6 = 14种。

    设第一个二进制串上有a个0,b个1。第二个串中为0位置上对应第一个串的0的个数和1的个数分别是p和q,总的交换方案数就是aq + bp - pq。

    代码

    #include 
    #include 
    using namespace std;
    int solution(int n, std::string str1, std::string str2){
        int a = 0,b = 0;//0 1
        for(int i = 0;i < n;i++) {
        	if(str1[i] == '1') b++;
        	else a++;
    	}
        int p = 0,q = 0;
        for(int i = 0;i < n;i++) {
        	if(str2[i] == '0') {
        		if(str1[i] == '0') p++;
        		else q++;
        	}
        }
        int res = a * q + b * p - p * q;
        return res;
    }
    int main() {
        int n;
        std::string str1;
        std::string str2;
        std::cin>>n;
        std::cin>>str1;
        std::cin>>str2;
        int result = solution(n, str1, str2);
        std::cout<<result<<std::endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    4.去除整数

    题目描述

    已知存在集合A包含n个整数,从1到n。 存在m个整数a[1…m]。 在集合A中去除这m个整数的倍数。 输出集合中包含 的元素的个数。

    分析

    本题考查容斥原理和最大公约数。数据范围n不超过109,m不超过10,但是注意要去除的是m个整数a[i]的倍数,虽然m不超过10,但是a[i]的范围可以很大。暴力做法很简单,遍历1到n中所有的数,对每个数判断下是否能够被a[i]整除,计算次数最多可以达到10 * 109 = 1010,所以显然是不可行的。

    我们需要找的是1到n中去除m个整数的倍数剩下的数,也就是只要找到能被这m个数之一整除的数有多少个即可。而1到n中能被x整除的数一共有n / x个。举个例子更能说明问题。

    n = 100,m = 3,a = [2, 3, 4]。100以内能被2整除的有50个,能被3整除的有33个,能被4整除的有25个,能否说明能被2或者3或者4整除的一共有50 + 33 + 25 = 108个呢?显然是不能的。设能被2整除的有x个,被3整除的有y个,被4整除的有z个,能被2和3整除的有a个,能被2和4整除的有b个,能被3和4整除的有c个,能被2,3,4整除的有d个,根据容斥原理可得,能被2或者3或者4整除的一共有x + y + z - a - b - c + d个。

    现在问题来到如何求能被x和y同时整除的数的个数,比如能被2和3同时整除的数就是能被6整除的数,能否说明能被x和y同时整除的数的个数就是能被x * y整除的数的个数呢?显然不能,如果只计算能被x * y整除数的个数,只能通过七成左右的用例。再比如能同时被2和4整除的数一定能被4整除,所以实际上能同时被k个数整除的数的个数等于能被这k个数的最小公倍数整除数的个数。

    总结下解题思路。例如m = 5,我们要求出1到n中能被五个数中的1个数整除的数的个数,能被5个数中2个数整除的数的个数,…。这就涉及组合数了,5个数中选1,2,3,4,5个的方案一共C(5,1)+C(5,2)+C(5,3)+C(5,4)+C(5,5) = 25种选法,我们可以用dfs将所有选法的最小公倍数存下,比如在2,3,4,5,6种选2个数可以是2,3,最小公倍数是6,能被6整除的一共n / 6个。1到n中能被m个数中一个数整除的一共x1个,能被2个数同时整除的一共x2个,能被3个数同时整除的一共x3个,…,所以最后能被这m个数中至少一个数整除的数一共有res = x1 - x2 + x3 - x4 +…个,n个数中去除res个数还剩n - res个数。

    最后要注意求最小公倍数时的可能爆int,需要用long long存储,由于超过109的数不可能整除n,所以可以忽略最小公倍数超过109的方案。

    有段时间没写算法题了,比赛时把gcd代码模板打错了,求lcm返回时也没有强转为long long,导致多花了不少时间才AC本题。

    代码

    #include 
    #include 
    #include 
    #include 
    using namespace std;
    typedef long long ll;
    const int N = 1e9 + 7;
    vector<int> temp;
    int gcd(int a,int b) {
        if(!b) return a;
        return gcd(b, a % b);
    }
    ll lcm(int a,int b) {
        if(a < b) swap(a,b);
        return (ll)a / gcd(a,b) * b;//计算时爆int,需要强转
    }
    //temp存储着v中选k个数的最小公倍数的集合
    void dfs(vector<int>& v,int u,int k,int cnt,int s) {
        if(cnt == k) {//已经选了k个数
        	temp.push_back(s);
        	return;
        }
        if(cnt > k || u >= (int)v.size()) return;
        dfs(v,u+1,k,cnt,s);//不选第u个数
        ll t = lcm(s, v[u]);//选了第u个数后的最小公倍数
        if(t < N) dfs(v,u+1,k,cnt+1,t);//选第u个数
    }
    int solution(int n, int m, std::vector<int>& vec){
        int res = 0;
        for(int i = 1;i <= m;i++) {
        	temp.clear();
        	dfs(vec,0,i,0,1);//预处理选了i个数的最小公倍数的集合
        	if(i & 1) {
        		for(auto x : temp) res += n / x;
        	}
        	else {
        		for(auto x : temp) res -= n / x;
        	}
        }
        return n - res;
    }
    int main() {
        int n;
        int m;
        std::vector<int> vec;
        std::cin>>n;
        std::cin>>m;
        std::string line_0, token_0;
        getline(std::cin >> std::ws,line_0);
        std::stringstream tokens_0(line_0);
        while(std::getline(tokens_0, token_0, ' ')){
        	vec.push_back(std::stoi(token_0));
        }
        int result = solution(n, m,vec);
        std::cout<<result<<std::endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    城市轨道交通站应急照明疏散指示系统设计
    leetcode笔记(自用)
    SpringCloud微服务
    【面试经典 150 | 回溯】电话号码的字母组合
    如何完成网课答案公众号搭建?小白教程!内附网课题库接口!
    Android 回声消除
    【树莓派不吃灰】Linux篇⑨ 学习 磁碟配额(Quota)与进阶文件系统管理(核心概念)
    什么是数据采集与监视控制系统(SCADA)?
    caffe搭建squeezenet网络的整套工程
    【GO】项目import第三方的依赖包
  • 原文地址:https://blog.csdn.net/qq_30277239/article/details/127400788