java实现 n人过桥问题
##【问题描述】 n个人要晚上过桥,在任何时候最多两个人一组过桥,每组要有一只手电筒。在这n个人中只有一个手电筒能用,求这些人过桥所用的最短时间。
##【输入】
输入的第一行给出n,接下来的n行给出每个人的过桥时间
例如: 5 1 2 3 4 5
##【输出】
输出的第一行给出所有n个人过桥的总的秒数,接下来的若干行给出实现策略。每行包括一个或两个整数,表示组成一组过桥的一个或两个人,以所用的时间标识。
5
1 2 3 4 5
去: 1, 2
回: 1
去: 4, 5
回: 2
去: 1, 3
回: 1
去: 1, 2
最短时间: 16
##【问题分析】
当看到求最短、最长、最多、最少等字样的时候,大概率会是动态规划或者贪心算法的问题。n人过桥问题是比较典型的贪心算法,由于一次过桥最多两人且手电筒需要往返传递,因此以两个成员过桥为一个分析单位,计算过桥时间。首先按过桥时间顺序从小到大排序,nums[1]为最小,nums[n]为最大。
我们可以这么思考贪心算法的问题:将贪心问题分为多个子问题,这些子问题相互之间没有关联(动态规划的每一个子问题都会相互关联,这是与贪心算法的主要区别),且每个子问题的状态都是独立的。那么我们可以这样思考n人过桥问题:每个子问题所求的都是过去两人所花费的最短时间,且手电要回到起始点。贪心就贪在每次我都想让两个最费时的人过去。
基于上述思想,问题简化为如何求得最费时的两个人过去所花费的最短时间呢?这里有点拗口哈。有两种方案:
方案一:用最快的成员nums[1]传递手电筒帮助最慢的nums[n]和nums[n-1]过桥,易知来回所用的时间为2*nums[1]+nums[n]+nums[n-1]。
方案二:用最快的成员nums[1]和次快的成员nums[2]传递手电筒帮助最慢的nums[n]和nums[n-1]过桥,具体方案如下:
第一步:nums[1]和nums[2]到对岸,所用时间为nums[2];
第二步:nums[1]返回,将手电筒给最慢的nums[n]和nums[n-1],并且nums[n]和nums[n-1]到对岸后将手电筒交给nums[2],所用时间为:nums[1]+nums[n];
第三步:nums[2]返回,所用时间为nums[2];
综合起来方案二所用的总时间为2*nums[2]+nums[n]+nums[1]。
最后,不论是动态规划还是贪心算法,都需要考虑边界条件
显然,这里的边界条件是: 最后尚未过桥的人可能有3人或者2人,如果是3人,则最少过桥时间为nums[1]+nums[2]+nums[3],如果是2人,则是nums[1]+nums[2]。下面是算法实现:
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取人数
int[] nums = new int[n+1];
Arrays.sort(nums);
for (int i = 1; i <= n; i++) {
nums[i] = sc.nextInt();
}
int result = 0;
int stay = n;
while(stay > 3){
if(nums[1] + nums[stay-1] > 2*nums[2]) {
// 第二种方案
System.out.println("去: " + nums[1] + ", " + nums[2]);
System.out.println("回: " + nums[1]);
System.out.println("去: " + nums[stay-1] + ", " + nums[stay]);
System.out.println("回: " + nums[2]);
result += 2*nums[2] + nums[stay] + nums[1];
} else {
// 第一种方案
System.out.println("去: " + nums[1] + ", " + nums[stay]);
System.out.println("回: " + nums[1]);
System.out.println("去: " + nums[1] + ", " + nums[stay-1]);
System.out.println("去: " + nums[1]);
result += 2*nums[1] + nums[stay] + nums[stay-1];
}
stay -= 2;
}
if (stay == 3){
System.out.println("去: " + nums[1] + ", " + nums[3]);
System.out.println("回: " + nums[1]);
System.out.println("去: " + nums[1] + ", " + nums[2]);
result += (nums[1] + nums[2] + nums[3]);
} else {
System.out.println("去:" + nums[1] + ", " + nums[2]);
result += (nums[2]);
}
System.out.println("最短时间: " + result);
}
}
当然,如果不需要显示来回的过程,只需要把打印语句去掉即可,这样使代码变得简便:
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取人数
int[] nums = new int[n+1];
Arrays.sort(nums);
for (int i = 1; i <= n; i++) {
nums[i] = sc.nextInt();
}
int result = 0;
int stay = n;
while(stay > 3){
if(nums[1] + nums[stay-1] > 2*nums[2]) {
// 第二种方案
result += 2*nums[2] + nums[stay] + nums[1];
} else {
// 第一种方案
result += 2*nums[1] + nums[stay] + nums[stay-1];
}
stay -= 2;
}
if (stay == 3){
result += (nums[1] + nums[2] + nums[3]);
} else {
result += (nums[2]);
}
System.out.println("最短时间: " + result);
}
}