Rayman的绝顶之路——Leetcode每日一题打卡10

Leetcode面试题51. 数组中的逆序对 题目:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:
0 <= 数组长度 <= 50000

1、暴力解法(超时):

这是我最开始想到的方法,花了1分钟写完执行了一下超时,我就说标着困难的题目没这么简单吧,哎,换种思路吧。

代码(超时):

public class Leetcode面试题51 {
    static int reversePairs(int[] nums) {
        if (nums.length <= 1) {
            return 0;
        }
        int res = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] > nums[j]) {
                    res++;
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(reversePairs(new int[]{7,5,6,4}));
    }
}

2、分治思想

计算逆序数就发生在排序的过程中,利用了「排序」以后数组的有序性。

  • 关键在于合并两个有序数组,利用数组的部分有序性,一下子计算出一个数之前或者之后元素的逆序的个数;
  • 前面"分"的时候什么都不做,"合"的过程中计算逆序对的个数;
  • 排序的工作是必要的,正是因为排序才能在下一轮利用顺序关系加快逆序数的计算,也能避免重复计算;
  • 在代码实现上,只需要在归并排序代码的基础上,加上逆序对个数的计算,计算公式需要自己在草稿纸上推导。
  • 逆序对总数=左边区间的逆序对+右边区间的逆序对+横跨两个区间的逆序对。

Java AC代码:

public class Leetcode面试题51 {
    static int reversePairs(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return 0;
        }
        int[] numsCopy = new int[len];
        for (int i = 0; i < len; i++) {
            numsCopy[i] = nums[i];
        }
        int[] temp = new int[len];
        return reversePairs(numsCopy, 0, len - 1, temp);
    }

    //计算逆序对个数并且排序
    static int reversePairs(int[] nums, int left, int right, int[] temp) {
        if (left == right) {
            return 0;
        }
        int mid = left + (right - left) / 2;
        int leftRes = reversePairs(nums, left, mid, temp);
        int rightRes = reversePairs(nums, mid + 1, right, temp);
        if (nums[mid] <= nums[mid + 1]) {
            return leftRes + rightRes;
        }
        int crossRes = mergeAndCount(nums, left, mid, right, temp);
        return leftRes + rightRes + crossRes;
    }

    //计算crossRes(交叉)
    static int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
        for (int i = left; i <= right; i++) {
            temp[i] = nums[i];
        }
        int i = left;
        int j = mid + 1;
        int count = 0;
        for (int k = left; k <= right; k++) {
            if (i == mid + 1) {
                nums[k] = temp[j];
                j++;
            } else if (j == right + 1) {
                nums[k] = temp[i];
                i++;
            } else if (temp[i] <= temp[j]) {
                nums[k] = temp[i];
                i++;
            } else {
                nums[k] = temp[j];
                j++;
                count += (mid - i + 1);
            }
        }
        return count;
    }

    public static void main(String[] args) {
        System.out.println(reversePairs(new int[]{7,5,6,4}));
    }
}

3、树状数组

这种方法是我看解题思路看到的,具体的做法是:

  • 先离散化,将所有的数组元素映射到 0、1、2、3… ,这是为了节约树状数组的空间;
  • 从后向前扫描,边统计边往树状数组里面添加元素,这个过程是「动态的」,需要动手计算才能明白思想。

这个解法我看了蛮久的,还是不会,感觉自己还是太菜了,大家可以参考一下我贴的大佬的代码:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

//作者:liweiwei1419
//链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/bao-li-jie-fa-fen-zhi-si-xiang-shu-zhuang-shu-zu-b/
public class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return 0;
        }
        // 离散化:使得数字更紧凑,节约树状数组的空间
        // 1、使用二分搜索树是为了去掉重复元素
        Set<Integer> treeSet = new TreeSet<>();
        for (int i = 0; i < len; i++) {
            treeSet.add(nums[i]);
        }
        // 2、把排名存在哈希表里方便查询
        Map<Integer, Integer> rankMap = new HashMap<>();
        int rankIndex = 1;
        for (Integer num : treeSet) {
            rankMap.put(num, rankIndex);
            rankIndex++;
        }
        int count = 0;
        // 在树状数组内部完成前缀和的计算
        // 规则是:从后向前,先给对应的排名 + 1,再查询前缀和
        FenwickTree fenwickTree = new FenwickTree(rankMap.size());
        for (int i = len - 1; i >= 0; i--) {
            int rank = rankMap.get(nums[i]);
            fenwickTree.update(rank, 1);
            count += fenwickTree.query(rank - 1);
        }
        return count;
    }

    private class FenwickTree {
        private int[] tree;
        private int len;
        public FenwickTree(int n) {
            this.len = n;
            tree = new int[n + 1];
        }
        /**
         * 单点更新:将 index 这个位置 + delta
         *
         * @param i
         * @param delta
         */
        public void update(int i, int delta) {
            // 从下到上,最多到 size,可以等于 size
            while (i <= this.len) {
                tree[i] += delta;
                i += lowbit(i);
            }
        }
        // 区间查询:查询小于等于 tree[index] 的元素个数
        // 查询的语义是「前缀和」
        public int query(int i) {
            // 从右到左查询
            int sum = 0;
            while (i > 0) {
                sum += tree[i];
                i -= lowbit(i);
            }
            return sum;
        }
        public int lowbit(int x) {
            return x & (-x);
        }
    }
}

2020.4.24打卡

你可能感兴趣的:(Leetcode每日一题打卡)