leetcode
leetcode 451 ~ 500
翻转对

翻转对

难度:

标签:

题目描述

给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对

你需要返回给定数组中的重要翻转对的数量。

示例 1:

输入: [1,3,2,3,1]
输出: 2

示例 2:

输入: [2,4,3,5,1]
输出: 3

注意:

  1. 给定数组的长度不会超过50000
  2. 输入数组中的所有数字都在32位整数的表示范围内。

代码结果

运行时间: 479 ms, 内存: 22.7 MB


/*
题目思路:
我们仍然需要找到数组中的所有重要翻转对(i, j),满足 i < j 且 nums[i] > 2 * nums[j]。
虽然Java Stream API不太适合进行复杂的索引操作和修改,但我们可以使用Stream API来简化某些部分。
此解决方案主要展示如何结合使用Stream API和传统循环。
*/
import java.util.stream.*;
public class SolutionStream {
    public int reversePairs(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        return mergeSort(nums, 0, nums.length - 1);
    }
 
    private int mergeSort(int[] nums, int left, int right) {
        if (left >= right) return 0;
        int mid = left + (right - left) / 2;
        int count = mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right);
        count += (int) IntStream.range(left, mid + 1).boxed().mapToLong(i -> {
            return IntStream.range(mid + 1, right + 1).filter(j -> nums[i] > 2L * nums[j]).count();
        }).sum();
        merge(nums, left, mid, right);
        return count;
    }
 
    private void merge(int[] nums, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;
        while (i <= mid && j <= right) {
            if (nums[i] <= nums[j]) temp[k++] = nums[i++];
            else temp[k++] = nums[j++];
        }
        while (i <= mid) temp[k++] = nums[i++];
        while (j <= right) temp[k++] = nums[j++];
        System.arraycopy(temp, 0, nums, left, temp.length);
    }
}
 

解释

方法:

本题解采用了分治策略结合归并排序的方法来解决问题。首先,通过递归地将数组分解为更小的子数组,直到每个子数组只包含一个元素。然后,在归并排序的过程中,将这些子数组合并回去,并在合并的同时计算两个子数组之间可能的重要翻转对的数量。具体来说,在合并两个已排序的子数组时,对于左边数组的每个元素,查找右边数组中满足条件(左边元素大于右边元素的两倍)的元素的数量,并累加到总计数中。最后返回这个计数值。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
这个算法为什么选择使用归并排序来处理这个问题?
归并排序是一种有效的排序算法,它通过分治法将数组分解为更小的子数组,分别对它们进行排序,然后将它们合并。在处理翻转对问题时,归并排序的分治策略允许我们在合并阶段同时计算翻转对的数量。这是因为在每次合并操作时,左右两个子数组都已经是排序好的状态,可以直接应用双指针技术来高效地计算满足条件的对数。此外,归并排序的时间复杂度为O(n log n),适合处理大数据量,符合本题中数据规模的需求。
🦆
在归并过程中,为什么要先对左数组的元素进行比较,而不是右数组的元素?
在归并排序的翻转对问题中,我们需要找到所有满足`nums[i] > 2*nums[j]`且`i < j`的对数。在此设置中,左数组的索引始终小于右数组的索引,因此我们需要检查左数组中的每个元素是否存在对应的右数组中的元素满足此条件。如果从右数组开始,我们无法有效地统计满足条件的索引对,因为右数组的元素索引是大于左数组的,不满足`i < j`的要求。
🦆
归并排序中的`merge_sort`函数递归地将数组分解,这种方式在空间复杂度上有什么影响?
归并排序在递归过程中需要额外的空间来存储临时的子数组,对于每个递归调用,都需要为左右子数组分配空间。归并排序的空间复杂度总体上是O(n),因为尽管在每层递归中都需要分配空间,但这些空间在每层递归后都会被释放。因此,尽管递归深度影响了空间的临时分配,但总体空间需求并不会超过输入数组的大小。

相关问题

计算右侧小于当前元素的个数

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

 

示例 1:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

示例 2:

输入:nums = [-1]
输出:[0]

示例 3:

输入:nums = [-1,-1]
输出:[0,0]

 

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

区间和的个数

给你一个整数数组 nums 以及两个整数 lowerupper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数

区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (ij)。

 

示例 1:
输入:nums = [-2,5,-1], lower = -2, upper = 2
输出:3
解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。

示例 2:

输入:nums = [0], lower = 0, upper = 0
输出:1

 

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • -105 <= lower <= upper <= 105
  • 题目数据保证答案是一个 32 位 的整数