伐木问题
一片环形的林带按照时钟方向分成N块相邻的林地,首尾相连,每一块林地均与另两块林地相邻。伐木工对每一块林地里可采伐的木材量进行了估计,这些木材量以 int[] woods 来表示。这个数组按顺时针顺序列出了每一块林地里的可采伐木材量。
现在要在这片林带中选择一些林地来进行采伐。但为了不破坏生态,不能有两块相邻的林地同时被采伐。你必须编写程序来计算最大可采伐的木材量。
函数声明:int maxWoods(int[] woods)
参数范围:woods可以包含2到50个元素。每一个元素的取值范围为1到1000。
测试用例:
1:{10, 3, 2, 5, 7, 8}
输出:19
注:因为林地首尾相连,所以不能同时取10和8。在此限制下,最大的开采量为10+2+7=19。
2:{11, 15}
输出:15
3:{7, 7, 7, 7, 7, 7, 7}
输出:21
4:{1, 2, 3, 4, 5, 1, 2, 3, 4, 5}
输出:16
5:{94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72}
输出:2926
分析:
状态转移方程:
两种状态:
1).包含i
2).不包含i
情况一:包含i
F(i) = Max{F(i-2) + a[i], a[i]},所以当F(i-2) < 0,时, F(i) = a[i],else: F(i) = F(i-2)+a[i]
情况二:不包含i
F(i) = F(i-1)
综合一二: F(i)= Max{Max{F(i-2)+a[i],a[i]}, F(i-1)}
初始化:
F(0)= a[0]
F(1) = Max{a[0], a[1]}
具体实现有下面三个方法:
方法一:
1. 递归实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication4 { class Program { static void Main(string[] args) { int[] input0 = { 11, 15 }; // GetMaxSubArrayValue(input0); int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 }; int temp = GetMaxGroupValueOuter(input1); } // 方法一: 采用递归 public static int GetMaxGroupValueOuter(int[] arrary) { int endIndex = arrary.Length - 1; int startIndex = 0; if (endIndex == 0) { return arrary[0]; } if (endIndex == 1) { return Math.Max(arrary[0], arrary[1]); } int temp1 = GetMaxGroupValue(arrary, startIndex, endIndex - 1); int temp2 = GetMaxGroupValue(arrary, startIndex + 1, endIndex); return Math.Max(temp1, temp2); } // 采用递归 public static int GetMaxGroupValue(int[] arrary, int startIndex, int endIndex) { if (startIndex == 0 || startIndex == 1) { if (endIndex == startIndex) { return arrary[startIndex]; } if (endIndex == startIndex + 1) { return Math.Max(arrary[startIndex], arrary[startIndex + 1]); } } int temp1 = GetMaxGroupValue(arrary, startIndex, endIndex - 2); if (temp1 < 0) //F(i-2) <0; { temp1 = arrary[endIndex]; // //f(i) = Max(F(i), F(i-2) +a[i], F(i-1)}; // } else { temp1 += arrary[endIndex]; } int temp2 = GetMaxGroupValue(arrary, startIndex, endIndex - 1); return Math.Max(temp1, temp2); } } }
方法二:
1.利用空间来换取时间,提高效率
2.自底而上,顺序求解
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication4 { class Program { static void Main(string[] args) { int[] input0 = { 11, 15 }; // GetMaxSubArrayValue(input0); int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 }; int temp = GetMaxGroupValueOuter2(input1); } public static int GetMaxGroupValueOuter2(int[] arrary) { int endIndex = arrary.Length - 1; int startIndex = 0; if (endIndex == 0) { return arrary[0]; } if (endIndex == 1) { return Math.Max(arrary[0], arrary[1]); } int temp1 = GetGroupMax(arrary, startIndex, endIndex - 1); int temp2 = GetGroupMax(arrary, startIndex + 1, endIndex); return Math.Max(temp1, temp2); } // 方法二。自底而上,顺序求解 public static int GetGroupMax(int[] array, int startIndex, int endIndex) { if (startIndex == 0 || startIndex == 1) { if (endIndex == startIndex) { return array[startIndex]; } if (endIndex == startIndex + 1) { return Math.Max(array[startIndex], array[startIndex + 1]); } } int[] values = new int[endIndex - startIndex + 1]; for (int j = 0; j < endIndex - startIndex + 1; j++) { values[j] = int.MinValue; } //初始化F(0), F(1) values[0] = array[startIndex]; values[1] = Math.Max(array[startIndex], array[startIndex + 1]); // 初始化sum int index = 0; for (int i = 2; i < endIndex - startIndex + 1; i++) { if (values[i - 2] > 0) { values[i] = Math.Max(values[i - 1], values[i - 2] + array[i + startIndex]); } else { values[i] = Math.Max(values[i - 1], array[i]); } } for (int i = 1; i < endIndex - startIndex + 1; i++) { if (values[i] > values[index]) { index = i; } } return values[index]; } } }
方法三:
1.采用迭代
2. 自底而上,顺序求解,优化空间
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication4 { class Program { static void Main(string[] args) { int[] input0 = { 11, 15 }; // GetMaxSubArrayValue(input0); int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 }; int temp = GetMaxGroupValueOuter3(input1); } // 方法三:采用迭代,自底而上,顺序求解,优化空间 public static int GetMaxGroupValueOuter3(int[] arrary) { int endIndex = arrary.Length - 1; int startIndex = 0; if (endIndex == 0) { return arrary[0]; } if (endIndex == 1) { return Math.Max(arrary[0], arrary[1]); } int temp1 = GetMaxSubArrayValue(arrary, startIndex, endIndex - 1); int temp2 = GetMaxSubArrayValue(arrary, startIndex + 1, endIndex); return Math.Max(temp1, temp2); } // 求不连续数组的子数组之和的最大值. public static int GetMaxSubArrayValue(int[] array, int startIndex, int endIndex) { int sum = array[0]; int temp1 = array[startIndex]; // F(i-2) int temp2 = array[startIndex + 1]; // F(i-1) // F(i) = Max (F(i-2) + array[i], array[i], F(i-1) // 采用迭代更替的方法,逐步更新. temp2代表 F(i-1)的最大值,temp1比temp2始终慢一步,代表F(i-2)的值. if (startIndex == 0 || startIndex == 1) { if (endIndex == startIndex) { return array[startIndex]; } if (endIndex == startIndex + 1) { return Math.Max(array[startIndex], array[startIndex + 1]); } } for (int i = startIndex + 2; i <= endIndex; i++) { if (temp1 < 0) { //情况一。 F(i-2) < 0;很明显 F(i-2) + a[i] < a[i] temp1 = array[i]; //s = e = i; } else //情况二 F(i-2) = F(i-2)+a[i] { temp1 += array[i]; // e = i; } // 到这一步时, F(i-2) = Max{(F(i-2), F(i-2) + a[i])} // 情况三。不包含a[i]的情况,即a[0]~a[i-1]的最大值,设为sum. if (temp1 > sum) //用F(i-2)和sum比较 { sum = temp1; } temp1 = temp2; temp2 = sum; } return sum; } } }
总结:1. 此题非常类似于2.14 求数组的子数组之和的最大值和最小值(动态规划),但又有所不同,主要在于此题的不连续性。所以才用在方法二中采用两个临时变量,来表示当前的最大值,和前一个最大值,交替实现。别的地方恶化子数组之和的最大值问题完全一样。
2. 此题实际上是由不连续子数组最大和问题延伸而来
http://www.cnblogs.com/freewater/archive/2012/08/18/2645777.html
3. 和01背包问题也非常类似(选i和不选i)
http://www.cnblogs.com/jiangjun/archive/2012/05/08/2489590.html