唱彻《阳关》泪未干,功名馀事且加餐。浮天水送无穷树,带雨云埋一半山。
今古恨,几千般,只应离合是悲欢。江头未是风波恶,别有人间行路难!
题目描述
leetcode链接: https://leetcode-cn.com/problems/reverse-pairs/
给定一个数组 n u m s nums n u m s ,如果 i < j i < j i < j 且 n u m s [ i ] > 2 ∗ n u m s [ j ] nums[i] > 2*nums[j] n u m s [ i ] > 2 ∗ n u m s [ j ] 我们就将 ( i , j ) (i, j) ( i , j ) 称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。
示例1
输入 :[1,3,2,3,1]
输出 :2
示例2
输入 :[2,4,3,5,1]
输出 :3
问题分析
本题宜采用分治算法。对于数组n u m s nums n u m s ,将其分为两半,不妨令左半段为l e f t left l e f t ,右半段为r i g h t right r i g h t 。
最终结果为l e f t left l e f t 、r i g h t right r i g h t 中的翻转对数量之和,加上横跨l e f t left l e f t 和r i g h t right r i g h t 的翻转对数量。这里的横跨指的是n u m s [ i ] nums[i] n u m s [ i ] 位于l e f t left l e f t 中,n u m s [ j ] nums[j] n u m s [ j ] 位于r i g h t right r i g h t 中。
我们发现,对于横跨的翻转对数量来说,无论l e f t left l e f t 和r i g h t right r i g h t 中的元素顺序如何变化,只要l e f t left l e f t 中的元素仍然在l e f t left l e f t 中,r i g h t right r i g h t 中的元素仍然在r i g h t right r i g h t 中,那么横跨的翻转对数量就不会受到影响。
由上面的发现,我们在递归求得l e f t left l e f t 和r i g h t right r i g h t 的翻转对过程中,可以顺便将其排序,方便后面求横跨l e f t left l e f t 和r i g h t right r i g h t 的翻转对数量。
现在,我们得到了算法的大致框架,如下所示
首先将数组n u m s nums n u m s 分为两半,分别为l e f t left l e f t 和r i g h t right r i g h t
递归求得l e f t left l e f t 和r i g h t right r i g h t 中的翻转对数量,该过程会顺便对l e f t left l e f t 和r i g h t right r i g h t 进行排序。
求横跨l e f t left l e f t 和r i g h t right r i g h t 的翻转对,并将l e f t left l e f t 和r i g h t right r i g h t 合并成一个有序的数组。
接下来讨论如何求横跨l e f t left l e f t 和r i g h t right r i g h t 的翻转对数量。
l e f t left l e f t 数组是有序的,对于r i g h t right r i g h t 中任意一个元素r i g h t [ j ] right[j] r i g h t [ j ] ,如果l e f t [ i ] left[i] l e f t [ i ] 与其构成翻转对,那么l e f t [ i ] left[i] l e f t [ i ] 之后的元素一定与其构成翻转对,因此我们的结果可以直接加上l e f t [ i ] left[i] l e f t [ i ] 后面的元素个数。
同样的,对于r i g h t right r i g h t 中的任意元素r i g h t [ j ] right[j] r i g h t [ j ] ,如果l e f t [ i ] left[i] l e f t [ i ] 不与其构成翻转对关系,则l e f t [ i ] left[i] l e f t [ i ] 之前的所有元素都不与其构成翻转对关系,因此,我们不需要再判断l e f t [ i ] left[i] l e f t [ i ] 之前的元素。
通过上面的结论,我们可以构造出如下算法:
定义两个索引i i i 、j j j ,分别用于l e f t left l e f t 和r i g h t right r i g h t ,并分别初始化为l e f t left l e f t 和r i g h t right r i g h t 的起始位置。
当l e f t [ i ] left[i] l e f t [ i ] 和r i g h t [ j ] right[j] r i g h t [ j ] 不满足翻转对关系时,增加i i i ,直到满足翻转对关系或者i i i 超出l e f t left l e f t 范围。
当l e f t [ i ] left[i] l e f t [ i ] 和r i g h t [ j ] right[j] r i g h t [ j ] 满足翻转对关系时,假设l e f t [ i ] left[i] l e f t [ i ] 后面还有k k k 个元素,那么我们就找到了k k k 个翻转对,于是结果要加上k k k 。然后增加j j j 。
重复中间两步,直到i i i 或者j j j 超出范围。
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 pub fn reverse (nums: &mut [i32 ]) -> i32 { if nums.len () <= 1 { return 0 ; } let mid = (nums.len () - 1 ) / 2 ; let t1 = reverse (&mut nums[..mid + 1 ]); let t2 = reverse (&mut nums[mid + 1 ..]); let mut left = 0 ; let mut right = mid + 1 ; let mut ret = t1 + t2; while left <= mid && right < nums.len () { while left <= mid && nums[left] as i64 <= 2 * nums[right] as i64 { left += 1 ; } if left > mid { break ; } while right < nums.len () && nums[left] as i64 > 2 * nums[right] as i64 { ret += (mid - left + 1 ) as i32 ; right += 1 ; } } left = 0 ; right = mid + 1 ; let mut hold : Vec <i32 > = Vec ::new (); while left <= mid && right < nums.len () { if nums[left] < nums[right] { hold.push (nums[left]); left += 1 ; } else { hold.push (nums[right]); right += 1 ; } } while left <= mid { hold.push (nums[left]); left += 1 ; } while right < nums.len () { hold.push (nums[right]); right += 1 ; } for i in 0 ..nums.len () { nums[i] = hold[i]; } ret }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Solution {public : int reversePairs (vector<int >& nums) { return reversePairs (nums, 0 , nums.size () - 1 ); } int reversePairs (vector<int >& nums, int l, int r) { if (l >= r) return 0 ; int mid = (l+r) >> 1 ; int t1 = reversePairs (nums, l, mid); int t2 = reversePairs (nums, mid+1 ,r); int left = l, right = mid+1 ; int ret = t1 + t2; while (left <= mid && right <= r) { while (left <= mid && nums[left] <= 2 * (long long )nums[right]) left++; if (left > mid) break ; int hold = right; while (right <= r && nums[left] > 2 * (long long )nums[right]) { ret += (mid-left+1 ); right++; } } left = l, right = mid+1 ; vector<int > hold; while (left <= mid && right <= r) { hold.push_back ( nums[left] < nums[right] ? nums[left++] : nums[right++] ); } while (left <= mid) { hold.push_back (nums[left++]); } while (right <= r) { hold.push_back (nums[right++]); } for (int i=l,j=0 ; i<=r; i++, j++) { nums[i] = hold[j]; } return ret; } };