Thoughts about General principles behind problems similar to "Reverse Pairs"

https://leetcode.com/problems/reverse-pairs/discuss/97268/General-principles-behind-problems-similar-to-%22Reverse-Pairs%22
such problems like:
315. Count of Smaller Numbers After Self
327. Count of Range Sum
493. Reverse Pairs
and this article will use LC493 as example.
Prtoblems that can be solved in BST, BIT, Segment Tree, MergeSort based idea. so this article is gonna to focused on the general principles behind these solutions and its possible application to a number of similar problems…

The fundamental idea is very simple: break down the array and solve for the subproblems.
but break down an array means subarrays, so the subproblem will be: let’s assume T(0, len - 1) is the results of own whole problem, so T(i,j) will denote as the total number of importatn reverse pair of subarray nums[i.j]
While there may be many ways for establishing recurrence relations for T(i, j), here I will only introduce the following two common ones:

  1. T(i, j) = T(i, j - 1) + C, i.e., elements will be processed sequentially and C denotes the subproblem for processing the last element of subarray nums[i, j]. We will call this sequential recurrence relation.
    2 . T(i, j) = T(i, m) + T(m + 1, j) + C where m = (i+j)/2, i.e., subarray nums[i, j] will be further partitioned into two parts and C denotes the subproblem for combining the two parts. We will call this partition recurrence relation.

Based on those two recusrion ways, let’s talk them one by one.
1.T(i, j) = T(i, j - 1) + C, C代表在nums[0] ~ nums[j-1] 中 有多少个满足大于2*nums[j]的。我们当然可以从0扫到j-1,但是这样不够高效。我们注意到此时nums的顺序已经不再重要,因此我们可以进行presort,然后用二分法查找这个区间。二分法的确是提高了效率,但是每一次j往后移 我们都要对前面的做一次presortt+二分查找操作吗?答案当然不是,我们要找到一种很好的数据结构来维持insert和search的平衡,而这种就可以通过BST或者BIT来实现。

public int reversePairs(int[] nums) {
    int res = 0;
    Node root = null;
    	
    for (int ele : nums) {
        res += search(root, 2L * ele + 1); //这也是为什么我们可以一边查找 一边更新
        root = insert(root, ele);
    }
    
    return res;
}

2.T(0, n - 1) = T(0, m) + T(m + 1, n - 1) + C
此处的C就代表了 nums[0, m]里面的元素 有多少是满足大于2倍的nums[m+1,n-1]里面的某元素的。当然 我们可以通过挨个扫一遍来检查 但是那样效率太低。就像之前一样,因为各自内部顺序不再重要 所以我们可以进行presort,然后我们只需要扫O(m+n)就可以得出答案(m是前半段长度,n是后半段长度)
在这里先停一停,仔细想一下前面的这段话让你想起了什么?没错 就是让你想起了merge sort的 partition和sort的阶段。

public int reversePairs(int[] nums) {
    return reversePairsSub(nums, 0, nums.length - 1);
}
    
private int reversePairsSub(int[] nums, int l, int r) {
    if (l >= r) return 0;
        
    int m = l + ((r - l) >> 1);
    int res = reversePairsSub(nums, l, m) + reversePairsSub(nums, m + 1, r);
     //接下来的代码块 就是在扫面前后两个区段,来找表达式中的C的值
    int i = l, j = m + 1, k = 0, p = m + 1;
    int[] merge = new int[r - l + 1];
        
    while (i <= m) {
        while (p <= r && nums[i] > 2 L * nums[p]) p++;
        res += 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++];
        
    System.arraycopy(merge, 0, nums, l, merge.length);
        
    return res;
}

最后总结一下:
很多有关于数组的问题 都能被break down成子问题,然后得出子问题和原问题之间的表达式 用递归或者地推进行实现。而子问题的原问题表达式中的关键元素C,也是一个最小快的子问题,很好解决。但是要注意 如果在解C的过程中,涉及到搜索动态区域,那么我们就可以考虑用一些特殊的数据结构,比如说BST,BIT,Segment Tree等等。
LC315 Count of smaller numbers sfter self:
if we are applying the T[i,j] = T[i+1, j] +C, C will be find the number of elements out of visited ones that are smaller than current element
and if we are applying for the second way, C will become: for each element in the left half, find the number of elements in the right half that are smaller than it,
LC327: Count of Range Sum:
if we are applying the first one, then, C: find the number of elements out of visited ones that are within the given range
and the second will be for each element in the left half, find the number of elements in the right half that are within the given range

posted @ 2020-05-06 07:53  EvanMeetTheWorld  阅读(17)  评论(0)    收藏  举报