题目描述:
启动速度时APP的核心内容但在超级APP中经常出现由于启动任务繁多导致启动速度慢的问题。因此我们尝试通过并行启动任务加快启动速度。假设启动任务
都是独立的,并且多线程并行执行,那么恰定一组正整数(<=50)个表示一组任务,每个数字表示启动任务耗时,所有的耗时之和(<10000),请算出该组任务的所需的
最短耗时。
题目分析:
根据题目要求,现在有两个线程,要想实现线程耗时最短,可行的方法是使两个线程运行时间尽可能平均,按这个想法来开考虑,那么最短耗时将会是比单独
一个线程运行总耗时的一半多点。如果是n个线程能?毫无疑问,将会比【总耗时/n】多一点,至于多多少,就要看情况而定了。
现在我们随便给出一组任务数据:比如 5,20,1,8,17,19,30,39 。
我们执行这样的操作:
0:设置两个阻塞队列,阻塞队列A和阻塞队列B
1:把这组任务时间按升序排列,即 1,5,8,17,19,20,30,39
2:线程A选耗时最长的任务 ,放入阻塞队列A
3:线程B选耗时次长的任务 , 放入阻塞队列B
4:在剩下任务中,线程A选耗时次长的任务 ,放入阻塞队列A ;线程B选耗时最长的任务 , 放入阻塞队列B
5:在剩下任务中,线程A选耗时最长的任务 ,放入阻塞队列A ;线程B选耗时次长的任务 , 放入阻塞队列B
(2-5的意思就是交换选择:这回你选最长的,我选次长的;下回你选次长的,我选最长的。知道全部选完,当然要注意奇数和偶数的情况)
.....
.....
.....
10:比较两个线程的总耗时,取耗时最小者即为我们本题目的解
上面给出的操作貌似是大致是,但是仔细想想。加入我的一组数有10个,前面9个数的总和是100,第10个数的值是1000,那么最短耗时肯定是1000。说明上面
给出的方案还是有点问题的,那我们在完善一下。
我们针对这组数 1,2,3,7,8,19 考虑这样一个等式【这组数是升序排列的】:
任选3个连续的数: 第一个数+第二个数 < 第三个数 ,我们令: 第一个数 = 第一个数+第二个数 ,第二个数删除掉
那么我们是从头到尾还是从尾到投一次执行这样的操作呢?
我们来实践一下:
从头到尾:
1+2 > 3 , 2+3<7,则新的数据为 1,5,7,8,19
1+5 <7 ,则新的数据为 6,7,8,19
6+7 >8 ,7+8<19,则新的数据为 6,15,19
我们可以看出,最短耗时将是 A:先选6 ,B先选15 。接着还剩下19 ,因为A此时耗时最短,我们将19放在A线程里面
所以最终最短耗时将是 A:6 + 19 = 25 , B:19 ,所以总的最短耗时是25
从尾到头:
7+8 <19 ,则新的数据为 1,2,3,15,19
3+15 <19 ,则新的数据为 1,2,18,19
2+18 >20 ,1+2 <18 ,则新的数据是 3,18,19
我们可以看出,最短耗时将是 A先选19 , B先18 。接着剩下3,因为B此时耗时最短,所以我们将2放在B线程里面
所以最终最短耗时将是:A:19 ,B:18+3 =21 ,,所以总的最短耗时是21
通过上面的数据分析,我们发现最从头到尾和从尾到头进行数据处理,最后的剩下的数据可能一直,但最终耗时是不一致的,很明显,从尾到头进行数据处
理是是可以找到最短耗时的。
经过这样的数据处理后,加入剩下的这组数的个数是偶数个,我们啥也不想,因为线程A和B能处理相同的任务个人,但是假如是奇数个呢?我们就要考虑这
组数的第一个任务给谁?当然是给线程A和线程B中总耗时最短的,才会使我们的总耗时最短。
我们看看阿里官方给出的模板代码:
import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;
public class Main {
/** 请完成下面这个函数,实现题目要求的功能 **/
/** 当然,你也可以不按照这个模板来作答,完全按照自己的想法来 ^-^ **/
static long AppLaunchTaskOptimize(boolean tasks) {
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
long res;
boolean _tasks;
int _tasks_temp = Integer.parseInt(in.nextLine().trim());
_tasks = (_tasks_temp != 0);
res = AppLaunchTaskOptimize(_tasks);
System.out.println(String.valueOf(res));
}
}
首先,我看到这个模板的时候完全是蒙蔽的,导致我只写了解体思路,就交卷了。现在我们仔细分析下这个模板,_task_temp 是这组任务的总数,判断如果
这任务总数不为0,就使 _task == true ,然后调用函数 AppLauchTaskOptimize(_tasks) ,具体的代码在AppLauchTaskOptimize(boolean _tasks){ }里面写。读者看到
这里行不行打人,妈的,你任务总数不传过来,还玩个球?反正阿里也不是第一回这样搞了,要不你就不要给出模板,给个模板还是误导答题者的 _task_temp != 0 ,
加入我输入 -1嘞?那_tasks还不是true,你这个参数没啥球用【虽说后台测试用例不会这么坑】。
说了这么所,我们直接上代码吧
import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;
//题目描述:启动速度时APP的核心内容但在超级APP中经常出现由于启动任务繁多导致启动速度慢的问题。
//因此我们尝试通过并行启动任务加快启动速度。假设启动任务都是独立的,并且多线程并行执行,那么恰定一
//组正整数(<=50)个表示一组任务,每个数字表示启动任务耗时,所有的耗时之和<10000
//请算出该组任务的所需的最短耗时
public class Main{
static long AppLaunchTaskOptimize(boolean tasks ,int taskCount) {
if (tasks) {
Scanner in = new Scanner(System.in);
int i = 0 ;
int[] consumeArray = new int[taskCount] ;
while(i < taskCount)
consumeArray[i++]=in.nextInt();
Arrays.sort(consumeArray);
int[] newConsumeArray = getIntArrays(consumeArray);
return getSmall(newConsumeArray) ;
}else {
return 0 ;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
long res;
boolean _tasks;
int _tasks_temp = Integer.parseInt(in.nextLine().trim());
_tasks = (_tasks_temp >= 0 && _tasks_temp <= 50);
res = AppLaunchTaskOptimize(_tasks,_tasks_temp);
System.out.println(String.valueOf(res));
}
//判断数组中三个连续整数: 最小 + 次小 < 第三小 (遇到0就忽略,即1、2、0、4 看成 1、2、4)
private static int[] getIntArrays(int [] arrays ) {
int i = arrays.length ;
int temp = arrays[--i];
while(i >= 2)
{
System.out.println("i="+i);
if(arrays[i-2] + arrays[i-1] < temp){
temp = arrays[i-2] + arrays[i-1] ;
arrays[i-2]=temp;
arrays[i-1]=0;
i=i-2;
}else{
temp = arrays[i-1];
i=i-1;
}
}
arrays = tirmArray(arrays);
if(arrays.length >= 3 && arrays[0] + arrays[1] < arrays[2]){
arrays[0]=arrays[0] + arrays[1];
arrays[1]=0;
}
return tirmArray(arrays) ;
}
//把数组的0元素去掉
private static int[] tirmArray(int oldArr[]){
//创建临时数组,存储非零值
int[] tempArr = new int[oldArr.length];
//记录临时数组当前下标
int index = 0;
for (int i = 0; i < oldArr.length; i++) {
if (oldArr[i] != 0) {
//按顺序存储,同时下标自增
tempArr[index++] = oldArr[i];
}
}
//创建最终数组,大小从index获取
int[] newArr = new int[index];
//从临时数组复制到最终数组
for (int i = 0; i < index; i++) {
newArr[i] = tempArr[i];
}
return newArr;
}
//获取最短耗时
private static int getSmall(int[] arrays){
int threadACount =0 ;
int threadBCount =0;
int index = arrays.length -1 ;
boolean flag = true ;
System.out.println(Arrays.toString(arrays));
if(arrays.length %2 == 0){
while(index >= 0)
{
if(flag){
threadACount = arrays[index] +threadACount;
threadBCount = arrays[index-1] + threadBCount ;
flag = false ;
}else {
threadACount = arrays[index-1] +threadACount;
threadBCount = arrays[index] + threadBCount ;
flag = true ;
}
index = index - 2 ;
}
return threadACount>threadBCount ? threadBCount : threadACount ;
}else {
while(index >= 1)
{
if(flag){
threadACount = arrays[index] +threadACount;
threadBCount = arrays[index-1] + threadBCount ;
flag = false ;
}else {
threadACount = arrays[index-1] +threadACount;
threadBCount = arrays[index] + threadBCount ;
flag = true ;
}
index = index - 2 ;
}
return threadACount>threadBCount ? threadBCount+arrays[0] : threadACount+arrays[0] ;
}
}
}
那么问题来了?这是不是最正确的呢?优细心的读者还是看出了问题。上面有一组数据 1,2,18,19 。那么我们可以看出1+19=20,2+18=20,那么最短耗
时是20,不可能是上面说到的21。带着这个问题,我仔细思考了一下,发现了问题的原因
假如这组数据还是升序排列的,1,2,3,7,8,19 。我们考虑这样一个等式
任选4个连续的数: 第二个数+第三个数 < 第四个数 ,我们令: 第二个数 = 第二个数+第三个数 ,第三个数=第四个数,第四个数置为0或删除掉 ;
第二个数+第三个数 > =第四个数 ,此时 a= 第一个数+第四个数 ,b=第二个数+第三个数 ,此时a和b的值就会比较接近,达到尽可能平均的目
的此时比较 a和b的大小,最小的为第二个数,较小的为第二个数;
经过这样的操作,我们得到的这组数是有序的【从尾到头】,而却尽可能平均的分给两个线程的耗时
当剩下3个数时: 第一个数+第二个数 与 第三个数 比较大小,选最大者
当剩下2个数时:我们肯定选第二个数
那么我们是从头到尾还是从尾到投一次执行这样的操作呢?
我们来实践一下:
从头到尾:
2+3 <7,则新的数据经过升序排列后为 :1,5,7,8,19
5+7 >8,则新的数据经过升序排列后尾: 8,12,19
我们可以看出,最短耗时将是 A:8+12 ,B:19 。所以最终耗时是20
但是从头到尾操作不能使数据的升序的,比如这组数据:1,2,4,5,6,7,8。所以从头到位需要没进行一次操作就要重新排序才能进行第二轮操作
从尾到头:
7+8 <19 ,则新的数据为 1,2,3,15,19
3+15 <19 ,则新的数据为 1,2,18,19
2+18 >20 , 则新的数据是 20,20
我们可以看出,最短耗时是 A:20 ,B:20 。所以最终耗时是20
通过上面的数据分析,我们可以【从尾到头】和【从头到尾】进行数据处理是是可以找到最短耗时的,但是【从头到尾每一轮都要对所得的数据+未进行操作
的数据重新进行升序排列】,二【从尾到头只需要对操作所得的数据进行升序排列,不需要未进行操作的数据参与排序】,从性能方面来考虑,【从尾到头】还是
比较优的
下面直接上修正后的代码哈
import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;
//题目描述:启动速度时APP的核心内容但在超级APP中经常出现由于启动任务繁多导致启动速度慢的问题。
//因此我们尝试通过并行启动任务加快启动速度。假设启动任务都是独立的,并且多线程并行执行,那么恰定一
//组正整数(<=50)个表示一组任务,每个数字表示启动任务耗时,所有的耗时之和<10000
//请算出该组任务的所需的最短耗时
public class Main {
static long AppLaunchTaskOptimize(boolean tasks ,int taskCount) {
if (tasks) {
Scanner in = new Scanner(System.in);
int i = 0 ;
int[] consumeArray = new int[taskCount] ;
while(i < taskCount)
consumeArray[i++]=in.nextInt();
System.out.println(Arrays.toString(consumeArray));
Arrays.sort(consumeArray);
return getIntArrays(consumeArray);
}else {
return 0 ;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
long res;
boolean _tasks;
int _tasks_temp = Integer.parseInt(in.nextLine().trim());
_tasks = (_tasks_temp != 0 && _tasks_temp <= 50);
res = AppLaunchTaskOptimize(_tasks,_tasks_temp);
System.out.println(String.valueOf(res));
}
//判断数组中三个连续整数: 最小+次小 < 第三小 (遇到0就忽略,即1、2、0、4 看成 1、2、4)
//假如数组中四个连续整数: 1,2,18,19 2+18 > 19 , 我们进行这样的操作 1+19=20 ,2+18=20
//即 a= 第一个数+第四个数 ,b=第二个数+第三个数 ,因为这组数是升序排列的,
//此时a和b的值就会比较接近,达到尽可能平均的目的
private static int getIntArrays(int [] arrays ) {
int i = arrays.length -1;
int temp = 0;
while(i >= 3)
{
if(arrays[i-2] + arrays[i-1] < arrays[i]){
temp = arrays[i-2] + arrays[i-1] ;
arrays[i-2]=temp;
arrays[i-1]=arrays[i];
arrays[i]=0;
i=i-1;
}else{
int a = arrays[i-3]+arrays[i] ;
int b = arrays[i-2]+arrays[i-1];
arrays[i-3]= a>b?b:a ;
arrays[i-2]=a>b?a:b ;;
arrays[i]=0;
arrays[i-1]=0;
i=i-2;
}
}
if(arrays.length == 3 && arrays[0] + arrays[1] < arrays[2]){
return arrays[0]+arrays[1]>arrays[2]?arrays[0]+arrays[1]:arrays[2] ;
}else{
return arrays[1] ;
}
}
}
如果还有问题,请读者在留言,笔者会继续改进。