题意分析
- 给你一个序列,需要将这个序列进行两两合并,最后合并成一个数字。每次合并的花费就是这两个数字的和乘上一个c,问合并成一个数字的最小的花费是多少?
思路分析
- 学过数据结构的同学应该都知道,这一题是一个很经典的类似于哈夫曼树的题目。解题的方法就是每次找到当前最小的两个数字,然后将这两个数字合并成一个数字放到原来的集合里面。每次累加花费即可。所以其实就是要我们构造出一棵哈夫曼树,在每次合并子节点的过程中累加我们得到的结果即可。
解法一 优先队列(小根堆)
在C++里面有一个很好用的STL容器,就是小根堆了,它为我们封装好了堆的一些基本的操作。每次都会自动调整这个堆,使得最小值在堆顶,我们可以直接O(1)取堆顶的数字即可。
代码如下
- 需要遍历n个数字,每次堆都会进行一次调整。调整的时间复杂度为,所以总的时间复杂度为
- 我们利用小根堆存储了序列。空间复杂度为
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 表示有n个数。 * @param c int整型 参数c * @param a int整型vector ai表示第i个数的大小 * @return long长整型 */ long long solve(int n, int c, vector<int>& a) { // write code here // 定义一个小根堆 priority_queue<long long,vector<long long>,greater<long long> > q; for(auto x:a){ q.push(x); } long long ans=0; while(1){ // 如果此时堆中的元素的个数小于2,那么得到了最后的结果 if(q.size()==1) break; // 取出堆顶的两个最小的数字 long long tmp1=q.top(); q.pop(); long long tmp2=q.top(); q.pop(); // 组合成一个新的数字 ans+=c*(tmp1+tmp2); // 新组成的数字入堆 q.push(tmp1+tmp2); } return ans; } };
解法二 (map红黑树)
- 我们平时经常用map来进行离散化,但是可能很少有人注意到我们map内部其实是可以进行排序的,它的内部是红黑树实现。按照键的大小进行排序的。所以我们可以利用这个性质解决本题。我们先将数字都存入map里面。然后我们可以每次取出map指针第一个位置的元素,取出这个元素。如果这个元素取完了,那么在map里面删除这个元素即可。
- 代码如下
- 每次map里面的元素变动都会导致map进行重新调整,时间复杂度为
- 利用map存储元素的值,空间复杂度为
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 表示有n个数。 * @param c int整型 参数c * @param a int整型vector ai表示第i个数的大小 * @return long长整型 */ typedef long long ll; ll get(map<ll,ll> &mp){ auto x=*mp.begin(); int y=x.first; mp[y]--; //取出一个 if(mp[y]==0){ // 如果此时的数字取完了,那么删除这个数字 mp.erase(mp.find(y)); } return y; } long long solve(int n, int c, vector<int>& a) { // write code here map<ll,ll> mp; for(auto x:a){ mp[x]++; } int cnt=0; // 记录消除了多少个数字 ll ans=0; while(cnt<n-1){ int x=get(mp); // 获得map里面的键最小的数字 int y=get(mp); // 获得map里面的键最小的数字 ans+=c*(x+y); mp[(x+y)]++; cnt++; } return ans; } };