错过的网易2021校招笔试-C++开发工程师 2020.8.8
因为腾讯的面试是14.00开始的,没想到面试官热情地面了2个小时,然后网易笔试就没做,直接去做的16点的美团笔试…
晚上在牛客上看到一个老兄的帖子,描述了一下4道题的题意,凭着自己的印象,做了一下。不知道正确与否。
1. 字符串追加字符变成最短的回文串
将一个字符串补成一个回文字符串。
字符串长度n<=1000
返回该回文字符串。
比如:输入“noo”返回“noon”;
输入“moom”返回“moom”;
输入“hello”返回“hellolleh”;
思路:
找到以s[n]结尾的回文串的最大长度ans,然后从s[n-ans],s[n-ans-1]…一直补到s[1]即可。
找ans可以用区间dp
#include <bits/stdc++.h>
using namespace std;
//n <= 1000
//给一个长度为n的字符串,可以往尾部追加任意字符多次,求可以得到的最短的回文串
//noon -> noon
//noo -> noon
//helloworld -> helloworlddrowolleh
const int N = 1002;
char str[N];
int dp[N][N];
int main(void) {
scanf("%s",str+1);
int ans = 1;
int n = strlen(str+1);
if(n==1) { //如果长度为1,则本身是回文
cout<<str+1<<endl;
return 0;
}
for(int i=1;i<=n;++i) dp[i][i] = 1;
for(int i=1;i<n;++i) dp[i][i+1] = (str[i]==str[i+1])?2:0;
if(str[n]==str[n-1]) ans = 2;
for(int len=3;len<=n;++len) {
for(int st=1;st+len-1<=n;++st) {
int ed = st+len-1;
if(str[st]==str[ed]&&dp[st+1][ed-1]!=0) {
dp[st][ed] = len;
if(ed==n) ans = max(ans,len);
}
}
}
//ans代表以最后一个字母为结尾的回文串的最大长度
string res = str+1;
for(int i=n-ans;i>=1;--i) res += str[i];
cout<<res<<endl;
return 0;
}
区间dp的时间复杂度和空间复杂度都是O(n^2), leetcode上有一道题是从首部添加,都差不多,我们可以用KMP。把该串作为文本串,把反转后的串作为模式串,这样匹配到文本串的结尾后,我们就找到了最长的回文后缀
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
char a[N],b[N];
int Next[N];
int KMP(char * a,int lena,char * b,int * Next) {
int i=0,j=0;
while(i<lena) {
if(j==-1||a[i]==b[j]) ++i,++j;
else j = Next[j];
} return j;
}
//fffabcba
// abcbafff
//把原串作为文本串,reverse后的串作为模式串进行匹配
void getNext(char * a,int len,int * Next) {
int i=0,j=-1;
Next[0] = -1;
while(i<len) {
if(j==-1||a[i]==a[j]) ++i,++j,Next[i]=j;
else j = Next[j];
}
}
int main(void) {
cin>>a;
int len = strlen(a);
for(int i=0;i<len;++i) b[i] = a[len-1-i];b[len] = 0;
getNext(b,len,Next);
int j = KMP(a,len,b,Next);
printf("%s",a);
for(int i=len-j-1;i>=0;--i) putchar(a[i]);puts("");
return 0;
}
2. 去掉价值最小的物品集合使得剩下的物品能分给两个人,价值相同
网易0808第2题
有一堆商品,各自有不同的价值
将这些商品分给两个人,要求两者拥有的价值数一样。
问需要舍弃多少价值的商品。
输入为一堆商品的价值序列。
T组输入(T<10), n<=15, a[i]<=100000
分析:
暴力选择去掉的集合(2^n种,dfs/二进制位压缩),然后每个集合判断可能性。判断可能性可以同样2^n的选,判断是否平分,也可以dp[i][j]代表前i个物品是否能恰好放满容量为j的背包。但容量过大,O(n*v) >>O(2^n)
也可以直接dfs三进制串,0表示给A,1表示给B,看是否相等,然后更新3^15 = 1.4E7
#include <bits/stdc++.h>
using namespace std;
//多组输入 T = 10
//每组n个物品,n<=15,每个物品价值为a[i], a[i]<=100'000
//问去掉价值最少的物品,使得剩下的物品可以平分给两个人价值相同
//先dfs/二进制状压得到所有剩下物品的组合,然后每个组合暴力判断是否能平分。这里暴力比dp快,因为dp范围大
const int N = 20;
int a[N],n;
//2^15 = 30000
//T = 10, 每组数据,
int r[N];
/*
解决一个问题,已经选出了m个物品,如何确定是否能凑出 sum/2?
法一:dfs爆搜,2^n 每种选择都看是否等于sum/2 (30000)
法二:dp,dp[i][j]表示前i个物品能否放满容量为j的背包,时间复杂度:n*sum(100000)
dp[1000000] = {1,0,0,0,0...0};
for(int i=1;i<=n;++i)
for(int j=sum;j>=a[i];--j)
dp[j] = dp[j-a[i]];
*/
int solve() {
vector<int> v;
int sum = 0;
for(int i=0;i<n;++i)
if(r[i]) v.push_back(a[i]),sum+=a[i];
if(sum&1) return -1;
sum >>= 1;
int cnt = v.size(); //cnt个数
int tot = 1<<cnt;
for(int st=0;st<tot;++st) {
int select = 0;
for(int i=0;i<cnt;++i) {
if((st>>i)&1) select+=v[i];
}
if(select==sum) return sum*2;
}
return -1;
}
int main(void) {
int T;
scanf("%d",&T);
while(T--) {
int sum = 0;
scanf("%d",&n);
for(int i=0;i<n;++i)
scanf("%d",a+i),sum+=a[i];
int ed = 1<<n;
int res = 0;
for(int state=0;state<ed;++state) {
for(int i=0;i<n;++i)
r[i] = ((state>>i)&1)?1:0;
int ans = solve();
if(ans!=-1) res = max(res,ans);
}
cout<<sum-res<<endl;
}
return 0;
}
3. 买票问题
网易0808第3题
n人排队购票,
给出:
单人购票用时,
一个人和其后的一个人一起购票用时;
问n个人购票,最少需要用时多少?
售票处开门时间是08:00:00 am,要求输出关门时间(12小时制,下午输出pm,显示格式两个数字)。
分析:每记错的话应该是某oj的原题,貌似之前在vj上做过,dp?
#include <bits/stdc++.h>
using namespace std;
const int N = 100000+100;
int a[N],b[N],dp[N];
//a[i]代表第i个人自己买票的时间
//b[i]代表第i个人和第i+1个人买票的时间
int main(void) {
int n;
cin>>n;
for(int i=1;i<=n;++i)
scanf("%d%d",a+i,b+i);
dp[1] = a[1];
dp[0] = 0;
for(int i=2;i<=n;++i) {
dp[i] = min(dp[i-1]+a[i],dp[i-2]+b[i-1]);
}
//cout<<dp[n]<<endl;
int h = dp[n]/60;
int m = dp[n]%60;
h+=8;
bool isPm = false;
if(h>12) h-=12,isPm = true;
printf("%02d:%02d:00 %s\n",h,m,isPm?"pm":"am");
return 0;
}
4. 相互认可的对数
网易0808笔试第4题
给出的是教授们认可信息,一组[1,2]表示1认可2,
如果有[1,2],[2,3],则可以认为1认可3.
问在给出信息中,互相认可的对数有多少?
n<=100000
分析:求有向图的强联通分量。每个强连通分量里的num个人相互认可。num*(num-1)/2 求和即可。
用tarjan求强连通分量。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
struct Node{
int to,Next;
}node[maxn];
int head[maxn],tot,cnt;
void init() { //初始化链式前向星
tot = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v) {
node[tot].to = v;
node[tot].Next = head[u];
head[u] = tot++;
}
stack<int> st;
bool instack[maxn];
int dfn[maxn];
int low[maxn];
long long res;
void tarjan(int u) {
st.push(u);
instack[u] = true;
dfn[u] = low[u] = ++cnt;
for(int i=head[u];i!=-1;i=node[i].Next) {
int v = node[i].to;
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
low[u] = min(low[u],dfn[v]);
}
if(dfn[u]==low[u]) {
int num = 0;
while(1) {
++num;
int x = st.top();st.pop();
instack[x] = false;
if(x==u) break;
}
//num为该强连通分量中结点的个数,他们都彼此认同,因此两两一对
res += 1ll*num*(num-1)/2;
}
}
int main(void) {
int n,m,u,v;
cin>>n>>m;
init();
for(int i=1;i<=m;++i) {
scanf("%d%d",&u,&v);
addedge(u,v);
}
res = 0;
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
printf("%lld\n",res);
return 0;
}
错过了网易笔试很可惜。题目质量挺高的。不知道这些代码能拿多少分, 希望深夜补做可以有所收获。
2020.8.9 1:44
xzc