网易笔试编程题2
数列还原
牛牛的作业薄上有一个长度为 n 的排列 A,这个排列包含了从1到n的n个数,但是因为一些原因,其中有一些位置(不超过 10 个)看不清了,但是牛牛记得这个数列顺序对的数量是 k,顺序对是指满足 i < j 且 A[i] < A[j] 的对数,请帮助牛牛计算出,符合这个要求的合法排列的数目。
输入描述:
每个输入包含一个测试用例。每个测试用例的第一行包含两个整数 n 和 k(1 <= n <= 100, 0 <= k <= 1000000000),接下来的 1 行,包含 n 个数字表示排列 A,其中等于0的项表示看不清的位置(不超过 10 个)。
输出描述:
输出一行表示合法的排列数目。
输入例子:
5 5
4 0 0 2 0
输出例子:
2
思路:
先统计哪些数没出现,哪些位置缺少数值。
然后考虑计算。
10!=3628800
如果是每种排列还暴力计算(~100*100),这不行,太慢了
考虑预先计算一部分。
总顺序对数=已经填进去的数之间的顺序对数+没有填进去的数之间的顺序对数+已经填进去和没有填进去的数之间的顺序对数
第一部分:预先算一遍就好了
第二部分:只有10*10,暴力计算
第三部分:预先处理,每个数填在空位上,和其他预先填进去的数组成的顺序对数
三部分相加,判断等不等于k就行。
计算量?
10*10+10,也就110
2:
首先,顺序对的个数互不影响。也就是说,对于数组A来说,增加(插入)一个数字,其A的顺序对个数不变,所以新数组A+1的顺序对个数=数组A的顺序对+新插入的数字产生的顺序对.
进而推广到,增加c个数字,新数组A+c的顺序对=数组A的顺序对+数组c的顺序对+每个新插入的数字产生顺序对(共c个数字)。
所以,1)首先计算已经给出的数字共有arrbase个顺序对;
2)然后计算缺失的数字共有canbase个顺序对;
3)最后计算每个缺失的数字产生的顺序对;
对于2,3步骤,可以将缺失的数字(数组)进行全排列。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class Main{
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int RES = 0;
int n = sc.nextInt();
int k = sc.nextInt();
int[] A = new int[n];
boolean[] flag = new boolean[n+1];
//flag标记哪些数字已经存在
for(int i=0;i<n;i++){
A[i] = sc.nextInt();
if(A[i] != 0){
flag[A[i]] = true;
}
}
//统计排列中不存在的数字
ArrayList<Integer> list = new ArrayList<Integer>();
for(int i=1;i<=n;i++){
if(flag[i] == false)
list.add(i);
}
//perm用来存模糊数字的全排列
List<ArrayList<Integer>> perm = new ArrayList<ArrayList<Integer>>();
//计算perm
calperm(perm, list, 0);
//统计已有的排列的顺序对
int cv = 0;
for(int i=0;i<n;i++){
if(A[i]!= 0){
for(int j=i+1;j<n;j++){
if(A[j] != 0 && A[i] < A[j])
cv++;
}
}
}
//计算每个模糊数字排列的顺序对,如果与k相等,则结果RES加一
for(ArrayList<Integer> tmp : perm){
int val = cv;
int[] tmpA = Arrays.copyOf(A, n);
val += calvalue(tmp, tmpA);
if(val == k)
RES++;
}
System.out.println(RES);
}
}
//计算排列的顺序对
public static int calvalue(List<Integer> list, int[] A){
int val = 0;
int j = 0;
for(int i=0;i<A.length;i++){
if(A[i] == 0){
A[i] = list.get(j++);
for(int k = 0;k<i;k++){
if(A[k]!=0 && A[k]<A[i])
val++;
}
for(int k=i+1;k<A.length;k++){
if(A[k]!=0 && A[k]>A[i])
val++;
}
}
}
return val;
}
//计算全排列
public static void calperm(List<ArrayList<Integer>> perm , ArrayList<Integer> list, int n){
if(n == list.size()){
perm.add(new ArrayList<Integer>(list));
}else{
for(int i=n;i<list.size();i++){
Collections.swap(list, i, n);
calperm(perm, list, n+1);
Collections.swap(list, i, n);
}
}
}
}
小易老师是非常严厉的,它会要求所有学生在进入教室前都排成一列,并且他要求学生按照身高不递减的顺序排列。有一次,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,2,3,4,5,6,7。我们先取最大的元素7,要绝对值尽可能的大,第二个元素我们取1,这样我们得到了[1,7],
接下来,我们从剩下的元素中取最大值,排放在1的旁边,取最小值排放在7的旁边,这样我们得到了[6,1,7,2]
继续上述过程,从剩下的元素中取最大值放在两侧较小元素的旁边,取最小值放在较大元素的旁边
……
直到最后,我们每次取2个元素,剩余的元素个数可能为0,也可能为1.如果还剩下1个元素,比较一下放在左边和右边的差值绝对值,那边大放哪边就好。
这样一个过程就可以使得一个队列的疯狂值达到最大了。
下面我们使用代码实现这一过程:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
while(in.hasNext()){
int n = in.nextInt();
in.nextLine();
int[] num = new int[n];
for(int i=0;i<n;i++){
num[i] = in.nextInt();
}
Arrays.sort(num); //排序
if(n==0 || n==1)
System.out.println(0);
if(n == 2)
System.out.println(num[1]-num[0]);
boolean b = false;
if(n>2) b = true;
int start = 1;
int end = n-2;
int max = num[n-1];
int min = num[0];
int abs = num[n-1]-num[0];
while(start <= end){
if(start == end){ //最后剩下1个元素的情况
abs += Math.max(Math.abs(num[start]-min),Math.abs(num[start]-max));
break;
}
int maxt = num[end];
int mint = num[start];
abs += Math.abs(min-maxt);
abs += Math.abs(max-mint);
min = mint;
max = maxt;
start++;
end--;
}
if(b)
System.out.println(abs);
}
}
}
洗牌问题
题目描述:
洗牌在生活中十分常见,现在需要写一个程序模拟洗牌的过程。 现在需要洗2n张牌,从上到下依次是第1张,第2张,第3张一直到第2n张。首先,我们把这2n张牌分成两堆,左手拿着第1张到第n张(上半堆),右手拿着第n+1张到第2n张(下半堆)。接着就开始洗牌的过程,先放下右手的最后一张牌,再放下左手的最后一张牌,接着放下右手的倒数第二张牌,再放下左手的倒数第二张牌,直到最后放下左手的第一张牌。接着把牌合并起来就可以了。 例如有6张牌,最开始牌的序列是1,2,3,4,5,6。首先分成两组,左手拿着1,2,3;右手拿着4,5,6。在洗牌过程中按顺序放下了6,3,5,2,4,1。把这六张牌再次合成一组牌之后,我们按照从上往下的顺序看这组牌,就变成了序列1,4,2,5,3,6。 现在给出一个原始牌组,请输出这副牌洗牌k次之后从上往下的序列。
输入描述:
第一行一个数T(T ≤ 100),表示数据组数。对于每组数据,第一行两个数n,k(1 ≤ n,k ≤ 100),
接下来一行有2n个数a1,a2,...,a2n(1 ≤ ai ≤ 1000000000)。表示原始牌组从上到下的序列。
- 1
- 2
- 3
输出描述:
对于每组数据,输出一行,最终的序列。数字之间用空格隔开,不要在行末输出多余的空格。
- 1
- 2
输入例子:
3
3 1
1 2 3 4 5 6
3 2
1 2 3 4 5 6
2 2
1 1 1 1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
输出例子:
1 4 2 5 3 6
1 5 4 3 2 6
1 1 1 1
- 1
- 2
- 3
- 4
题目分析:
我们主要需要执行的操作就是洗牌这个过程,将洗完的牌返回以供继续洗牌,所以我们可以构造一个洗牌函数,根据传进来的参数给定其洗多少次,返回最后的那副牌即可。洗牌函数较简单,所以这道题目不难。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> sort(vector<int>& res);
int main() {
int time;
int n,k;
cin>>time;
while(time){
cin>>n>>k;
vector<int> res(2*n);
for(unsigned int i = 0;i < 2*n;++i){
cin >> res[i];
}
while(k){
res = sort(res);
k--;
}
for(int i = 0;i < res.size();i++){
if(i != res.size()-1)
cout<<res[i]<<" ";
else
cout<<res[i];
}
cout<<endl;
time--;
}
return 0;
}
vector<int> sort(vector<int>& res){
int size = res.size();
int i = size/2;
int j = 1;
vector<int> r;
while(i){
r.push_back(res[size - j]);
r.push_back(res[(size/2) - j]);
j++;
i--;
}
for(int i = 0;i < (r.size()/2);i++){
swap(r[i],r[r.size()-i-1]);
}
return r;
}
2. 构造队列
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Problem Description:
小明同学把1到n这n个数字按照一定的顺序放入了一个队列Q中。现在他对队列Q执行了如下程序:
while(!Q.empty()) //队列不空,执行循环
{
int x=Q.front(); //取出当前队头的值x
Q.pop(); //弹出当前队头
Q.push(x); //把x放入队尾
x=Q.front(); //取出这时候队头的值
printf("%d\n",x); //输出x
Q.pop(); //弹出这时候的队头
}
做取出队头的值操作的时候,并不弹出当前队头。
小明同学发现,这段程序恰好按顺序输出了1,2,3,...,n。现在小明想让你构造出原始的队列,你能做到吗?
输入
第一行一个整数T(T<=100)表示数据组数,每组数据输入一个数n(1<=n<=100000),输入的所有n之和不超过200000。
输出
对于每组数据,输出一行,表示原始的队列。数字之间用一个空格隔开,不要在行末输出多余的空格。
样例输入
4
1
2
3
10
样例输出
1
2 1
2 1 3
8 1 6 2 10 3 7 4 9 5
解析:
本题循环中所做操作为1)把队头元素放入队尾;2)取出当前的队头元素,打印其值,并弹出队列。我们现在知道操作后的输出结果,想得到原队列,只需要进行逆序的操作即可。
1)将打印的元素插入到队列中(注意是要插入到队头)
2)把队尾元素移至队头
值得注意的是,打印的是1~n,最后打印的元素为n,因此最先插入的应该为n
例子:1 2 3 -》(3先插入队头) 3 -》(2插入队头) 2 3 ->(队尾转到队头) 3 2 -》(1插入队头)1 3 2->(队尾转入队头) 2 1 3
#include <iostream>
#include <deque>
using namespace std;
int main()
{
int T;//组数
cin>>T;
int n;//每组数据对应的n
while(T>0)
{
cin>>n;
deque<int> Q;
while(n>0)
{
Q.push_front(n);//插入队头
int x=Q.back();//取队尾
Q.pop_back();//弹出队尾
Q.push_front(x);//队尾插入队头
n--;
}
for(int i=0;i<Q.size()-1;i++)
cout<<Q[i]<<" ";
cout<<Q[Q.size()-1]<<endl;//最后一个元素后无空格而且换行
T--;
}
return 0;
}
java的代码:
import java.util.Deque;
import java.util.LinkedList;
import java.util.Scanner;
public class Main {
public static void main(String [] args){
Scanner in = new Scanner(System.in);
int T = in.nextInt();
while(T > 0) {
int n = in.nextInt();
Deque<Integer> deque = new LinkedList<Integer>();
while(n != 0){
deque.addFirst(n);
Integer aInteger = deque.pollLast();
deque.addFirst(aInteger);
n--;
}
System.out.print(deque.pollFirst());
while(deque.size() != 0) {
System.out.print(" " + deque.pollFirst());
}
System.out.println();
T--;
}
}
}
跳石板(动态规划)
小易来到了一条石板路前,每块石板上从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
输入例子:
4 24
输出例子:
5
解析:动态dp
#include <iostream>
using namespace std;
int ans[100001];
int main()
{
int n, m;
cin >> n >> m;
if (n == m)
{
cout << 0;
return 0;
}
for (int i = n+1; i <= m; i++)ans[i] = 100000;
ans[n] = 0;
for (int i = n; i < m; i++) for (int j = 2; j*j <= i; j++)
{
if (i%j)continue;
if (i + j <= m && ans[i + j]>ans[i] + 1)ans[i + j] = ans[i] + 1;
if (i + i / j <= m && ans[i + i / j]>ans[i] + 1)ans[i + i / j] = ans[i] + 1;
}
if (ans[m] == 100000)ans[m] = -1;
cout << ans[m];
return 0;
}
回文序列(贪心法)
如果一个数字序列逆置之后跟原序列是一样的就称这样的数字序列为回文序列。例如:
{1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列,
{1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。
现在给出一个数字序列,允许使用一种转换操作:
选择任意两个相邻的数,然后从序列移除这两个数,并用这两个数字的和插入到这两个数之前的位置(只插入一个和)。
现在对于所给序列要求出最少需要多少次操作可以将其变成回文序列。
输入描述:
输入为两行,第一行为序列长度n ( 1 ≤ n ≤ 50) 第二行为序列中的n个整数item[i] (1 ≤ iteam[i] ≤ 1000),以空格分隔。
输出描述:
输出一个数,表示最少需要的转换次数
输入例子:
4 1 1 1 3
输出例子:
2 思路:从两边向中间开始分情况计算
#include <iostream>
using namespace std;
int main()
{
int n, list[50], ans = 0, suml = 0, sumr = 0;
cin >> n;
for (int i = 0; i < n; i++)cin >> list[i];
int i = -1, j = n;
while (i<j)
{
if (suml == sumr)
{
i++, j--;
suml = list[i], sumr = list[j];
}
else if (suml < sumr)
{
i++, ans++;
suml += list[i];
}
else
{
j--, ans++;
sumr += list[j];
}
}
cout << ans;
return 0;
}
安置路灯
题目:
小Q正在给一条长度为
n
的道路设计路灯安置方案。
为了让问题更简单,小Q把道路视为n
个方格,需要照亮的地方用'.'
表示, 不需要照亮的障碍物格子用'X'
表示。
小Q现在要在道路上设置一些路灯, 对于安置在pos
位置的路灯, 这盏路灯可以照亮pos - 1, pos, pos + 1
这三个位置。
小Q希望能安置尽量少的路灯照亮所有'.'
区域, 希望你能帮他计算一下最少需要多少盏路灯。
输入描述:
输入的第一行包含一个正整数
t
[Math Processing Error](1≤t≤1000), 表示测试用例数
接下来每两行一个测试数据, 第一行一个正整数n
[Math Processing Error](1≤n≤1000),表示道路的长度。
第二行一个字符串s
表示道路的构造,只包含'.'
和'X'
。
输出描述:
对于每个测试用例, 输出一个正整数表示最少需要多少盏路灯。
样例:
in: 2 3 .X. 11 ...XX....XX out: 1 3
解析:没有提交不知道对错,我的想法是判断如果第i位置的符号是 . 时,判断它的剩余长度是否小于等于3,直接加一返回结果,不然就在三个数的中间插上灯,i+2,再继续遍历判断。
import java.util.Scanner;
public class Main {
public static void main(String [] args){
Scanner in = new Scanner(System.in);
int t = in.nextInt();
while(t > 0) {
int n = in.nextInt();
in.nextLine();
String string = in.nextLine();
int len = string.length();
int cnt = 0;
for(int i = 0; i < len; i++) {
if(string.charAt(i) == '.'){
if(len - i <= 2){
cnt++;
break;
}
i = i+2;
cnt++;
}else {
continue;
}
}
System.out.println(cnt);
t--;
}
}
}
迷路的牛牛
题目:
牛牛去犇犇老师家补课,出门的时候面向北方,但是现在他迷路了。虽然他手里有一张地图,但是他需要知道自己面向哪个方向,请你帮帮他。
输入描述:
每个输入包含一个测试用例。
每个测试用例的第一行包含一个正整数,表示转方向的次数N
[Math Processing Error](N≤1000)。
接下来的一行包含一个长度为N
的字符串,由L
和R
组成,L
表示向左转,R
表示向右转。
输出描述:
输出牛牛最后面向的方向,
N
表示北,S
表示南,E
表示东,W
表示西。
样例:
in: 3 LRR out: E
解析:
如图:
解析:把NESW
用数字0123
表示,向左走就减一,向右走就加一,由于在处理的过程中ans
可能为负数,因此对ans
加4
再模4
。
#include <bits/stdc++.h>
using namespace std;
int main()
{
const char DIR[] = "NESW";
for (int n; cin >> n; ) {
string str;
cin >> str;
int ans = 0;
for (int i = 0; i < n; i++)
ans = (ans + (str[i] == 'L' ? -1 : 1) + 4) % 4;
cout << DIR[ans % 4] << endl;
}
return 0;
}