【LeetCode】493. 翻转对

一、题目

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

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

示例 1:

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

示例 2:

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

注意:

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

二、解决

1、暴力

思路: 略。
代码: 略。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

2、归并排序

先附一下归并排序模板:

public static void mergeSort(int[] array, int left, int right) {

	if (right <= left) return;
	int mid = (left + right) >> 1; // (left+right)/2
	
	mergeSort(array, left, mid);
	mergeSort(array, mid+1, right);
	merge(array, left, mid, right);
}

public static void merge(int[] arr, 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) {
		temp[k++] = arr[i]<=arr[j] ? arr[i++] : arr[j++];
	}
	while (i<=mid)   temp[k++] = arr[i++];
	while (j<=right) temp[k++] = arr[j++];
	
	for (int p=0; p<temp.length; p++) {
		arr[left+p] = temp[p];
	}
	// 也可以用 System.arraycopy(sourceArr, sourceStartIndex, destArr, destStartIndex, copyLength); 
}

思路-版本1:

归并排序隐含了逆序对的数量计算,每一次合并(Merge)的过程,左半边和右半边都是分别有序的。

当我们用双指针合并左右半边的时候,当nums[left] > 2*nums[right],代表当前的nums[right]要小于nums[left] ~ nums[m]的所有数(本应大于),也就是有m - left + 1个逆序对。
归并排序

代码-版本1(推荐):

public class Solution {
    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 l, int r) {
        if (l >= r) return 0;
        int mid = l + (r - l)/2;
        int count = mergeSort(nums, l, mid) + mergeSort(nums, mid + 1, r);
        int[] cache = new int[r - l + 1];
        int i = l, t = l, c = 0;
        for (int j = mid + 1; j <= r; j++, c++) {
            while (i <= mid && nums[i] <= 2 * (long)nums[j]) i++;
            while (t <= mid && nums[t] < nums[j]) cache[c++] = nums[t++];
            cache[c] = nums[j];
            count += mid - i + 1;
        }
        while (t <= mid) cache[c++] = nums[t++];
        System.arraycopy(cache, 0, nums, l, r - l + 1);
        return count;
    }
}

思路-版本2:

思路类似上面,不过处理过程略有不同。

归并排序-版本2

代码-版本2:

public class Solution {
    public int reversePairs(int[] nums) {
        return mergeSort(nums, 0, nums.length-1);
    }
    private int mergeSort(int[] nums, int s, int e){
        if(s>=e) return 0; 
        int mid = s + (e-s)/2; 
        int cnt = mergeSort(nums, s, mid) + mergeSort(nums, mid+1, e); 
        for(int i = s, j = mid+1; i<=mid; i++){
            while(j<=e && nums[i]/2.0 > nums[j]) j++; 
            cnt += j-(mid+1); 
        }
        Arrays.sort(nums, s, e+1); 
        return cnt; 
    }
}

思路-版本3:

理解关键在于左 && 右数组是有序的。具体请结合代码注释来理解。

归并排序

代码-版本3:

public class Solution {
        
    public int ret = 0;
    public int reversePairs(int[] nums) {
        mergeSort(nums, 0, nums.length-1);
        return ret;
    }

    public void mergeSort(int[] nums, int left, int right) {
        if (right <= left) {
            return;
        }
        int middle = left + (right - left)/2;
        mergeSort(nums, left, middle);
        mergeSort(nums,middle+1, right);
        merge(nums, left, middle, right);
    }

    public void merge(int[] nums, int left, int middle, int right) {
        // count elements
        int count = 0;
        for (int l = left, r = middle+1; l <= middle;) {
            if (r > right || (long)nums[l] <= 2*(long)nums[r]) {  // 右侧数组移到最后一个位置 或者 正常状态
                l++;  // 左移一位,看下一个数是否符合翻转对条件
                ret += count;  // nums[x]>2*nums[r],翻转对数量为count,而l>x, nums[l]>nums[x],则翻转对数量也为count
            } else {  // 满足翻转对条件
                r++;  // 继续右移一位
                count++;  // nums[l]>2*nums[r-1],翻转对数量为count,nums[l]>2*nums[r],翻转对数量为count+1
            }
        }
        
        // merge sort
        int[] temp = new int[right - left + 1];
        for (int l = left, r = middle+1, k = 0; l <= middle || r <= right;) {
            if (l <= middle && ((r > right) || nums[l] < nums[r])) {
                temp[k++] = nums[l++];
            } else {
                temp[k++] = nums[r++];
            }
        }
        for (int p = 0; p < temp.length; p++) {
            nums[left + p] = temp[p];
        }
    }
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

3、树状数组

思路: 暂略。
代码:

private int search(int[] bit, int i) {
    int sum = 0;
    
    while (i < bit.length) {
        sum += bit[i];
        i += i & -i;
    }

    return sum;
}

private void insert(int[] bit, int i) {
    while (i > 0) {
        bit[i] += 1;
        i -= i & -i;
    }
}

public int reversePairs(int[] nums) {
    int res = 0;
    int[] copy = Arrays.copyOf(nums, nums.length);
    int[] bit = new int[copy.length + 1];
    
    Arrays.sort(copy);
    
    for (int ele : nums) {
        res += search(bit, index(copy, 2L * ele + 1));
        insert(bit, index(copy, ele));
    }
    
    return res;
}

private int index(int[] arr, long val) {
    int l = 0, r = arr.length - 1, m = 0;
    	
    while (l <= r) {
    	m = l + ((r - l) >> 1);
    		
    	if (arr[m] >= val) {
    	    r = m - 1;
    	} else {
    	    l = m + 1;
    	}
    }
    
    return l + 1;
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

三、参考

1、剑指 Offer 51. 数组中的逆序对[Hard]
2、Very Short and Clear MergeSort & BST Java Solutions
3、Java merge sort solution, O(nlog(n))
4、<= 20 lines Java code. Beats 100%!!
5、翻转对
6、General principles behind problems similar to “Reverse Pairs”
7、Very Short and Clear MergeSort & BST Java Solutions

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值