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);
}
}