【OD机试题解法笔记】士兵过河

士兵过河问题最优解法

题目

一支N个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河。
敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭。
现在军队只找到了一只小船,这船最多能同时坐上2个士兵。

1)当一个士兵划船过河,用时为a[i];0 <= i < N
2)当两个士兵坐船同时划船过河时,用时为 max(a[j], a[i]) 两士兵中用时最长的。
3)当两个士兵坐船一个士兵划船时,用时为 a[i] * 10 ; a[i] 为划船士兵用时。
4)如果士兵下河游泳,则会被湍急水流直接带走,算作死亡。

请帮忙给出一种解决方案,保证存活的士兵最多,且过河用时最短。

输入描述
第一行:N 表示士兵数(0 < N < 1,000,000)
第二行:T 表示敌军到达时长(0 < T < 100,000,000)
第三行:a[0] a[1] … a[i] … a[N - 1]
a[i] 表示每个士兵的过河时长。
(10 < a[i] < 100; 0 < i < N)

输出描述
第一行:“最多存活士兵数” “最短用时”

用例

输入输出说明

5

43

12 13 15 20 50

3 40可以达到或小于43的一种方案:
第一步:a[0] a[1] 过河用时: 13
第二步:a[0] 返回用时: 12
第三步:a[0] a[2] 过河用时: 15

5

130

50 12 13 15 20

5 128

可以达到或小于130的一种方案:
第一步:a[1] a[2] 过河用时: 13
第二步:a[1] 返回用时: 12
第三步:a[0] a[4] 过河用时: 50
第四步:a[2] 返回用时: 13
第五步:a[1] a[2] 过河用时: 13
第六步:a[1] 返回用时: 12
第七步:a[1] a[3] 过河用时: 15

7

171

25 12 13 15 20 35 20

7 171

可以达到或小于171的一种方案:
第一步:a[1] a[2] 过河用时: 13
第二步:a[1] 返回用时: 12
第三步:a[0] a[5] 过河用时: 35
第四步:a[2] 返回用时: 13
第五步:a[1] a[2] 过河用时: 13
第六步:a[1] 返回用时: 12
第七步:a[4] a[6] 过河用时: 20
第八步:a[2] 返回用时: 13
第九步:a[1] a[3] 过河用时: 15
第十步:a[1] 返回用时: 12
第十一步:a[1] a[2] 过河用时: 13

思考

题目要尽可能多的士兵过河,然后才是过河时间最短。看到题目中给出了很多计算过河的时间花费,猜测可能用到动态规划求解,比如只有一个士兵过河呢,2个士兵、N-1个士兵和N个士兵过河的解之间的关系。如果暴力求解需要把每一种过河方案都枚举一遍,每次选一个不同的士兵从余下士兵中递归枚举直到一个士兵,这其中可能出现许多重复的求解过程,而动态规划比较适合缓存这些重复的计算。(1)先分析一个士兵过河情形,当前敌军到达时间 T > a[0],则士兵能过河,存活数1,最短时间就是 a[0],否则不能过河,存活数0,最短时间 0 ;(2)假如有 2 个士兵,先计算两个人过河最短时长t1 = Min(Max(a[0], a[1]), a[0]*10, a[1]*10),如果t1 < T,则 2 个人都能过河,结果是存活 2,用时最短 t1,如果 t1 >= T,则 2 个人不能都过河,回到 (1) 的计算,这时候尝试让 a[0] 和 a[1] 过河时长最短的士兵过河;(3)假如有 3 个士兵,题目中说明一条船一次只能坐 2 个人,我到底让哪个 2 个先过河呢?我是不是应该先将 3 个士兵的过河时长排个序?我先从小到大排序,a[0] <= a[1] <= a[2]。因为只有一只船,两个人过河后还得让一个人把船划到对岸去,余下的士兵才能继续过河。也就是 3 个人以上过河,除了第一次和最后一次过河,中间每次过河都需要有人从对岸把船划过来,应该尽可能选择过河最快人划船?根据直觉最快的人每次单独划船耗时最短,这样会有效减少回程的累计时间。每次过河怎么配对人员,是最快的和次快的组合还是最快的和最慢的组合,不会最快的和随机选一个组合吧?最慢的过河时间就是最慢的那个人,最慢的过河了就不能让他回程。每次让最快搬运次快的和让最快的搬运最慢的感觉不到差异,至少 3 个士兵看不出来时间差异,先按最快带余下的次快过河这个思路来处理 3 个士兵过河的问题。假如三个士兵过河时间为 [12 13 15],最短时间过河步骤:

  • 12 和 13 过河 花费时间 13;
  • 12 返程花费时间 12;
  • 12 和 15 过河花费时间 15;

因此总的 3 个人过河时长是 13 +  12 +  15 = 40,这时候有人可能说如果敌军到达时长T < 40,这个最短时长就没意义。是这样,但这个计算目的是让 3 个士兵以最短的时间过河,如果这个时间都无法在敌军到达前过河,那么证明 3 个士兵无法一起过河,只能考虑 2个士兵过河情况,把问题规划缩小了。因此,这题解法应该是从1 个士兵一直到 N 个士兵过河最短时间自底向上计算,直到敌军时长无法满足 k 个士兵过河,此时答案就是 k-1 个士兵的能存活,过河时间就是 k-1 个士兵过河最短时间。

为了验证前面用划船时间的最短的士兵返程方案是否是最佳方案,用测试用例2来验证下,5个士兵过河,过河时长分别为 50 12 13 15 20,从小到大排序为 12 13 15 20 50,过河方案:

  • 12 和 13 过河, 耗时 13;-
  • 12 返程,           耗时 12;-
  • 12 带 15 过河, 耗时 15;-
  • 12 返程,          耗时 12;-
  • 12 带 20 过河, 耗时 20;
  • 12 返程,          耗时 12;
  • 12 带 50 过河,耗时 50。-

5 个士兵过河总时长是 13 +  12 + 15 + 12 + 20 + 12 + 50 = 134,用例答案是128,方案不对!按照题目用例说明的步骤执行下看看为什么最短时间是128不是134:

  • 12 和 13 过河, 耗时 13;-
  • 12 返程,          耗时 12;-
  • 20 和 50 过河, 耗时 50;-
  • 13 返回,          耗时 13;
  • 12 和 13 过河, 耗时 13
  • 12 返程,          耗时 12;-
  • 12 和 15 过河,耗时 15. -

总的时长是 13 + 12 + 50 + 13 + 13 + 12 + 15 = 128。

和这个方案比较发现,并不是每次都让 12 返程,12 在第二次返程后让最慢的 50 和次慢的 20 过河了,为什么这么做?通过仔细比对两个方案发现,让最慢的和次慢的一次过河会更节省时间。观察发现第一个方案20没有和50一起过河导致他们两个耗时最长的家伙各自过河累计耗时70比一起过河慢了20,这个应该就是原因所在,这个134-128 = 6,这个差了6个时长实际上是20 + 12 - (13 + 13) = 6,很明显最快的12和次快的13差距很小,而次慢的20拉大了差距。查阅资料得知这个问题改自吊桥谜题,最优过河策略是尽量保证让最慢带次慢的过河,这样就能让整体过河时间最短。具体方案:1)如果剩下最慢的士兵没过河,需要河对岸最快的士兵返回带最慢的过河;2)剩下两个最慢士兵没过河,需要让河对岸最快士兵返回,让最慢带次慢的士兵过河,再让河对岸次快的士兵返回带最快的士兵过河,综合取这这两种情形中过河最短时间。

算法过程

1、对士兵过河时间按从小到大排序,a[0]<a[1]<... <a[n-1];

2、如果a[0] >T,则没有人能过河,返回"0 0";

3、如果只有一个士兵,返回“1 a[0] ";

4、初始化动态规划数据 dp,容量是N,dp[i] 表示前 i 个士兵过河最短时间,0个士兵过河时间dp[0] = 0,前1个士兵过河最短时间 dp[1]  = a[0],前2个士兵过河时间 dp[2] = Min(a[0] * 10, a[1]);

5、如果dp[2] > T,前 2 个士兵过不了河,则让第一个更快的士兵过河,返回 “1 dp[1]";

6、从 i = 3 开始遍历,表示对 3 个以及更多的士兵过河最短时间进行更新,每轮循环根据前面分析的两种情形计算最短时间。

剩下一个最慢士兵没过河的最短时间为 :

dp[i-1]+a[0]+Min(a[0]*10, a[i-1]);

剩下一个最慢河一个次慢的士兵没过河的最短时间:

dp[i-2]+a[0]+Min(a[i-2]*10, a[i-1])+a[1]+Min(a[0]*10, a[1])

综合计算 dp[i] = Min(a1, a2);

7、如果发现 dp[i] > T,那么前 i 个士兵过不了河,前 i-1个士兵能过河,返回 ”i-1,  dp[i-1]";

8、循环终止,返回所有士兵过河结果: N   dp[N]。

参考代码

function solution(readlines) {
  const lines = readlines.trim().split('\n');
    const N = parseInt(lines[0]);
    const T = parseInt(lines[1]);
    const a = lines[2].trim().split(' ').map(Number);
    // 对士兵过河时间进行排序
    a.sort((x, y) => x - y);
    // console.log(a);
    if (a[0] > T) return '0 0';
    if (N < 2) {
      return `1 ${a[0]}`;
    }
    let dp = new Array(N+1);
    dp[0] = 0;
    dp[1] = a[0];
    dp[2] = Math.min(a[0]*10, a[1]);
    if (dp[2] > T) {
      return "1 " + dp[1];
    }
    for (let i = 3; i <= N; i++) {
      // 剩下一个最慢士兵没过河,需要河对岸最快士兵划船返回带最慢的过河
      let a1 = dp[i-1]+a[0]+Math.min(a[0]*10, a[i-1]);
      // 剩下两个最慢士兵没过河,需要河对岸最快士兵划船返回,让最慢的两个士兵先过河,再让次快的士兵返回带最快的士兵过河
      let a2 = dp[i-2]+a[0]+Math.min(a[i-2]*10, a[i-1])+a[1]+Math.min(a[0]*10, a[1]);
      dp[i] = Math.min(a1, a2); // 取两种方案中最小的时间
      if (dp[i] > T) return i-1 + " " + dp[i-1];
    }
    return N + " " + dp[N];    
}

const cases = [
  `5
   43
   12 13 15 20 50`,
  `5
   130
   50 12 13 15 20`,
   `7
   171
   25 12 13 15 20 35 20`
  ];

cases.forEach(e => {
  console.log(solution(e));
});

华为OD考试中的问题是:士兵过河的情境。士兵过河是一道经典的逻辑思维题目,要求在特定条件下找到一种最佳解决方案。 在这个情景中,士兵需要过一条河,河上只有一条船可供使用。然而,在河的两岸和船上都有一些限制条件。首先,士兵们只能以一次过河的方式进行,也就是每次只能有一个人或一组人乘船。其次,船的容量有限,只能乘坐有限数量的人。最后,船上必须有人才能使其正常行驶。 为了解决这道题目,需考虑以下几个策略。首先,可以利用船的来回行驶,逐个运送士兵。例如,士兵A、B、C、D需要过河,如果船只能容纳两人,则可以让A和B先过去,然后A再返回,接C和D过去,最后再返回接A。其次,还可以根据士兵的能力和速度进行组合,使过河时间最短。例如,如果A花费的时间最长,可以让A和B一起过河,然后A返回,接C和D过河,这样可以减少整体的过河时间。 在解决这道题目的过程中,需要全面考虑船的容量和限制条件,合理安排士兵过河顺序和组合方式,以最优的方式完成过河任务。这道题目旨在考察考生的逻辑思维能力、组织协调能力和问题解决能力,同时也考察了考生在有限资源条件下做出最优选择的能力。通过这样的考题,华为OD考试能够评估出考生的思维方式和解决问题的能力,从而筛选出最适合职位需求的人才。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值