LeetCode 315. Count of Smaller Numbers After Self (逆序数对)

Posted jmspan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 315. Count of Smaller Numbers After Self (逆序数对)相关的知识,希望对你有一定的参考价值。

原题网址:https://leetcode.com/problems/count-of-smaller-numbers-after-self/

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Given nums = [5, 2, 6, 1]

To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.

Return the array [2, 1, 1, 0].

方法一:穷举计数,时间复杂度O(n^2),超时、无法通过。

public class Solution 
    public List<Integer> countSmaller(int[] nums) 
        int[] smaller = new int[nums.length];
        for(int i=0; i<nums.length-1; i++) 
            for(int j=i+1; j<nums.length; j++) 
                if (nums[i] > nums[j]) smaller[i] ++;
            
        
        List<Integer> results = new ArrayList<>(smaller.length);
        for(int i=0; i<smaller.length; i++) results.add(smaller[i]);
        return results;
    
方法二:从右到左扫描,应用二分法将当前数据插入到前面已排好序的数组中,二分法查找的时间复杂度为O(logn),但由于需要插入数据,所以每个数据查找和插入的总时间是O(nlogn),总体的时间复杂度O(n^2*logn),但勉强能够通过测试。

public class Solution 
    public List<Integer> countSmaller(int[] nums) 
        int[] smaller = new int[nums.length];
        for(int i=nums.length-2; i>=0; i--) 
            int left = i+1;
            int right = nums.length-1;
            while (left<=right) 
                int m = (left+right)/2;
                if (nums[i] > nums[m]) right = m - 1;
                else left = m + 1;
            
            smaller[i] = nums.length - left;
            int temp = nums[i];
            for(int j=i; j<right; j++) nums[j] = nums[j+1];
            nums[right] = temp;
        
        List<Integer> results = new ArrayList<>(nums.length);
        for(int i=0; i<smaller.length; i++) results.add(smaller[i]);
        return results;
    

方法三:应用合并排序。如果我们对数组进行排序,那么对于某个特定的数据,其后面的逆序数等于在排序过程中需要移动到该数前面的个数。时间复杂度O(nlogn)。

我们定义数组pos[i] = j,表示第排名i个数据的元素下标是j。

public class Solution 
    private void sort(int[] nums, int[] smaller, int[] pos, int from, int to) 
        if (from >= to) return;
        int m = (from + to) / 2;
        sort(nums, smaller, pos, from, m);
        sort(nums, smaller, pos, m+1, to);
        int[] merged = new int[to-from+1];
        int i=from, j=m+1, k=0, jump = 0;
        while (i<=m || j<=to) 
            if (i>m) 
                jump ++;
                merged[k++] = pos[j++];
             else if (j>to) 
                smaller[pos[i]] += jump;
                merged[k++] = pos[i++];
             else if (nums[pos[i]] <= nums[pos[j]]) 
                smaller[pos[i]] += jump;
                merged[k++] = pos[i++];
             else 
                jump ++;
                merged[k++] = pos[j++];
            
        
        for(int p=0; p<merged.length; p++) pos[from+p] = merged[p];
    
    public List<Integer> countSmaller(int[] nums) 
        int[] smaller = new int[nums.length];
        int[] pos =new int[nums.length];
        for(int i=0; i<pos.length; i++) pos[i] = i;
        sort(nums, smaller, pos, 0, nums.length-1);
        List<Integer> result = new ArrayList<>(nums.length);
        for(int i=0; i<nums.length; i++) result.add(smaller[i]);
        return result;
    

另一种实现形式:

public class Solution 
    private void sort(int[] nums, int[] pos, int[] counts, int from, int to) 
        if (from+1>=to) return;
        int m=(from+to)/2;
        sort(nums, pos, counts, from, m);
        sort(nums, pos, counts, m, to);
        int[] merged = new int[to-from];
        int smaller = 0;
        for(int i=from, j=m, k=0; k<merged.length; k++) 
            if (i>=m) 
                merged[k] = pos[j++];
             else if (j>=to) 
                counts[pos[i]] += smaller;
                merged[k] = pos[i++];
             else if (nums[pos[i]] <= nums[pos[j]]) 
                counts[pos[i]] += smaller;
                merged[k] = pos[i++];
             else 
                smaller ++;
                merged[k] = pos[j++];
            
        
        for(int i=0; i<merged.length; i++) pos[from+i] = merged[i];
    
    public List<Integer> countSmaller(int[] nums) 
        int[] counts = new int[nums.length];
        int[] pos = new int[nums.length];
        for(int i=0; i<pos.length; i++) pos[i] = i;
        sort(nums, pos, counts, 0, nums.length);
        List<Integer> results = new ArrayList<>(nums.length);
        for(int count : counts) results.add(count);
        return results;
    


方法四:使用BST进行统计。时间复杂度O(nlogn)。可以先对数组排序,以便使BST均衡?

public class Solution 
    TreeNode root;
    private int smaller(TreeNode current, int val) 
        current.size ++;
         if (current.val < val) 
             if (current.right == null) current.right = new TreeNode(val);
             return current.size - 1 - current.right.size + smaller(current.right, val);
          else if (current.val > val) 
             if (current.left == null) current.left = new TreeNode(val);
             return smaller(current.left, val);
          else 
             return current.left == null? 0 : current.left.size;
         
    
    public List<Integer> countSmaller(int[] nums) 
        List<Integer> result = new ArrayList<>(nums.length);
        int[] smaller = new int[nums.length];
        if (nums == null || nums.length == 0) return result;
        root = new TreeNode(nums[nums.length-1]);
        for(int i=nums.length-1; i>=0; i--) 
            smaller[i] = smaller(root, nums[i]);
        
        for(int i=0; i<smaller.length; i++) result.add(smaller[i]);
        return result;
    

class TreeNode 
    int val;
    int size;
    TreeNode left, right;
    TreeNode(int val) 
        this.val = val;
    

事实上,二叉搜索树可以用数组实现:

public class Solution 
    private void update(int[] tree, int[] smaller, int value) 
        int i=0, j=tree.length-1;
        while (i<=j) 
            int m = (i+j)/2;
            if (value < tree[m]) 
                smaller[m] ++;
                j = m - 1;
             else 
                i = m + 1;
            
        
    
    private int smaller(int[] tree, int[] smaller, int value) 
        int sum = 0;
        int i=0, j=tree.length-1;
        while (i<=j) 
            int m = (i+j)/2;
            if (tree[m] <= value) 
                sum += smaller[m];
                i = m + 1;
             else 
                j = m - 1;
            
        
        return sum;
    
    public List<Integer> countSmaller(int[] nums) 
        int[] tree = Arrays.copyOf(nums, nums.length);
        Arrays.sort(tree);
        int[] smaller = new int[nums.length];
        int[] count = new int[nums.length];
        for(int i=nums.length-1; i>=0; i--) 
            count[i] = smaller(tree, smaller, nums[i]);
            update(tree, smaller, nums[i]);
        
        List<Integer> results = new ArrayList<>(nums.length);
        for(int i=0; i<count.length; i++) results.add(count[i]);
        return results;
    

甚至再进一步简化:

public class Solution 
    private int smaller(int[] tree, int[] smaller, int value) 
        int sum = 0;
        int i=0, j=tree.length-1;
        while (i<=j) 
            int m = (i+j)/2;
            if (tree[m] <= value) 
                sum += smaller[m];
                i = m + 1;
             else 
                smaller[m] ++;
                j = m - 1;
            
        
        return sum;
    
    public List<Integer> countSmaller(int[] nums) 
        int[] tree = Arrays.copyOf(nums, nums.length);
        Arrays.sort(tree);
        int[] smaller = new int[nums.length];
        int[] count = new int[nums.length];
        for(int i=nums.length-1; i>=0; i--) 
            count[i] = smaller(tree, smaller, nums[i]);
        
        List<Integer> results = new ArrayList<>(nums.length);
        for(int i=0; i<count.length; i++) results.add(count[i]);
        return results;
    

上面判断条件中,tree[m] <= value修改为tree[m] < value会更加make sense:

public class Solution 
    private int smaller(int[] tree, int[] count, int value) 
        int sum = 0;
        int i = 0, j = tree.length - 1;
        while (i <= j) 
            int m = (i + j) / 2;
            if (tree[m] < value) 
                sum += count[m];
                i = m + 1;
             else 
                count[m]++;
                j = m - 1;
            
        
        return sum;
    
    public List<Integer> countSmaller(int[] nums) 
        int[] tree = Arrays.copyOf(nums, nums.length);
        Arrays.sort(tree);
        int[] count = new int[nums.length];
        Integer[] result = new Integer[nums.length];
        for(int i = nums.length - 1; i >= 0; i--) 
            result[i] = smaller(tree, count, nums[i]);
        
        return Arrays.asList(result);
    

如果先用集合排除重复的数字,则数据量可以缩减:

public class Solution 
    private int smaller(int[] tree, int[] count, int value) 
        int sum = 0;
        int i = 0, j = tree.length - 1;
        while (i <= j) 
            int m = (i + j) / 2;
            if (tree[m] < value) 
                sum += count[m];
                i = m + 1;
             else 
                count[m]++;
                j = m - 1;
            
        
        return sum;
    
    public List<Integer> countSmaller(int[] nums) 
        Set<Integer> unique = new HashSet<>();
        for(int num : nums) unique.add(num);
        int[] tree = new int[unique.size()];
        int pos = 0;
        for(int num : unique) tree[pos++] = num;
        Arrays.sort(tree);
        int[] count = new int[nums.length];
        Integer[] result = new Integer[nums.length];
        for(int i = nums.length - 1; i >= 0; i--) 
            result[i] = smaller(tree, count, nums[i]);
        
        return Arrays.asList(result);
    

方法五:使用分段树。时间复杂度O(nlogn)。

public class Solution 
    private int smaller(Node node, int val) 
        node.count ++;
        if (node.min == node.max) return 0;
        int m = (node.min + node.max) / 2;
        if (m < val) 
            if (node.right == null) node.right = new Node(m+1, node.max);
            return node.count - 1 - node.right.count + smaller(node.right, val);
         else if (m > val) 
            if (node.min == m) return 0;
            if (node.left == null) node.left = new Node(node.min, m-1);
            return smaller(node.left, val);
         else 
            if (node.left == null) return 0;
            return node.left.count;
        
    
    public List<Integer> countSmaller(int[] nums) 
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for(int num: nums) 
            min = Math.min(min, num);
            max = Math.max(max, num);
        
        Node root = new Node(min, max);
        int[] smaller = new int[nums.length];
        for(int i=nums.length-1; i>=0; i--) 
            smaller[i] = smaller(root, nums[i]);
        
        List<Integer> result = new ArrayList<>(nums.length);
        for(int i=0; i<smaller.length; i++) result.add(smaller[i]);
        return result;
    

class Node 
    int min, max;
    int count;
    Node left, right;
    Node(int min, int max) 
        this.min = min;
        this.max = max;
    

注意,关于分段树此处可能有概念误解,分段树应该是按照名次进行分段,即等同于树状数组,以后再具体检查修正。


方法六:使用树状数组,要点是按名次进行计数。时间复杂度O(nlogn)。

public class Solution 
    private void sort(int[] nums, int[] pos, int from, int to) 
        if (from>=to) return;
        int m = (from+to)/2;
        sort(nums, pos, from, m);
        sort(nums, pos, m+1, to);
        int[] merged = new int[to-from+1];
        int i=from, j=m+1, p=0;
        while (i<=m || j<=to) 
            if (i>m) 
                merged[p++] = pos[j++];
             else if (j>to) 
                merged[p++] = pos[i++];
             else if (nums[pos[i]] <= nums[pos[j]]) 
                merged[p++] = pos[i++];
             else 
                merged[p++] = pos[j++];
            
        
        for(int k=0; k<merged.length; k++) pos[from+k] = merged[k];
    
    private int count(int[] sum, int s) 
        int count = 0;
        while (s>0) 
            count += sum[s];
            s -= (s & -s);
        
        return count;
    
    private void update(int[] sum, int s) 
        while (s<sum.length) 
            sum[s] ++;
            s += (s & -s);
        
    
    public List<Integer> countSmaller(int[] nums) 
        int[] pos = new int[nums.length];
        for(int i=0; i<nums.length; i++) pos[i] = i;
        sort(nums, pos, 0, nums.length-1);
        int[] seq = new int[nums.length];
        for(int i=0, s=0; i<pos.length; i++) 
            if (i==0 || nums[pos[i]] != nums[pos[i-1]]) seq[pos[i]] = ++ s; else seq[pos[i]] = s;
        
        int[] sum = new int[nums.length+1];
        int[] smaller = new int[nums.length];
        for(int i=nums.length-1; i>=0; i--) 
            smaller[i] = count(sum, seq[i]-1);
            update(sum, seq[i]);
        
        List<Integer> result = new ArrayList<>(nums.length);
        for(int i=0; i<smaller.length; i++) result.add(smaller[i]);
        return result;
    


以上是关于LeetCode 315. Count of Smaller Numbers After Self (逆序数对)的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode 315. Count of Smaller Numbers After Self

leetcode No315. Count of Smaller Numbers After Self

leetcode No315. Count of Smaller Numbers After Self

leetcode No315. Count of Smaller Numbers After Self

Leetcode 315. Count of Smaller Numbers After Self

LeetCode 315. Count of Smaller Numbers After Self(线段树,树状数组)