题目大意:
给出一个长度为n的整数数组nums,求其中逆对的数目。其中一个逆对(i, j)是指满足条件i<j且nums[i]>2*num[j]的数对。
题目保证输入的n<=5e4。
解法:
可以利用线段树来求解,因为其中涉及了批量区间赋值和批量区间加总。首先由于nums中数值范围是32位整数,其范围过大,无法直接使用线段树,所以先对其进行压缩。压缩的过程就是对nums进行排序得到排序完成的新数组ordered,之后从ordered中移除重复元素,之后建立与范围为[0, ordered.length - 1]的线段树,而线段树的空间复杂度为O(n)。线段树中[x, y]区间需要记录属性s,且[x, y].s = [x, x].s + [x + 1, x + 1].s + ... +[y, y].s,以及为了性能考虑需要添加一个肮脏标志[x, y].d,用于缓存对整个[x, y]区间的修改。且[x, x].s表示ordered[x]元素在nums[i]左方出现的次数,而我们需要的是对i做从0到n的迭代。每次迭代首先利用二分查找法在ordered中找到最小的元素m,使得m>nums[i]*2,记其在ordered中的下标为M,我们只需要查找线段树中区间[M, ordered.length-1].s,其表示在nums[i]左方出现的所有大于等于m的元素数目,也就是要累加到结果中的值;之后利用二分查找法找到ordered中nums[i]出现的下标P,并令线段树中区间[P, P]加1即可,这一步为之后的步骤做服务。
时间复杂度=对nums排序的时间复杂度+从ordered中移除重复元素的时间复杂度+建立线段树的时间复杂度+n*(从线段树中查找区间s的时间复杂度+更新线段树区间s的时间复杂度+2*从ordered中二分查找元素的时间复杂度)=O(nlog2(n))+O(n)+O(n)+n*O(log2(n))=O(nlog2(n))。
空间复杂度=额外分配的数组ordered的空间复杂度+线段树的空间复杂度=O(n)+O(n)=O(n)。
代码:
1 class Solution { 2 public int reversePairs(int[] nums) { 3 //Sort at first 4 int[] sorted = nums.clone(); 5 Arrays.sort(sorted); 6 7 //Remove duplicated element 8 int orginalLength = sorted.length; 9 int rpos = 1; 10 int wpos = 1; 11 while (rpos < orginalLength) { 12 if (sorted[rpos] != sorted[rpos - 1]) { 13 sorted[wpos++] = sorted[rpos]; 14 } 15 rpos++; 16 } 17 18 SegmentTree tree = new SegmentTree(0, wpos - 1); //tree[i] save how many guy = sorted[i] appear 19 int result = 0; 20 int maxAllowed = Integer.MAX_VALUE >> 1; 21 int minAllowed = Integer.MIN_VALUE >> 1; 22 for (int num : nums) { 23 //x > num * 2 24 25 int guyLargerThan = 0; 26 if (num > maxAllowed) { 27 } else if (num < minAllowed) { 28 guyLargerThan = tree.sumOf(0, wpos); 29 } else { 30 int doubleNum = num << 1; 31 int halfIndex = binarySearch(sorted, 0, wpos, doubleNum); 32 guyLargerThan = tree.sumOf(halfIndex, wpos); 33 } 34 result += guyLargerThan; 35 36 //Add num into tree 37 int numIndex = binarySearch(sorted, 0, wpos, num) - 1; 38 tree.increment(numIndex, numIndex, 1); 39 } 40 return result; 41 } 42 43 public static class SegmentTree { 44 public static class SNode { 45 int left; 46 int right; 47 int flag; //The incremented value for all element in [left, right] 48 int sum; //The sum o 49 SNode leftHalf; //[left, (left + right) / 2] 50 SNode rightHalf; //[(left + right) / 2 + 1, right] 51 } 52 53 SNode root; 54 55 public SegmentTree(int left, int right) { 56 root = build(left, right); 57 } 58 59 private static SNode build(int left, int right) { 60 SNode root = new SNode(); 61 root.left = left; 62 root.right = right; 63 64 int half = (left + right) >> 1; 65 66 if (right > left) { 67 root.leftHalf = build(left, half); 68 root.rightHalf = build(half + 1, right); 69 } 70 71 72 return root; 73 } 74 75 public void increment(int left, int right, int val) { 76 increment(root, left, right, val); 77 } 78 79 private static void increment(SNode node, int left, int right, int val) { 80 if (node.left > right || node.right < left) { 81 return; 82 } 83 if (node.left >= left && node.right <= right) { 84 node.flag += val; 85 node.sum += val * (right - left + 1); 86 return; 87 } 88 increment(node.leftHalf, left, right, val); 89 increment(node.rightHalf, left, right, val); 90 node.sum = node.leftHalf.sum + node.rightHalf.sum + node.flag * (right - left + 1); 91 } 92 93 public int sumOf(int left, int right) { 94 return sumOf(root, left, right); 95 } 96 97 private static int sumOf(SNode node, int left, int right) { 98 left = Math.max(node.left, left); 99 right = Math.min(node.right, right); 100 if (left > right) { 101 return 0; 102 } 103 if (node.left >= left && node.right <= right) { 104 return node.sum; 105 } 106 return sumOf(node.leftHalf, left, right) + sumOf(node.rightHalf, left, right) 107 + node.flag * (right - left + 1); 108 } 109 } 110 111 /** 112 * find the index of the first element in arr which is larger than val or end 113 * 114 * @param arr an ordered array 115 * @param begin from which index 116 * @param end until to which index 117 * @param val the value to be searched 118 * @return the index of the first element in arr which is larger than val or end 119 */ 120 public int binarySearch(int[] arr, int begin, int end, int val) { 121 int left = begin; 122 int right = end; 123 //arr[begin] <= val and arr[end] > val 124 while (left < right) { 125 int mid = (left + right) >> 1; 126 if (arr[mid] > val) { 127 right = mid; 128 } else { 129 left = mid + 1; 130 } 131 } 132 return left; 133 } 134 }