下面简单聊聊leetcode中的两道类似的DC的题目。之所以来写博客,是因为它们的思路确实与以往遇到的简单的DC有所不同。
1. Count of range sum
Given an integer array nums
, return the number of range sums that lie in [lower, upper]
inclusive.
Range sum S(i, j)
is defined as the sum of the elements in nums
between indices i
and j
(i
≤ j
), inclusive.
Note:
A naive algorithm of O(n2) is trivial. You MUST do better than that.
Example:
Given nums =
Return
The three ranges are :
Given nums =
[-2, 5, -1]
,
lower
=
-2
,
upper
=
2
,Return
3
.The three ranges are :
[0, 0]
,
[2, 2]
,
[0, 2]
and their respective sums are:
-2, -1, 2
.
我采用的方法是经典的分治法,即把数组从中间分成两半先求两边的然后合并形成最终结果,这样递归求解。
divide即把原始问题化成了两个比较小的数组中count问题,但是这里conquer并不是特别直观。
divide的时候我们已经把A[l,m]和A[m+1,r]里面满足要求的range都计数了,所以conquer的时候要计数的是横跨这两个数组的满足要求的range,而range的起点可以是l,l+1,...,m,终点可以是m+1,m+2,...,r,最直观的方法去枚举终点和起点,那么这种算法是O(n^2)的,不满足题目要求。
这里的一个小技巧是:如果把横跨两个子数组的range从m分成两半的话,即range=range1+range2,range1一定是以m结尾,range2一定是以m+1开始。那么,我们可以先求输出所有的range1的和,然后去求range2时只需要去range1中找出符合要求的和的数目,而且预先求出来的range1可以先排好序,这样就可以采用二分查找,所以conquer的时间复杂度就是O(nlogn)。
这样总的时间复杂度就是O(n(logn)^2)。
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
if(0 == nums.size()) return 0;
return countRangeSum(nums, 0, nums.size()-1, lower, upper);
}
private:
int countRangeSum(vector<int>& nums, int l, int r, int lower, int upper) {
if(l > r) return 0;
if(l == r) return (lower <= nums[l] && nums[l] <= upper) ? 1 : 0;
int m = l + ((r-l)>>1);
vector<long> sums;
sums.push_back(nums[m]);
for(int i = m-1; i >= l; --i)
sums.push_back(nums[i]+sums.back());
sort(sums.begin(), sums.end());
long tmp = 0, count = 0;
for(int i = m+1; i <= r; ++i)
{
tmp += nums[i];
count += (BinSearch(sums, upper-tmp+0.5) - BinSearch(sums, lower-tmp-0.5));
}
return countRangeSum(nums, l, m, lower, upper) + countRangeSum(nums, m+1, r, lower, upper) + count;
}
int BinSearch(vector<long> &sums, double target) {
int l = 0, r = sums.size() - 1;
while(l <= r)
{
int m = l + ((r-l)>>1);
if(sums[m] > target) r = m-1;
else l = m+1;
}
return r;
}
};
2. Count number of smaller numbers of After self
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i]
is the number of smaller elements to the right of nums[i]
.
Example:
Given nums = [5, 2, 6, 1] To the right of 5 there are 2 smaller elements (2 and 1). To the right of 2 there is only 1 smaller element (1). To the right of 6 there is 1 smaller element (1). To the right of 1 there is 0 smaller element.
Return the array [2, 1, 1, 0]
.
思路类似,这里不再赘述,直接copy代码。
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int> ret;
if(0 == nums.size()) return ret;
ret.assign(nums.size(), 0);
countSmaller(nums, ret, 0, nums.size()-1);
return ret;
}
private:
void countSmaller(vector<int>& nums, vector<int>& ret, int l, int r) {
if(l >= r) return;
if(l+1 == r) {ret[l] += (int)(nums[r] < nums[l]); return;}
int m = l + ((r-l)>>1);
countSmaller(nums, ret, l, m);
countSmaller(nums, ret, m+1, r);
vector<int> tmp;
for(int i = m+1; i <= r; ++i) tmp.push_back(nums[i]);
sort(tmp.begin(), tmp.end());
for(int i = l; i <= m; ++i)
ret[i] += lower_bound(tmp.begin(), tmp.end(), nums[i]-0.5) - tmp.begin();
}
};