LeetCode week 1 : Reverse Pairs(493)

题目:https://leetcode.com/problems/reverse-pairs/description/

题目类别:分治法
题目难度:hard
题目描述:

Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].

 You need to return the number of important reverse pairs in the given array.

Example1:

Input: [1,3,2,3,1]
Output: 2

Example2:

Input: [2,4,3,5,1]
Output: 3

Note:

  1. The length of the given array will not exceed 50,000.
  2. All the numbers in the input array are in the range of 32-bit integer.

题目分析:

对于给定的数组nums,满足i < j 且nums[i] > 2 * nums[j]的数对称为重要逆序对,需要求出重要逆序对的总对数。对此我们采用分治法去解决,定义T(i, j)为求出i到j之间的重要逆序对,我们有两种分治策略:

1.顺序递推(sequential recurrence):

T(i, j) = T(i, j - 1) + C

将求出i到j之间的重要逆序对划分为两个子问题:

  • T(i, j - 1):求出i到j-1之间的重要逆序对
  • C:求出num[i, j-1]中大于2 * nums[j]与num[j]所形成的重要逆序对
两个子问题的合并也很简单,将两个子问题求得的数量相加即可。对于此种策略可以采用BIT和BST两种数据结构实现:

BST(binary search tree):

此方法虽然比暴力求解快很多,但是因为BST不是自平衡的,所以仍然无法保证O(nlogn)的时间复杂度,可考虑使用

红黑树或AVL树,下面为普通BST的实现。


class Node {
public:
    int val;
    int cnt;  //the total number of elements in the subtree rooted at current node that are greater than or equal to 'val'
    Node *left, *right;
    Node(int val) {
        this->val = val;
        this->cnt = 1;
        left = NULL;
        right = NULL;
    }
    Node() {
        left = NULL;
        right = NULL;
    }
};

class Solution {
private:
    //insert node correspond to value to tree
    Node* insert(Node * root, int val) {
        if (root == NULL) {
            root = new Node(val);
        } else if (val == root->val) {
            root->cnt++;
        } else if (val < root->val) {
            root->left = insert(root->left, val);
        } else {
            root->cnt++;
            root->right = insert(root->right, val);
        }

        return root;
    }
    
    //search the count of value which is no smaller than 'val' in the tree 'root'
    int search(Node * root, long val) {
        if (root == NULL) {
            return 0;
        } else if (val == root->val) {
            return root->cnt;
        } else if (val < root->val) {
            return root->cnt + search(root->left, val);
        } else {
            return search(root->right, val);
        }
    }
public:
    //calculate the number of pairs while construct the tree
    int reversePairs(vector<int>& nums) {
        int count = 0;    //
        Node* root = new Node;

        for (auto ele : nums) {
            count = count + search(root, 2L * ele + 1); //'2L' to avoid overflow
            root = insert(root, ele);
        }
        return count;
    }
    
};

BIT(binary index tree):
此方法时间复杂度为O(nlogn),BIT介绍参看:http://www.cnblogs.com/whensean/p/6851018.html
class Solution {
public:
    int search(vector<int>& bit, int i) {
        int sum = 0;

        while (i < bit.size()) {
            sum += bit[i];
            i += i & -i;
        }

        return sum;
    }

    void insert(vector<int>& bit, int i) {
        while (i > 0) {
            bit[i] += 1;
            i -= i & -i;
        }
    }

    int index(vector<int>& arr, long val) {
        int l = 0, r = arr.size() - 1, m = 0;

        while (l <= r) {
            m = l + ((r - l) >> 1);

            if (arr[m] >= val) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }

        return l + 1;
    }
    int reversePairs(vector<int>& nums) {
        int count = 0;
        vector<int> copy(nums);  
        vector<int> bit(copy.size() + 1);

        std::sort(copy.begin(), copy.end());  
        
        for (auto ele : nums) {
            count = count + search(bit, index(copy, 2L * ele + 1));
            insert(bit, index(copy, ele));
        }

        return count;
    }
};

2.划分递推( partition recurrence):

T(i, j) = T(i, m) + T(m + 1, j) + C(m = (i+j)/2)

将求出i到j之间的重要逆序对划分为两个子问题:

  • T(i, m):求出i到m之间的重要逆序对
  • T(m+1, j):求出i到m之间的重要逆序对
  • C:求出num[i, m]中元素与num[m+1, j]中元素所形成的重要逆序对
这种思路的难点在于C,即如何求出子序列num[i, m]中元素与子序列num[m+1, j]中元素所形成的重要逆序对,若一一比较,仍为O(n^2)。但是若左右两个子序列是排过序的,则只需要线性时间即可,因此可考虑结合归并排序对其进行操作,最终时间复杂度为O(nlogn),实现代码如下:
class Solution {
public:
    int reversePairs(vector<int>& nums) {
        return reversePairsSub(nums, 0, nums.size() - 1);
    }
    
    int reversePairsSub(vector<int>& nums, int l, int r) {
        if (l >= r) return 0;

        int m = l + ((r - l) >> 1);
        int count = reversePairsSub(nums, l, m) + reversePairsSub(nums, m + 1, r);

        int i = l, j = m + 1, k = 0, p = m + 1;
        vector<int> merge(r - l + 1);

        while (i <= m) {
            while (p <= r && nums[i] > 2L * nums[p]) p++;
            
            count += p - (m + 1);

            while (j <= r && nums[i] >= nums[j]) merge[k++] = nums[j++];
            
            merge[k++] = nums[i++];
        }

        while (j <= r) merge[k++] = nums[j++];
        
        for(int a = 0; a < merge.size(); a++) {
            nums[l+a] = merge[a];
        }
        
        return count;
    }
};
参考文献:








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值