0315. Count of Smaller Numbers After Self

https://leetcode.com/problems/count-of-smaller-numbers-after-self

Description

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 1:

**Input:** nums = [5,2,6,1]
**Output:** [2,1,1,0]
**Explanation:**
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.

Example 2:

**Input:** nums = [-1]
**Output:** [0]

Example 3:

**Input:** nums = [-1,-1]
**Output:** [0,0]

Constraints:

  • 1 <= nums.length <= 105

  • -104 <= nums[i] <= 104

ac1: Merge sort

Amazing solution from https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/445769/merge-sort-CLEAR-simple-EXPLANATION-with-EXAMPLES-O(n-lg-n). So easy to understand that not like a hard problem.

// Wrapper class for each and every value of the input array,
// to store the original index position of each value, before we merge sort the array
private class ArrayValWithOrigIdx {
    int val;
    int originalIdx;

    public ArrayValWithOrigIdx(int val, int originalIdx) {
        this.val = val;
        this.originalIdx = originalIdx;
    }
}

public List<Integer> countSmaller(int[] nums) {
    if (nums == null || nums.length == 0) return new LinkedList<Integer>();
    int n = nums.length;
    int[] result = new int[n];

    ArrayValWithOrigIdx[] newNums = new ArrayValWithOrigIdx[n];
    for (int i = 0; i < n; ++i) newNums[i] = new ArrayValWithOrigIdx(nums[i], i);

    mergeSortAndCount(newNums, 0, n - 1, result);

    // notice we don't care about the sorted array after merge sort finishes.
    // we only wanted the result counts, generated by running merge sort
    List<Integer> resultList = new LinkedList<Integer>();
    for (int i : result) resultList.add(i);
    return resultList;
}

private void mergeSortAndCount(ArrayValWithOrigIdx[] nums, int start, int end, int[] result) {
    if (start >= end) return;

    int mid = (start + end) / 2;
    mergeSortAndCount(nums, start, mid, result);
    mergeSortAndCount(nums, mid + 1, end, result);

    // left subarray start...mid
    // right subarray mid+1...end
    int leftPos = start;
    int rightPos = mid + 1;
    LinkedList<ArrayValWithOrigIdx> merged = new LinkedList<ArrayValWithOrigIdx>();
    int numElemsRightArrayLessThanLeftArray = 0;
    while (leftPos < mid + 1 && rightPos <= end) {
        if (nums[leftPos].val > nums[rightPos].val) {
            // this code block is exactly what the problem is asking us for:
            // a number from the right side of the original input array, is smaller
            // than a number from the left side
            //
            // within this code block,
            // nums[rightPos] is smaller than the start of the left sub-array.
            // Since left sub-array is already sorted,
            // nums[rightPos] must also be smaller than the entire remaining left sub-array
            ++numElemsRightArrayLessThanLeftArray;

            // continue with normal merge sort, merge
            merged.add(nums[rightPos]);
            ++rightPos;
        } else {
            // a number from left side of array, is smaller than a number from
            // right side of array
            result[nums[leftPos].originalIdx] += numElemsRightArrayLessThanLeftArray;

            // Continue with normal merge sort
            merged.add(nums[leftPos]);
            ++leftPos;
        }
    }

    // part of normal merge sort, if either left or right sub-array is not empty,
    // move all remaining elements into merged result
    while (leftPos < mid + 1) {
        result[nums[leftPos].originalIdx] += numElemsRightArrayLessThanLeftArray;

        merged.add(nums[leftPos]);
        ++leftPos;
    }
    while (rightPos <= end) {
        merged.add(nums[rightPos]);
        ++rightPos;
    }

    // part of normal merge sort
    // copy back merged result into array
    int pos = start;
    for (ArrayValWithOrigIdx m : merged) {
        nums[pos] = m;
        ++pos;
    }
}

ac2: BST

This is not optimal, the worse case is still O(n^2), which is same as brute force.

class Solution {
    public List<Integer> countSmaller(int[] nums) {
        LinkedList<Integer> res = new LinkedList<Integer>();
        // edge cases
        if (nums == null || nums.length == 0) return res;

        // iterate right to left, build tree, get result
        Node root = null;
        for (int i = nums.length - 1; i >= 0; i--) {
            root = getLessInsert(root, nums[i], 0, res);
        }
        Collections.reverse(res);

        return res;
    }
    private Node getLessInsert(Node root, int val, int prevSum, List<Integer> res) {
        if (root == null) {
            root = new Node(val);
            res.add(prevSum);
        } else if (val == root.val) {
            root.count++;
            res.add(root.leftSum + prevSum);
        } else if (val < root.val) {
            root.leftSum++;
            root.left = getLessInsert(root.left, val, prevSum, res);
        } else {  // val > root.val
            root.right = getLessInsert(root.right, val, prevSum + root.count + root.leftSum, res);
        }

        return root;
    }

    class Node {
        int val, leftSum, count;
        Node left, right;
        public Node(int val) {
            this.val = val;
            leftSum = 0;
            count = 1;
        }
    }
}

/*
BS
BST
BIT
merge sort
*/

ac3: BIT

Binary Indexed Tree, O(nlogn) time gurantee.

class Solution {
    public List<Integer> countSmaller(int[] nums) {
        LinkedList<Integer> res = new LinkedList<Integer>();
        // edge cases
        if (nums == null || nums.length == 0) return res;

        int[] nums2 = nums.clone();
        Arrays.sort(nums2);
        Map<Integer, Integer> rankMap = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums2.length; i++) {
            if (!rankMap.containsKey(nums2[i])) {
                rankMap.put(nums2[i], i+1);
            }
        }

        // BIT
        int[] tree = new int[nums.length + 1];
        for (int i = nums.length - 1; i >= 0; i--) {
            int rank = rankMap.get(nums[i]);
            res.add(sum(rank-1, tree));
            update(rank, tree);
        }
        Collections.reverse(res);

        return res;
    }

    private int sum(int n, int[] tree) {
        int res = 0;
        while (n > 0) {
            res += tree[n];
            n -= n & (-n);
        }
        return res;
    }
    private void update(int n, int[] tree) {
        while (n < tree.length) {
            tree[n]++;
            n += n & (-n);
        }
    }
}
/*
From right to left, for a num, I need to know how many smaller numbers we had met. So we use BIT to keep track of it. 
Smaller number means: num with lower rank. How to update BIT? For each num, we update BIT by doing rank+1.
*/

Last updated