问题描述
假设将一根金条切成两半,是需要花费和金条长度相同的钱。比如,一根80cm的金条,切成两半,需要花80块钱。现在,一群人想分一整块金条,每个人分得的长度保存在一个数组中,例如[10,20,30]意思就是将一根60cm的金条,划分成10,20,30三段,请问,怎样切分最省钱?
例如:
先将金条切分成10和50,需要60块钱,然后,再将50切分成20和30,需要50块,总共需要110块钱。
又或者,先将金条切分成30和30,花60块钱,然后再将30切分成10和20,花30块钱,总共就是90块钱。
90<110,所以第二种划算。
思路分析
首先,观察题目,可以发现,每切一次,只能切出来一段最终结果,另一端肯定还有继续切割,这一段最终结果是不需要再次切分了,也就说明,后面切分时就不需要再算这一段的钱了,因此,这一段最终结果的数值越大,总的代价就越小。也就是说,我们先把大的切出来,越省钱,大的不会再次计算价钱了,越小的,我们最后切出来。
因此,从上面分析出来的结果可以看出,其实,本质就是,减少数值大的遍历次数,增加数值小的遍历次数,这样总体代价最低。
哈夫曼树
这样的贪心算法有一种数据结构能够轻松实现,那就是哈夫曼树,哈夫曼树的构建过程,就是先把最小的两个相连,然后组成一个新节点,然后放入总的集合中,再从中找出最小的两颗子树再次相连,再将结果放入集合中,最终集合中只剩下一棵树,这棵树就是哈夫曼树。这道题其实就是哈夫曼树的应用。
小根堆
此题用哈夫曼树解决,为什么会提到小根堆呢?因此,哈夫曼树的构建过程,每次需要将两个子树合成的结果再次放入集合中,然后,再从集合中取出两个最小值。这个过程,如果用常规数组的话,每次放入后,是不是还需要再次排序呀?这个再次排序的过程,是不是小根堆效率很好啊?只需要调整小根堆就行了,最多logn,如果是一般方法,是不是每次都需要重新排序,效率极低的。
最终解题思路
先将数组构建成小根堆,然后,从小根堆中弹出两个。把这两个加在总和money上,然后这两个数相加,相加后,再次入小根堆。然后再从中弹出两个最小值,重复上述操作,直到小根堆中只剩一个数,结束,计算money。
代码
小根堆代码
public class littleHeap {
int[] arr;
int index;//堆中的最后一个元素的索引
public littleHeap(int[] a) {
arr = new int[a.length * 2];
index = 0;
for (int i = 0; i < a.length; i++) {
arr[++index]=a[i];
insert(arr,index);
}
}
//弹出堆顶,并调整小根堆
public int getTop(){
if(index<1){
System.out.println("见底了");
return 0;
}else if(index==1){
index--;
return arr[1];
}else {
int temp=arr[1];
arr[1]=arr[index];
arr[index]=temp;
index--;
int left=2;
int father=1;
while (left<=index){
int right=left+1;
int min;
if(right>index){
min=left;
}else {
min=arr[left]<arr[right]?left:right;
}
if(arr[min]<arr[father]){
temp=arr[min];
arr[min]=arr[father];
arr[father]=temp;
father=min;
left=father*2;
}else {
break;
}
}
return arr[index+1];
}
}
//插入一个数
public void insert(int num){
arr[++index]=num;
insert(arr,index);
if(index==arr.length-1){
int[] newArr=new int[arr.length*2];
for (int i = 1; i < arr.length; i++) {
newArr[i]=arr[i];
}
arr=newArr;
}
}
private void insert(int[] arr, int index) {
int father=index/2;
while(father>=1&&arr[father]>arr[index]){
int temp=arr[father];
arr[father]=arr[index];
arr[index]=temp;
index=father;
father=index/2;
}
}
}
本题代码
int[] arr=new int[]{20,10,30};
littleHeap heap=new littleHeap(arr);
int money=0;
//index表示堆中末尾索引
while (heap.index>1){
int min1=heap.getTop();
int min2=heap.getTop();
money+=min1+min2;
heap.insert(min1+min2);
}
System.out.println("最小花费为:"+money);