小和问题
题目描述
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。
举个栗子
{1,3,4,2,5}
1左边比1小的数,没有
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16
题目解析
第一种思路就是暴力递归:
- 使用一个指针,对于每一个 n u m s [ i ] nums[i] nums[i],往左看,把比它小的数加起来,最终得到结果
- 这种方法时间复杂度为O(N^2)。
这样时间复杂度太高了,所以我们可以转换一个思路
- 题目要求找左边数比它小的数字相加,那么我们可以转变为看看右边有多少个数字比它大(假设为n),表示该数在小和的计算中出现了n次,最后相加即可(可以通过归并排序在进行排序的同时拿到它的小和)
- 即1右边比1大的数有4个,3右边比3大的数有2个,4右边比4大的数有1个,2右边比2大的数有1个,5右边没有数。小和=14+32+41+21=16
- 同时判断右边比左边大的数的多少是通过排序然后计算数组得到的。
另外,如果合并的时候相等怎么办?
这个为什么快呢?因为在求小和的过程中后边子数组已经是有序的了,只要求出它的长度,在乘以当前数组的长度就可以求出小和的大小了,而不需要再遍历数组。
#include <iostream>
#include <vector>
#include <memory>
#include <list>
#include <random>
using namespace std;
int merge(std::vector<int> & nums, int left, int mid, int right){
int ans = 0;
std::vector<int> help(right - left + 1);
int p1 = left, p2 = mid + 1, i = 0;
while (p1 <= mid && p2 <= right){
ans += nums[p1] < nums[p2] ? (right - p2 + 1) * nums[p1] : 0;
help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
}
while (p1 <= mid){
help[i++] = nums[p1++];
}
while (p2 <= right){
help[i++] = nums[p2++];
}
i = 0;
for (int j = left; j <= right; ++j) {
nums[j] = help[i++];
}
return ans;
}
// arr[L..R]既要排好序,也要求小和返回
// 所有merge时,产生的小和,累加
// 左 排序 merge
// 右 排序 merge
// merge
int process(std::vector<int> & nums, int left, int right){
if(left == right){
return 0;
}
int mid = left + (right - left) / 2;
return process(nums, left, mid) +
process(nums, mid + 1, right) +
merge(nums, left, mid, right);
}
int smallSum(std::vector<int> &nums){
if(nums.empty()){
return 0;
}
return process(nums, 0, (int)nums.size() - 1);
}
int comparator(std::vector<int> &nums){
if(nums.empty() || nums.size() < 2){
return 0;
}
int res = 0;
int size = nums.size();
for (int i = 1; i < size; ++i) {
for (int j = 0; j < i; ++j) {
res += (nums[j] < nums[i]) ? nums[j] : 0;
}
}
return res;
}
std::default_random_engine e;
std::vector<int> gen_randomArray(int maxLen, int minValue, int maxValue){
std::uniform_int_distribution<int> distS(0, maxLen);
std::uniform_int_distribution<int> distV(minValue, maxValue);
int size = distS(e);
std::vector<int> result(size);
for (int i = 0; i < size - 1; ++i) {
result[i] = distV(e);
}
return result;
}
std::vector<int> copy_Array(std::vector<int> &nums){
std::vector<int> res;
res.reserve(nums.size());
for(auto num : nums){
res.emplace_back(num);
}
return res;
}
void printVec(std::vector<int> & nums){
for(auto num : nums){
std::cout << num << "\t";
}
std::cout << "\n\n";
}
int main() {
e.seed(time(NULL));
int maxLen = 10;
int minValue = 0;
int maxValue = 10;
bool success = true;
for (int i = 0; i < 100; ++i) {
auto num1 = gen_randomArray(maxLen, minValue, maxValue);
auto num2 = copy_Array(num1);
int a = smallSum(num1);
int b = comparator(num2);
if(a != b){
success = false;
std::cout << a << "," << b << "\n";
break;
}
}
if(success){
std::cout << "ok!";
}else{
std::cout << "error!";
}
return 0;
}
小结:
- 左组的数小于右组的数时,产生小和,左指针右移;
- 左组的数等于右组时,直接拷贝右组,不产生小和;
- 左组的数大于右组时,直接拷贝右移,不产生小和。
逆序对
题目来源
题目描述
例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
题目解析
和上面的那道题类似,上面是看右边有多少个数字比它大;这里是,转变为看看右边有多少个数字比它小的个数,或者说,左边有多少个数比它大。
#include <iostream>
#include <vector>
#include <memory>
#include <list>
#include <random>
using namespace std;
int merge2(std::vector<int> & nums, int left, int mid, int right){
int ans = 0;
std::vector<int> help(right - left + 1);
int p1 = mid, p2 = right, i = (int)help.size() - 1;
while (p1 >= left && p2 >= mid + 1 ){
ans += nums[p2] >= nums[p1] ?
0 : // 右边的更大一些当然不产生逆序对
(p2 - (mid + 1) + 1);
help[i--] = nums[p2] >= nums[p1] ? nums[p2--] : nums[p1--];
}
while (p1 >= left){
help[i--] = nums[p1--];
}
while (p2 >= mid + 1){
help[i--] = nums[p2--];
}
i = 0;
for (int j = left; j <= right; ++j) {
nums[j] = help[i++];
}
return ans;
}
int merge(std::vector<int> & nums, int left, int mid, int right){
int ans = 0;
std::vector<int> help(right - left + 1);
int p1 = left, p2 = mid + 1, i = 0;
while (p1 <= mid && p2 <= right){
ans += nums[p1] <= nums[p2] ? 0 : mid - p1 + 1 ;
help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
}
while (p1 <= mid){
help[i++] = nums[p1++];
}
while (p2 <= right){
help[i++] = nums[p2++];
}
i = 0;
for (int j = left; j <= right; ++j) {
nums[j] = help[i++];
}
return ans;
}
// arr[L..R]既要排好序,也要求小和返回
// 所有merge时,产生的小和,累加
// 左 排序 merge
// 右 排序 merge
// merge
int process(std::vector<int> & nums, int left, int right){
if(left == right){
return 0;
}
int mid = left + (right - left) / 2;
return process(nums, left, mid) +
process(nums, mid + 1, right) +
merge2(nums, left, mid, right);
}
int reversePairs(std::vector<int> &nums){
if(nums.empty()){
return 0;
}
return process(nums, 0, (int)nums.size() - 1);
}
int comparator(std::vector<int> &nums){
if(nums.empty() || nums.size() < 2){
return 0;
}
int res = 0;
int size = nums.size();
for (int i = 0; i < size - 1; ++i) {
for (int j = i + 1; j < size; ++j) {
res += nums[i] > nums[j] ? 1 : 0;
}
}
return res;
}
std::default_random_engine e;
std::vector<int> gen_randomArray(int maxLen, int minValue, int maxValue){
std::uniform_int_distribution<int> distS(0, maxLen);
std::uniform_int_distribution<int> distV(minValue, maxValue);
int size = distS(e);
std::vector<int> result(size);
for (int i = 0; i < size - 1; ++i) {
result[i] = distV(e);
}
return result;
}
std::vector<int> copy_Array(std::vector<int> &nums){
std::vector<int> res;
res.reserve(nums.size());
for(auto num : nums){
res.emplace_back(num);
}
return res;
}
void printVec(std::vector<int> & nums){
for(auto num : nums){
std::cout << num << "\t";
}
std::cout << "\n\n";
}
int main() {
e.seed(time(NULL));
int maxLen = 10;
int minValue = 0;
int maxValue = 10;
bool success = true;
for (int i = 0; i < 100; ++i) {
auto num1 = gen_randomArray(maxLen, minValue, maxValue);
auto num2 = copy_Array(num1);
int a = reversePairs(num1);
int b = comparator(num2);
if(a != b){
success = false;
std::cout << a << "," << b << "\n";
break;
}
}
if(success){
std::cout << "ok!";
}else{
std::cout << "error!";
}
return 0;
}
翻转对
题目来源
题目描述
人话就是:在一个数组中,对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数。比如:[3,1,7,0,2],3的后面有:1,0;1的后面有:0;7的后面有:0,2;0的后面
题目解析
#include <iostream>
#include <vector>
#include <memory>
#include <list>
#include <random>
using namespace std;
int merge(std::vector<int> & nums, int left, int mid, int right){
int ans = 0;
int windowL = mid , windowR = mid + 1; // [windowL, windowR)
for (int i = left; i <= mid; ++i) {
// 判断windowsR是否可以纳入
while (windowR <= right && (long)nums[i] > (long)2 * nums[windowR]){
windowR++;
}
ans += (windowR - 1 ) - windowL;
}
std::vector<int> help(right - left + 1);
int p1 = left, p2 = mid + 1, i = 0;
while (p1 <= mid && p2 <= right){
help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
}
while (p1 <= mid){
help[i++] = nums[p1++];
}
while (p2 <= right){
help[i++] = nums[p2++];
}
i = 0;
for (int j = left; j <= right; ++j) {
nums[j] = help[i++];
}
return ans;
}
int process(std::vector<int> & nums, int left, int right){
if(left == right){ // 递归的出口:不能再二分了,返回
return 0;
}
int mid = left + (right - left) / 2;
return process(nums, left, mid) +
process(nums, mid + 1, right) +
merge(nums, left, mid, right);
}
int reversePairs(std::vector<int> &nums){
if(nums.empty()){
return 0;
}
return process(nums, 0, (int)nums.size() - 1);
}
int comparator(std::vector<int> &nums){
if(nums.empty() || nums.size() < 2){
return 0;
}
int res = 0;
int size = nums.size();
for (int i = 0; i < size - 1; ++i) {
for (int j = i + 1; j < size; ++j) {
res += nums[i] > 2 * nums[j] ? 1 : 0;
}
}
return res;
}
std::default_random_engine e;
std::vector<int> gen_randomArray(int maxLen, int minValue, int maxValue){
std::uniform_int_distribution<int> distS(0, maxLen);
std::uniform_int_distribution<int> distV(minValue, maxValue);
int size = distS(e);
std::vector<int> result(size);
for (int i = 0; i < size - 1; ++i) {
result[i] = distV(e);
}
return result;
}
std::vector<int> copy_Array(std::vector<int> &nums){
std::vector<int> res;
res.reserve(nums.size());
for(auto num : nums){
res.emplace_back(num);
}
return res;
}
void printVec(std::vector<int> & nums){
for(auto num : nums){
std::cout << num << "\t";
}
std::cout << "\n\n";
}
int main() {
e.seed(time(NULL));
int maxLen = 10;
int minValue = 0;
int maxValue = 10;
bool success = true;
for (int i = 0; i < 100; ++i) {
auto num1 = gen_randomArray(maxLen, minValue, maxValue);
auto num2 = copy_Array(num1);
printVec(num1);
int a = reversePairs(num1);
int b = comparator(num2);
if(a != b){
success = false;
std::cout << "get [" << a << "] ,but should [" << b << "]\n";
break;
}
}
if(success){
std::cout << "ok!";
}else{
std::cout << "error!";
}
return 0;
}