算法:归并排序面试题

小和问题

题目描述

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。

举个栗子

{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;
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值