小v最近在玩一款挖矿的游戏,该游戏介绍如下:
1、每次可以挖到多个矿石,每个矿石的重量都不一样,挖矿结束后需要通过一款平衡矿车运送下山;
2、平衡矿车有左右2个车厢,中间只有1个车轮沿着导轨滑到山下,且矿车只有在2个车厢重量完全相等且矿石数量相差不超过1个的情况下才能成功运送矿石,否则在转弯时可能出现侧翻。
假设小v挖到了n(n<100)个矿石,每个矿石重量不超过100,为了确保一次性将n个矿石都运送出去,一旦矿车的车厢重量不一样就需要购买配重砝码。请问小v每次最少需要购买多少重量的砝码呢? (假设车厢足够放下这些矿石和砝码,砝码重量任选)
输入描述:
输入n个正整数表示每个矿石的重量
输出描述:
输出一个正整数表示最少需要购买的砝码重量
输入例子1:
3 7 4 11 8 10
输出例子1:
1
例子说明1:
小v可以将重量3,7和11的矿石放到左车厢,重量4,8和10 放到右车厢,然后购买重量为1的砝码放到左车厢
一开始毫无头绪,想的都是往两个栈里进出,不得要领。
后来想到假设两个车厢都是满的,不做加减,只做交换,就容易了很多。
小车厢的容量是固定的,就是矿石数量一半下取整。只要小车厢的载重逼近半重,大小车厢的载重差就越小,买的砝码就少。可以将数组分成两个大小车厢,通过循环历遍一节车厢的每一块石头,比较另一个车厢中是否有可以替换的矿石,使该车厢载重最逼近标准线(半重)。
但在如何出这个历遍的时候,出现了麻烦。
后来想到用一个标记量记录连续未替换的次数,如果连续一轮都没有替换的,说明已是最优情况,可以跳出导出结果。
private static int solution(int[] input) {
//准备工作---定义小车厢的容量,求小车厢的载重,矿石总重,标准线(半重)
int len =input.length/2,littleCaseWeight=0,allweight=0;
final double halfweight;
for(int i : input){ //求总重
allweight+=i;
}
halfweight=(double)allweight/2;//求半重
for(int i=0;i<len;i++){
littleCaseWeight+=input[i];
}
int flag=len;
int index=0;
while (flag>0){
flag--;
double deltaweight=Math.abs(halfweight-littleCaseWeight);
int swapindex=-1;
for(int i=len;i<input.length;i++){
int trylittleCaseWeight=littleCaseWeight-input[index]+input[i];
if(Math.abs(trylittleCaseWeight-halfweight)<deltaweight){
deltaweight=Math.abs(trylittleCaseWeight-halfweight);
swapindex=i;
}
}
if(swapindex!=-1){
littleCaseWeight=littleCaseWeight-input[index]+input[swapindex];//刷新小车厢载重和交换石块位置
int temp=input[index];
input[index]=input[swapindex];
input[swapindex]=temp;
flag=len;//替换后应该再开一轮
}
index=(index+1)%len;//从头循环
}
return Math.abs(allweight-2*littleCaseWeight);
}
后来又想到两个极端情况。
1.重的矿石先放进小车厢。因为容量固定,在这种情况下还无法超过半重,重石块换轻石块更是不可能,就可以直接导出结果了。
2.轻的石块先放小车厢。因为容量固定,在这种情况下还无法低于半重,轻石块换重石块更是不可能,就可以直接导出结果了。
那先对其进行部分选择排序,判断极端情况,应该能提升不少效率。
private static int solution(int[] input) {
//准备工作---定义小车厢的容量,求小车厢的载重,矿石总重,标准线(半重)
int len =input.length/2,littleCaseWeight=0,allweight=0;
final double halfweight;
for(int i =0;i<len;i++){//进行部分选择排序,把最重的几块矿石填满小车厢
int max_index=i;
for(int j=i;j<input.length-1;j++){
if(input[j]<input[j+1]){
max_index=j+1;
}
}
int temp=input[max_index];
input[max_index]=input[i];
input[i]=temp;
}
for(int i : input){ //求总重
allweight+=i;
}
halfweight=(double)allweight/2;//求半重
for(int i=0;i<len;i++){
littleCaseWeight+=input[i];
}
//准备完后可得,小车厢都是重石块,如果小车载重小于标准线,说明换石块是不可能让形成载重逼近标准线,可直接得到结果
if(littleCaseWeight<=halfweight){
// for(int i: input)System.out.print(i+",");
return allweight-2*littleCaseWeight;
}
//如果小车载重高于标准线,就需要调换小石块,使其逼近标准线,应该循环每个石块,在大车厢里找一块轻石块替换该石块,让小车厢载重更逼近标准线。如果1正轮都找不到,说明已经最接近了
int flag=len;
int index=0;
while (flag>0){
flag--;
double deltaweight=Math.abs(halfweight-littleCaseWeight);
int swapindex=-1;
for(int i=len;i<input.length;i++){
int trylittleCaseWeight=littleCaseWeight-input[index]+input[i];
if(Math.abs(trylittleCaseWeight-halfweight)<deltaweight){
deltaweight=Math.abs(trylittleCaseWeight-halfweight);
swapindex=i;
}
}
if(swapindex!=-1){
littleCaseWeight=littleCaseWeight-input[index]+input[swapindex];//刷新小车厢载重和交换石块位置
int temp=input[index];
input[index]=input[swapindex];
input[swapindex]=temp;
flag=len;//替换后应该再开一轮
}
index=(index+1)%len;//从头循环
}
return Math.abs(allweight-2*littleCaseWeight);
}
今年7月份vivo迎来了新入职的大学生,现在需要为每个新同事分配一个工号。人力资源部同事小v设计了一个方法为每个人进行排序并分配最终的工号,具体规则是:
将N(N<10000)个人排成一排,从第1个人开始报数;如果报数是M的倍数就出列,报到队尾后则回到队头继续报,直到所有人都出列;
最后按照出列顺序为每个人依次分配工号。请你使用自己擅长的编程语言帮助小v实现此方法。
输入描述:
输入2个正整数,空格分隔,第一个代表人数N,第二个代表M:
输出描述:
输出一个int数组,每个数据表示原来在队列中的位置用空格隔开,表示出列顺序:
输入例子1:
6 3
输出例子1:
3 6 4 2 5 1
例子说明1:
6个人排成一排,原始位置编号即为1-6。最终输出3 6 4 2 5 1表示的是原来编号为3的第一个出列,编号为1的最后一个出列。
看到题目的第一反应是“约瑟夫环问题”,但细细一品又不一样,所以前几天找的数学解法不能套用,那就只能模拟了。但模拟时发现题目隐藏着两个要求
要求1:重新排队(要求新序列)
要求2:还没有分配工号(保留旧信息)
以N=7,M=3为例。结果是3 6 2 7 5 1 4。
要求新序列是指原来的顺序改变了。保留旧信息,例子可见原第三个的人排第一了但还是没有分配工号1号。
最简单就是安排两个全0数组,一个做原队,另一个做新队,定义自增M为号令。原队循环遍历。每次报道%m= =0的人就把自己的index复制到新队,这样内存开销比较大。
private static String solution(int[] input) {
int n=input[0];
int m=input[1];
int[] res=new int[n];
int[] result=new int[n];
int index=0;
int signednum=1;
int M=1;
int idx=0;
while(idx<n){
if(res[index]!=0){
index=(index+1)%n;
continue;
}
if(M%m==0){
res[index]=1;
result[idx]=index+1;
idx++;
}
M++;
index=(index+1)%n;
}
StringBuffer ab=new StringBuffer();
for(int i :result){
ab.append(i+" ");
}
return ab.toString();
}
另外一种就是单组的推移方法。需要先遍历赋值,每次报道%m==0的人就移到队首,循环的起点也后移。不过这样数组赋值操作很频繁,比较慢。
小v在公司负责游戏运营,今天收到一款申请新上架的游戏“跳一跳”,为了确保提供给广大玩家朋友们的游戏都是高品质的,按照运营流程小v必须对新游戏进行全方位了解体验和评估。这款游戏的规则如下:
有n个盒子排成了一行,每个盒子上面有一个数字a[i],表示在该盒子上的人最多能向右移动a[i]个盒子(比如当前所在盒子上的数字是3,则表示可以一次向右前进1个盒子,2个盒子或者3个盒子)。
现在小v从左边第一个盒子上开始体验游戏,请问最少需要移动几次能到最后一个盒子上?
输入描述:
输入 :2 2 3 0 4
表示现在有5个盒子,上面的数字分别是2, 2, 3, 0, 4。
输出描述:
输出:2
小v有两种跳法:
跳法1:盒子1 -> 盒子2 -> 盒子3 -> 盒子5,共3下
跳法2:盒子1 -> 盒子3 -> 盒子5,共2下
跳法2的步骤数最少,所以输出最少步数:2。
输入例子1:
2 2 3 0 4
输出例子1:
2
典型的递归求解,只是没说无解时为-1
设数组的解为f()
看这个例子:2 2 3 0 4 和 2 3 0 4, 3 0 4
很容易就得到 f([2 2 3 0 4] )=min( f([2 3 0 4 ] ),f([3 0 4]) )+1
只要将其用编程语言来描述就行了。
另外可以发现,从a往后跳,它的范围是[a的index+1,a的index+a],这样只要在一个循环中分别试探后可达起点数组的次数,取最小值+1返回。如果是空集,就返回默认值(MAX__VALUE。。注意到是比小,如果函数中暂存次数的标记默认结果为-1,当出现一条路不通时,结果就是-1和其他路径次数比较了。所以这里应该设置暂存最小次数的值为极大)
接着分析这个函数需要的参数 :数组 ,次数计数器
数组不用每次都创建新数组,只要添加一个起点参数就可以了。
次数计数器,是每递归一一次,就自增一次的Int;
如果起点就是终点。说明数组已经跳到完结了,返回次数计数器。
,没有到底就看看可达起点的次数,取最小的+1返回。没有可达的点就返回极大值。
这里用的是倒着往前来。
private static int jump(int endindex ,int[] arr,int deepth ){
int minpath=Integer.MAX_VALUE;
if(endindex==0){
return deepth;
}
else {
for(int i=endindex-1;i>=0;i--){
if(arr[i]+i>=endindex){
int t=jump(i,arr,deepth+1);
minpath=t>minpath?minpath:t;
}
}
return minpath;
}
}