2017年网易内推笔试编程题
1
(动态规划)
小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…….
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板
输入描述:
输入为一行,有两个整数N,M,以空格隔开。
(4 ≤ N ≤ 100000)
(N ≤ M ≤ 100000)
输出描述:
输出小易最少需要跳跃的步数,如果不能到达输出-1
输入例子1:
4 24
输出例子1:
5
思路:采用动态规划
#include <iostream>
#include<algorithm>
#include <vector>
#include <cmath>
using namespace std;
vector<int> getStep(int x);
int main(){
int N, M;
cin >> N >> M;
vector<int> path(M + 1, M);
for (int i = 0; i<N + 1; i++){
path[i] = 0;
}
for (int i = N; i<M + 1; i++){
vector<int> steps = getStep(i);
for (int j = 0; j<steps.size(); j++){
if (i + steps[j]<M + 1)
path[i + steps[j]] = min(path[i] + 1, path[i + steps[j]]);
}
}
if (path[M] == M)
cout << -1;
else
cout << path[M];
return 0;
}
//计算因数的快速方法
vector<int> getStep(int x){
vector<int> result;
for (int i = 2; i <= sqrt(x); i++){
if (x%i == 0){
result.push_back(i);
if (x / i != i)
result.push_back(x / i);
}
}
return result;
}
2
(动态规划)
一个只包含’A’、’B’和’C’的字符串,如果存在某一段长度为3的连续子串中恰好’A’、’B’和’C’各有一个,那么这个字符串就是纯净的,否则这个字符串就是暗黑的。例如:
BAACAACCBAAA 连续子串”CBA”中包含了’A’,’B’,’C’各一个,所以是纯净的字符串
AABBCCAABB 不存在一个长度为3的连续子串包含’A’,’B’,’C’,所以是暗黑的字符串
你的任务就是计算出长度为n的字符串(只包含’A’、’B’和’C’),有多少个是暗黑的字符串。
输入描述:
输入一个整数n,表示字符串长度(1 ≤ n ≤ 30)
输出描述:
输出一个整数表示有多少个暗黑字符串
输入例子1:
2
3
输出例子1:
9
21
解析:
假设现在字符串中有i个字符,并且满足条件,那么第i+1个字符的选取情况与第i-1和第i个字符的状态(用S表示)有关:若S=’AA’,那么第i+1个字符可以选取’A’,’B’,’C’中任意一个;若S=‘AB’,那么第i+1个字符只能取’A’或’B’,如果选取’C’,就会产生纯净的字符串;若S=’AC’,那么第i+1个字符只能取’A’或’C’。由题意可知S有9种状态,其他6中状态的转移情况同理。这样,我们就可以在状态转移过程中统计暗黑的字符串的个数。此处有些类似于动态规划,不过动态规划是在每一步状态转移的时候选取最优方案,从而使最终方案最优;而此处是在状态转移过程中避免产生纯净字符串,同时统计暗黑的字符串的个数。
状态转移方程:
dp[i+1][0]=dp[i][0]+dp[i][3]+dp[i][6]
dp[i+1][1]=dp[i][0]+dp[i][3]
dp[i+1][2]=dp[i][0]+dp[i][6]
dp[i+1][3]=dp[i][1]+dp[i][4]
dp[i+1][4]=dp[i][1]+dp[i][4]+dp[i][7]
dp[i+1][5]=dp[i][4]+dp[i][7]
dp[i+1][6]=dp[i][2]+dp[i][8]
dp[i+1][7]=dp[i][5]+dp[i][8]
dp[i+1][8]=dp[i][2]+dp[i][5]+dp[i][8]
其中dp数组的第一维表示字符串长度,第二维的9个值分别表示字符串最后两个字符的9中状态,对应关系如下:
0->AA 1->AB 2->AC 3->BA 4->BB 5->BC 6->CA 7->CB 8->CC
那么dp[i][0]就表示长度为i并且最后两位字符为AA的暗黑的字符串的个数,其他同理。
上述状态转移方程中第一个式子表达的意思就是:
长度为i+1并且最后两个字符为AA的字符串来源有三类:长度为i并且最后两个字符为AA或BA或CA的字符串,添加字符A即可;其它式子同理;
源代码:
#include <iostream>
#include <vector>
using namespace std;
long long Cal(int n)
{
if (n == 1) return 3;
else if (n == 2) return 9;
vector<long long> Record(9, 1), temp;
for (int i = 3; i <= n; i++)
{
temp = Record;
Record[0] = temp[0] + temp[3] + temp[6];
Record[1] = temp[0] + temp[3];
Record[2] = temp[0] + temp[6];
Record[3] = temp[1] + temp[4];
Record[4] = temp[1] + temp[4] + temp[7];
Record[5] = temp[4] + temp[7];
Record[6] = temp[2] + temp[8];
Record[7] = temp[5] + temp[8];
Record[8] = temp[2] + temp[5] + temp[8];
}
long long ans = 0;
for (int i = 0; i < 9; i++) ans += Record[i];
return ans;
}
int main()
{
int n;
cin >> n;
cout << Cal(n) << endl;
return 0;
}
上述是我的解题思路,接下来的部分是我参考了别人的解题思路,加上自己的理解。
上述解题思路最大的问题是状态转移方程太过复杂,究其原因,是因为对状态划分的太过细致。分析可知,我们可以将状态分为两种:一种是字符串的最后两个字符相同(用S(n)表示),另一种是最后两个字符不同(用D(n)表示)。这样划分使状态转移更加抽象,但好处是状态转移方程更加精简,处理起来更加方便。
两种状态的转移情况如下:S(i)状态添加的字符与最后两字符相等时,转化为S(i+1)状态,否则转化为D(i+1)状态;D(i)状态添加的字符与其最后字符相等时,转化为S(i)状态,与其倒数第二个字符相等时,转化为D(i+1)状态。状态转移方程如下:
S(n+1)=S(n)+D(n)
D(n+1)=2*S(n)+D(n)
源代码:
#include <iostream>
using namespace std;
long long Cal(int n) //注意这里要返回long long,当n值较多的时候,int会溢出
{
if (n == 1) return 3;
else if (n == 2) return 9;
long long S = 3, D = 6, s, d;
for (int k = 3; k <= n; k++)
{
s = S, d = D;
S = s + d;
D = 2 * s + d;
}
return S + D;
}
int main()
{
int n;
cin >> n;
cout << Cal(n) << endl;
return 0;
}
接下来,我们可以通过上述状态转移方程式来推出该问题的递推式。我们知道,递推式描述一个问题的时候,非常精简清晰,但递推式并不是凭空产生的,一些简单问题递推式可以直接归纳出来,但一些复杂点的问题,可以通过状态转移方程来推到出来。这种思维模式在算法中很常见。
我们用F(n)表示长度为n的暗黑字符串的个数,则:
F(n)=S(n)+D(n)
=3*S(n-1)+2*D(n-1)
=2*F(n-1)+S(n-1)
=2*F(n-1)+S(n-2)+D(n-2)
=2*F(n-1)+F(n-2)
故得到递推式F(n)=2*F(n-1)+F(n-2)。
3
对于一个整数X,定义操作rev(X)为将X按数位翻转过来,并且去除掉前导0。例如:
如果 X = 123,则rev(X) = 321;
如果 X = 100,则rev(X) = 1.
现在给出整数x和y,要求rev(rev(x) + rev(y))为多少?
输入描述:
输入为一行,x、y(1 ≤ x、y ≤ 1000),以空格隔开。
输出描述:
输出rev(rev(x) + rev(y))的值
输入例子1:
123 100
输出例子1:
223
#include <iostream>
#include <string>
using namespace std;
int rev(int x);
int main(){
int x,y;
cin>>x>>y;
cout<<rev(rev(x)+rev(y))<<endl;
return 0;
}
int rev(int x){
string s_x = to_string(x);
for(int i = s_x.size()-1;i>-1;i--){
if(s_x[i] == '0')
s_x.pop_back();
else
break;
}
for(int i = 0,j = s_x.size()-1;i<j;i++,j--){
swap(s_x[i],s_x[j]);
}
int result = stoi(s_x);
return result;
}
4
小易是一个数论爱好者,并且对于一个数的奇数约数十分感兴趣。一天小易遇到这样一个问题: 定义函数f(x)为x最大的奇数约数,x为正整数。 例如:f(44) = 11.
现在给出一个N,需要求出 f(1) + f(2) + f(3)…….f(N)
例如: N = 7
f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) = 1 + 1 + 3 + 1 + 5 + 3 + 7 = 21
小易计算这个问题遇到了困难,需要你来设计一个算法帮助他。
输入描述:
输入一个整数N (1 ≤ N ≤ 1000000000)
输出描述:
输出一个整数,即为f(1) + f(2) + f(3)…….f(N)
输入例子1:
7
输出例子1:
21
第一次提交的代码因为时间复杂度过高通过了60%,思路是直接判断一个数如果是奇数那么f(x) = x,如果一个数是偶数,就一直除以2,直到为奇数,即为f(x)。依次相加,代码如下:
#include <iostream>
using namespace std;
long long func(long long i);
int main(){
long long n,result = 0;
cin>>n;
for(long long i = 1;i<=n;i++){
result += func(i);
}
cout<<result<<endl;
return 0;
}
long long func(long long i){
if((i&1) == 1)
return i;
while(i>>1){
i = i>>1;
if((i&1) == 1)
break;
}
return i;
}
第二次提交的全部通过,思路如下,把偶数和奇数分开处理,每次返回奇数的和,偶数除以2,重复返回奇数的和,使用等差数列的求和公式。
#include <iostream>
using namespace std;
long long func(long long i);
int main(){
long long n,result = 0;
cin>>n;
result = result+func(n);
while((n>>1) != 0){
n = n>>1;
result = result + func(n);
}
cout<<result<<endl;
return 0;
}
long long func(long long i){
if(i == 0)
return 0;
long long result = 0;
if((i&1) ==1)
result = (i+1)*(i+1)/4;
else
result = i*i/4;
return result;
}
5
小易去附近的商店买苹果,奸诈的商贩使用了捆绑交易,只提供6个每袋和8个每袋的包装(包装不可拆分)。 可是小易现在只想购买恰好n个苹果,小易想购买尽量少的袋数方便携带。如果不能购买恰好n个苹果,小易将不会购买。
输入描述:
输入一个整数n,表示小易想购买n(1 ≤ n ≤ 100)个苹果
输出描述:
输出一个整数表示最少需要购买的袋数,如果不能买恰好n个苹果则输出-1
输入例子1:
20
输出例子1:
3
#include <iostream>
#include <vector>
using namespace std;
int main(){
int n;
cin>>n;
vector<int> bagNum(n+1,-1);
bagNum[6] = bagNum[8] = 1;
for(int i = 9;i<=n;i++){
if(bagNum[i-6] == -1&&bagNum[i-8] == -1)
bagNum[i] = -1;
else if(bagNum[i-6] == -1)
bagNum[i] = bagNum[i-8]+1;
else if(bagNum[i-8] == -1)
bagNum[i] = bagNum[i-6]+1;
else
bagNum[i] = min(bagNum[i-6],bagNum[i-8])+1;
}
cout<<bagNum[n]<<endl;
return 0;
}
2018年网易内推笔试编程题
1
小易有一些彩色的砖块。每种颜色由一个大写字母表示。各个颜色砖块看起来都完全一样。现在有一个给定的字符串s,s中每个字符代表小易的某个砖块的颜色。小易想把他所有的砖块排成一行。如果最多存在一对不同颜色的相邻砖块,那么这行砖块就很漂亮的。请你帮助小易计算有多少种方式将他所有砖块排成漂亮的一行。(如果两种方式所对应的砖块颜色序列是相同的,那么认为这两种方式是一样的。)
例如: s = “ABAB”,那么小易有六种排列的结果:
“AABB”,”ABAB”,”ABBA”,”BAAB”,”BABA”,”BBAA”
其中只有”AABB”和”BBAA”满足最多只有一对不同颜色的相邻砖块。
输入描述:
输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s中的每一个字符都为一个大写字母(A到Z)。
输出描述:
输出一个整数,表示小易可以有多少种方式。
输入例子1:
ABAB
输出例子1:
2
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
int num =0;
cin>>str;
int exist[26] = {0};
for(int i = 0;i<str.size();i++){
if(exist[str[i] - 'A'] == 0)
exist[str[i] - 'A'] =1;
}
for(int i = 0;i<26;i++){
num = num+exist[i];
}
if(num == 1)
cout<<1<<endl;
else if(num == 2)
cout<<2<<endl;
else
cout<<0<<endl;
return 0;
}
2
如果一个01串任意两个相邻位置的字符都是不一样的,我们就叫这个01串为交错01串。例如: “1”,”10101”,”0101010”都是交错01串。
小易现在有一个01串s,小易想找出一个最长的连续子串,并且这个子串是一个交错01串。小易需要你帮帮忙求出最长的这样的子串的长度是多少。
输入描述:
输入包括字符串s,s的长度length(1 ≤ length ≤ 50),字符串中只包含’0’和’1’
输出描述:
输出一个整数,表示最长的满足要求的子串长度。
输入例子1:
111101111
输出例子1:
3
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main(){
string str;
int num = 1, maxNum = 1;//最少为1,当全部相等的时候
bool flag = false;
cin >> str;
char cur = str[0];
for (int i = 1; i<str.size(); i++){
if (str[i] == cur&&flag == false){
continue;
}
else if (flag != false && str[i] != cur){
num++;
cur = str[i];
}
else if (str[i] != cur&&flag == false){
num = 2;
flag = true;
cur = str[i];
}
else if (str[i] == cur&&flag != false){
flag = false;
maxNum = max(maxNum, num);
}
}
maxNum = max(maxNum, num);
cout << maxNum << endl;
return 0;
}
3
小易将n个棋子摆放在一张无限大的棋盘上。第i个棋子放在第x[i]行y[i]列。同一个格子允许放置多个棋子。每一次操作小易可以把一个棋子拿起并将其移动到原格子的上、下、左、右的任意一个格子中。小易想知道要让棋盘上出现有一个格子中至少有i(1 ≤ i ≤ n)个棋子所需要的最少操作次数.
输入描述:
输入包括三行,第一行一个整数n(1 ≤ n ≤ 50),表示棋子的个数
第二行为n个棋子的横坐标x[i](1 ≤ x[i] ≤ 10^9)
第三行为n个棋子的纵坐标y[i](1 ≤ y[i] ≤ 10^9)
输出描述:
输出n个整数,第i个表示棋盘上有一个格子至少有i个棋子所需要的操作数,以空格分割。行末无空格
如样例所示:
对于1个棋子: 不需要操作
对于2个棋子: 将前两个棋子放在(1, 1)中
对于3个棋子: 将前三个棋子放在(2, 1)中
对于4个棋子: 将所有棋子都放在(3, 1)中
输入例子1:
4
1 2 4 9
1 1 1 1
输出例子1:
0 1 3 10
#include <iostream>
#include <vector>
#include <climits>
#include <set>
#include <algorithm>
using namespace std;
long optionNum(vector<vector<long>> distance, int pointNum);
int main(){
int num;
long xInput, yInput;
vector<long> x;
vector<long> y;
vector<long> stepNum;
vector<vector<long>> distance;
cin >> num;
for (int i = 0; i<num; i++){
cin >> xInput;
x.push_back(xInput);
}
for (int i = 0; i<num; i++){
cin >> yInput;
y.push_back(yInput);
}
//去除vector中的重复元素
vector<long> Xset = x;
vector<long> Yset = y;
sort(Xset.begin(),Xset.end());
sort(Yset.begin(),Yset.end());
Xset.erase(unique(Xset.begin(),Xset.end()),Xset.end());
Yset.erase(unique(Yset.begin(),Yset.end()),Yset.end());
vector<long>::iterator iterX,iterY;
for (iterX = Xset.begin();iterX<Xset.end();iterX++){
for(iterY = Yset.begin();iterY<Yset.end();iterY++){
vector<long> distanceElement;
for (int j = 0; j<num; j++){
long temp = abs(x[j] - *iterX) + abs(y[j] - *iterY);
distanceElement.push_back(temp);
}
sort(distanceElement.begin(),distanceElement.end());
distance.push_back(distanceElement);
}
}
for (int i = 1; i <= num; i++){
stepNum.push_back(optionNum(distance, i));
}
cout << stepNum[0];
for (int i = 1; i<num; i++){
cout << " " << stepNum[i];
}
return 0;
}
long optionNum(vector<vector<long>> distance, int pointNum){
long result = LONG_MAX;
if (pointNum == 1) return 0;
else{
for (int i = 0; i<distance.size(); i++){
long cur = 0;
for (int j = 0; j<pointNum; j++)
cur = cur + distance[i][j];
result = min(result, cur);
}
}
return result;
}
4
小易老师是非常严厉的,它会要求所有学生在进入教室前都排成一列,并且他要求学生按照身高不递减的顺序排列。有一次,n个学生在列队的时候,小易老师正好去卫生间了。学生们终于有机会反击了,于是学生们决定来一次疯狂的队列,他们定义一个队列的疯狂值为每对相邻排列学生身高差的绝对值总和。由于按照身高顺序排列的队列的疯狂值是最小的,他们当然决定按照疯狂值最大的顺序来进行列队。现在给出n个学生的身高,请计算出这些学生列队的最大可能的疯狂值。小易老师回来一定会气得半死。
输入描述:
输入包括两行,第一行一个整数n(1 ≤ n ≤ 50),表示学生的人数
第二行为n个整数h[i](1 ≤ h[i] ≤ 1000),表示每个学生的身高
输出描述:
输出一个整数,表示n个学生列队可以获得的最大的疯狂值。
如样例所示:
当队列排列顺序是: 25-10-40-5-25, 身高差绝对值的总和为15+30+35+20=100。
这是最大的疯狂值了。
输入例子1:
5
5 10 25 40 25
输出例子1:
100
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int num,result=0,flag = -1,i,j;
vector<int> student;
cin>>num;
for(int i = 0;i<num;i++){
int input;
cin>>input;
student.push_back(input);
}
sort(student.begin(),student.end());
for(i=0,j=num-1;i<num&&j>-1&&j>=i;j--,i++){
if(flag == -1){
result += student[j]-student[i];
flag = 0;
continue;
}
if(i == j){
result += max(student[i]-student[i-1],student[i+1]-student[i]);
break;
}
result += student[j]-student[i-1];
result += student[j+1]-student[i];
}
cout<<result;
return 0;
}
5
(动态规划)
小易非常喜欢拥有以下性质的数列:
1、数列的长度为n
2、数列中的每个数都在1到k之间(包括1和k)
3、对于位置相邻的两个数A和B(A在B前),都满足(A <= B)或(A mod B != 0)(满足其一即可)
例如,当n = 4, k = 7
那么{1,7,7,2},它的长度是4,所有数字也在1到7范围内,并且满足第三条性质,所以小易是喜欢这个数列的
但是小易不喜欢{4,4,4,2}这个数列。小易给出n和k,希望你能帮他求出有多少个是他会喜欢的数列。
输入描述:
输入包括两个整数n和k(1 ≤ n ≤ 10, 1 ≤ k ≤ 10^5)
输出描述:
输出一个整数,即满足要求的数列个数,因为答案可能很大,输出对1,000,000,007取模的结果。
输入例子1:
2 2
输出例子1:
3
动态规划,这个版本超过内存限制,通过40%
#include <iostream>
#include <vector>
using namespace std;
int mod = 100000007;
int main(){
int n, k, result = 0;
cin >> n >> k;
vector<vector<int> > state(n, vector<int>(k, 0));
for (int i = 0; i<k; i++)
state[0][i] = 1;
vector<vector<int> > valid;
//如果不在这里进行判断,而在后面每次进行判断,三层循环的话,超过时间限制通过40%
for (int i = 0; i < k; i++){
vector<int> validElement;
for (int j = 0; j < k; j++){
if (j <= i || (j + 1) % (i + 1) != 0)
validElement.push_back(j);
}
valid.push_back(validElement);
}
for (int i = 1; i<n; i++){
for (int j = 0; j<k; j++){
for (int select = 0; select<valid[j].size(); select++){
state[i][j] = (state[i][j] + state[i - 1][valid[j][select]])%mod;
}
}
}
for (int j = 0; j<k; j++){
result = (result + state[n - 1][j])%mod;
}
cout << result;
cin.get();
cin.get();
return 0;
}
下面这种方法可以通过,先计算总和,然后去除不合法的
#include <iostream>
#include <vector>
using namespace std;
int mod = 1000000007;
int main(){
int n, k, result = 0;
cin >> n >> k;
vector<vector<int> > state(n, vector<int>(k, 0));
for (int i = 0; i<k; i++)
state[0][i] = 1;
for (int i = 1; i<n; i++){
int sum = 0;
for (int j = 0; j<k; j++){
sum = (sum + state[i - 1][j]) % mod;
}
for (int j = 0; j < k; j++){
int invalid = 0;
if (j!=k-1)
for (int inj = j + 1 + j + 1; inj < k + 1; inj = inj + j + 1){
invalid = (invalid + state[i - 1][inj - 1]) % mod;
}
state[i][j] = (sum - invalid) % mod;
}
}
for (int j = 0; j<k; j++){
result = (result + state[n - 1][j])%mod;
}
cout << result;
cin.get();
cin.get();
return 0;
}
2018阿里巴巴
(动态规划)
小猴子下山,沿着下山的路有一排桃树,每棵树都结了一些桃子。小猴子想摘桃子,但是有一些条件需要遵守,小猴子只能沿着下山的方向走,不能回头,每颗树最多摘一个,而且一旦摘了一棵树的桃子,就不能再摘比这棵树结的桃子少的树上的桃子。那么小猴子最多能摘到几颗桃子呢?
举例说明,比如有5棵树,分别结了10,4,5,12,8颗桃子,那么小猴子最多能摘3颗桃子,来自于结了4,5,8颗桃子的桃树。