这次竞赛的题目质量相对之前竞赛来说是有明显进步的,由两道经典面试题加上两道中等难度题目构成。前两道的受众可能是初学算法的同学吧,对于学算法的同学来说,前两道题没有在五分钟内AC都是不合格的。当然,偷懒这么久没学算法的我,也花了数倍的时间才ac前两道。
T3主要考察问题的分析能力,实现不难。T4考察数论基础,容斥原理和GCD,注意下细节也是不难ac的。
给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。
经典面试题,由于思维惯性,往往第一反应是不使用额外空间,使用双指针来处理。但是最快的做法显然是使用额外空间,而且题目模板给的返回值已经是额外空间了。所以,不妨遍历两遍数组,第一遍将奇数加入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;
}
已知字符串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;
}
给定两组长度为n的二进制串,请问有多少种方法在第一个串中交换两个不同位置上的数字,使得这两个二进制串“或”的 结果发生改变?
本题也是一类经典问题的变形,第一组二进制串上的一些位置的数字改变,可能会改变两个二进制串或的结果。
如果第二个二进制串对应位置上是1,第一个二进制串上对应位置上的数字不管怎么改变,最终或的结果都是1,所以能够让或的结果发生改变的,只能是第二个二进制串上为0的位置。第一个二进制串上的数是0,则与为1位置上的数交换下就可以改变该位置的或结果;第一个二进制串上的数是1,则与为0位置上的数交换下也可以改变结果。
举个例子:
10010011
01010100
第一个串有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;
}
已知存在集合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;
}