第五章 优化时间和空间效率
1. 时间效率
字符串多次拼接时,不要多次使用String的+来拼接字符串,这样会产生很多string临时实例。更好地是用append方法完成字符串拼接。s1.append(s2)
把字符串s2拼接到s1后面。
查找:顺序查找需要O(n),排序数组需要O(logn),哈希表则是O(1)。
剑指 Offer 39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
初始版本:将原数组排序,由于所求数超过数组长度地一半,所以中位数必为所求数。sort复杂度O(n)。可以利用快速排序或者快速选择替代sort。
int majorityElement(vector<int>& nums) {
int n = nums.size();
if(n == 1) return nums.front();
sort(nums.begin(),nums.end());
return nums[n/2];
}
优化:根据数组特点找出O(n)的算法。遍历数组的时候保存前一个遍历地数字和频次。当我们遍历到当前数字的时候,如果当前数字和我们之前保存的数字相同,则频次+1;如果当前数字和之前保存的数字不同,则频次+1。如果频次为零,我们需要保存当前数组,并把频次设为1。
由于我们要找的数字出现的频次比其他所有数字出现的频次之和还要多,那么要找的数字肯定是最后一次把频次设为1时对应的数字。
int majorityElement(vector<int>& nums) {
if(nums.size() == 1) return nums.front();
int res = nums[0],times = 1;//遍历数组的时候保存遍历地数字和频次
for(int i = 1;i < nums.size();++i){
if(nums[i] == res) ++times;//如果当前数字和我们之前保存的数字相同,则频次+1
else{
--times;//如果当前数字和之前保存的数字不同,则频次-1。
if(!times){
res = nums[i];times = 1;//如果频次为零,我们需要保存当前数字,并把频次设为1
}
}
}
return res;
}
剑指 Offer 40. 最小的k个数
输入整数数组 arr
,找出其中最小的 k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
该算法适 合海量数据的输入。假设题目是要求从海量的数据中找出最小的k个数 字,由于内存的大小是有限的,有可能不能把这些海量的数据一次性全部载入内存。这个时候,我们可以从辅助存储空间(比如硬盘)中每次读入一个数字,因此它最适合的情形就是n很大并且k较小的问题。
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(!k) return vector<int>{};
if(arr.size() <= k) return arr;//如果数组内的元素个数小于k,直接全部输出
priority_queue<int> pq;
vector<int> res;
for(int i = 0;i < k; ++i){//先将前k个放入大根堆中
pq.push(arr[i]);
}
for(int i = k;i < arr.size();++i){//如果待插入的值比当前已有的最大值小,则用这个数替换当前最大值
if(arr[i] < pq.top()){
pq.pop();
pq.push(arr[i]);
}
}
for(int i = 0;i < k; ++i){
res.emplace_back(pq.top());
pq.pop();
}
return res;
}
剑指 Offer 41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,[2,3,4] 的中位数是 3。[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
整个数据被中间一个或者两个数据分隔成两部分。左边的数据全部小于右边的数据。可以根据左边最大的数和右边最小的数得到中位数。所以将左边的数存入大根堆,右边的数存入小根堆。那么插入一个数的复杂度是O(logn),得到中位数复杂度是O(1)。
为了实现平均分配,当已有数据的总数目是偶数时,把新数据插入左边,也就是大根堆。当已有数据的总数目是奇数时,把新数据插入右边,即小根堆。插入过程中,如果新数据要插入左边,但新数据大于左边数据的最大值,那么把新数据插入右边,再把右边小根堆顶最小值删除,并插入左边。如果新数据要插入右边,但新数据小于右边数据的最小值,那么把新数据插入左边,再把左边大根堆顶最大值删除,并插入右边。