二维偏序
概念
偏序关系满足:
- 反自反性(Reflexivity): 对于集合中的每个元素,关系都与自身存在。
- 反对称性(Antisymmetry): 如果存在
a<=b && b<=a
,则必须是a==b
- 传递性(Transitivity): 如果存在
a<=b && b<=c
,则必须是a<=c
二维偏序问题涉及对一个二维数据集中元素的排序,其中排序不仅仅依赖于元素自身的大小关系,还可能依赖于元素在不同维度上的关系。
二维偏序是这样一类问题:已知点对的序列(a1,b1), (a2,b2), (a3,b3)...
,
并在其上定义某种偏序关系<
,现有点(ai,bi)
,求满足(aj,bj) < (ai,bi)
的的数量。
二维偏序问题一般是要使其中的一个维度有序,再通过树状数组的方式处理另一个维度
给定一个数组 nums
,如果 i < j
且 nums[i] > 2*nums[j]
我们就将 (i, j)
称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。
示例 1:
示例 2:
注意:
- 给定数组的长度不会超过
50000
。
- 输入数组中的所有数字都在32位整数的表示范围内。
树状数组
题目要求的第一个维度:i < j
,我们只要按顺序(或者逆序)遍历数组,那么这个维度就已经是有序的,不需要处理了。
所以我们考虑第二个维度,我们使用树状数组add
之前出现过的数nums[i]
,用query
获取满足nums[i] > 2*nums[j]
条件的数
(ps:这题的条件似乎并不满足偏序的第一个性质:反自反性,但是很适合这样的解法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class FenwickTree { int[] tr; public FenwickTree(int n) { this.tr = new int[n+1]; } public int lowbit(int x) { return x & (-x); } public void add(int i, int val) { while (i < tr.length) { tr[i] += val; i += lowbit(i); } } public int query(int i) { int res = 0; while (i > 0) { res += tr[i]; i -= lowbit(i); } return res; } }
|
如果是顺序遍历数组,那么我们就按照nums[i] > 2*nums[j]
条件query
即可,这里求的就是后缀了。同时注意这里因为乘2,所以需要用Long
保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public int reversePairs(int[] nums) { int n = nums.length, res = 0; TreeSet<Long> set = new TreeSet<>(); for (int i = 0; i < n; i++) { set.add((long)nums[i]); set.add(2L*nums[i]); } int idx = 0; Map<Long, Integer> map = new HashMap<>(); for (Long x : set) map.put(x, ++idx); FenwickTree tr = new FenwickTree(set.size()); for (int i = 0; i < n; i++) { res += tr.query(set.size()) - tr.query(map.get(2L*nums[i])); tr.add(map.get((long)nums[i]), 1); } return res; } }
|
如果是逆序遍历数组,那么query
的条件就是nums[j] < nums[i]/2
,需要注意这里除2的取整问题,正数除2是向下取整,负数除2是向上取整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public int reversePairs(int[] nums) { int n = nums.length, res = 0; TreeSet<Integer> set = new TreeSet<>(); for (int i = 0; i < n; i++) { set.add(nums[i]); set.add(nums[i]/2); set.add(nums[i]/2-1); } int idx = 0; Map<Integer, Integer> map = new HashMap<>(); for (Integer x : set) map.put(x, ++idx); FenwickTree tr = new FenwickTree(set.size()); for (int i = n-1; i >= 0; i--) { if (nums[i] >= 0 && nums[i] % 2 == 1) idx = nums[i]/2; else idx = nums[i]/2 - 1; res += tr.query(map.get(idx)); tr.add(map.get(nums[i]), 1); } return res; } }
|
给你两个长度为 n
、下标从 0 开始的整数数组 nums1
和 nums2
,另给你一个下标从 1 开始的二维数组 queries
,其中 queries[i] = [xi, yi]
。
对于第 i
个查询,在所有满足 nums1[j] >= xi
且 nums2[j] >= yi
的下标 j
(0 <= j < n)
中,找出 nums1[j] + nums2[j]
的 最大值 ,如果不存在满足条件的 j
则返回 -1 。
返回数组 answer
*,*其中 answer[i]
是第 i
个查询的答案。
示例 1:
1 2 3 4 5 6 7
| 输入:nums1 = [4,3,1,2], nums2 = [2,4,9,5], queries = [[4,1],[1,3],[2,5]] 输出:[6,10,7] 解释: 对于第 1 个查询:xi = 4 且 yi = 1 ,可以选择下标 j = 0 ,此时 nums1[j] >= 4 且 nums2[j] >= 1 。nums1[j] + nums2[j] 等于 6 ,可以证明 6 是可以获得的最大值。 对于第 2 个查询:xi = 1 且 yi = 3 ,可以选择下标 j = 2 ,此时 nums1[j] >= 1 且 nums2[j] >= 3 。nums1[j] + nums2[j] 等于 10 ,可以证明 10 是可以获得的最大值。 对于第 3 个查询:xi = 2 且 yi = 5 ,可以选择下标 j = 3 ,此时 nums1[j] >= 2 且 nums2[j] >= 5 。nums1[j] + nums2[j] 等于 7 ,可以证明 7 是可以获得的最大值。 因此,我们返回 [6,10,7] 。
|
示例 2:
1 2 3
| 输入:nums1 = [3,2,5], nums2 = [2,3,4], queries = [[4,4],[3,2],[1,1]] 输出:[9,9,9] 解释:对于这个示例,我们可以选择下标 j = 2 ,该下标可以满足每个查询的限制。
|
示例 3:
1 2 3
| 输入:nums1 = [2,1], nums2 = [2,3], queries = [[3,3]] 输出:[-1] 解释:示例中的查询 xi = 3 且 yi = 3 。对于每个下标 j ,都只满足 nums1[j] < xi 或者 nums2[j] < yi 。因此,不存在答案。
|
提示:
nums1.length == nums2.length
n == nums1.length
1 <= n <= 10^5
1 <= nums1[i], nums2[i] <= 10^9
1 <= queries.length <= 10^5
queries[i].length == 2
xi == queries[i][1]
yi == queries[i][2]
1 <= xi, yi <= 10^9
排序+树状数组
这题就需要先排序来确保第一个维度是有序的,再通过树状数组处理第二个维度
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 54 55 56 57 58 59 60 61 62 63
| class FenwickTree { int[] tr; public FenwickTree(int n) { this.tr = new int[n]; Arrays.fill(tr, -1); } public int lowbit(int x) { return x & (-x); } public void add(int i, int val) { while (i < tr.length) { tr[i] = Math.max(tr[i], val); i += lowbit(i); } } public int query(int i) { int res = -1; while (i > 0) { res = Math.max(res, tr[i]); i -= lowbit(i); } return res; } } class Solution {
public int[] maximumSumQueries(int[] nums1, int[] nums2, int[][] queries) { int m = queries.length, n = nums1.length, res[] = new int[m]; Arrays.fill(res, -1); Integer qidxs[] = new Integer[m], idxs[] = new Integer[n]; Arrays.setAll(qidxs, i -> i); Arrays.setAll(idxs, i -> i); Arrays.sort(idxs, (j, i) -> nums1[i] - nums1[j]); Arrays.sort(qidxs, (j, i) -> queries[i][0] - queries[j][0]);
Set<Integer> set = new HashSet<>(); for (int x : nums2) set.add(x); for (int[] q : queries) set.add(q[1]); List<Integer> list = new ArrayList<>(set); Collections.sort(list); int sz = list.size(); Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < sz; i++) map.put(list.get(i), i);
FenwickTree tr = new FenwickTree(sz + 10); int j = 0; for (int qidx : qidxs) { int x = queries[qidx][0], y = queries[qidx][1]; for (; j < n && nums1[idxs[j]] >= x; j++) { int idx = idxs[j]; tr.add(sz - map.get(nums2[idx]), nums1[idx] + nums2[idx]); } res[qidx] = tr.query(sz - map.get(y)); } return res; } }
|