• [LeetCode] 1395. Count Number of Teams


    There are n soldiers standing in a line. Each soldier is assigned a unique rating value.

    You have to form a team of 3 soldiers amongst them under the following rules:

    • Choose 3 soldiers with index (ijk) with rating (rating[i]rating[j]rating[k]).
    • A team is valid if:  (rating[i] < rating[j] < rating[k]) or (rating[i] > rating[j] > rating[k]) where (0 <= i < j < k < n).

    Return the number of teams you can form given the conditions. (soldiers can be part of multiple teams).

     

    Example 1:

    Input: rating = [2,5,3,4,1]
    Output: 3
    Explanation: We can form three teams given the conditions. (2,3,4), (5,4,1), (5,3,1). 
    

    Example 2:

    Input: rating = [2,1,3]
    Output: 0
    Explanation: We can't form any team given the conditions.
    

    Example 3:

    Input: rating = [1,2,3,4]
    Output: 4
    

     

    Constraints:

    • n == rating.length
    • 1 <= n <= 200
    • 1 <= rating[i] <= 10^5

    The constraints of this problem permits a brute force O(N^3) solution by simply checking all possible triplets. However, if we increase the input size to 10^5,  we are required to come up with a more efficient algorithm to solve this problem.

    First point we should observe is that if we can come up with an efficient algorithm for condition (rating[i] < rating[j] < rating[k]) where (0 <= i < j < k < n), then we can simply reverse the input array and apply the same algorithm for condition (rating[i] > rating[j] > rating[k]) where (0 <= i < j < k < n).

    Let's walk through the following example to get an idea of how an efficient algorithm should work: 

    2 5 3 4 1 6

     

    If we only consider the increasing case, the answer is 5 shown as the follows.

    (2, 3, 4)

    (2, 5, 6)

    (2, 3, 6)

    (2, 4, 6)

    (3, 4, 6)

    Let's find all triplets by traversing the input array and make each element V as the largest number of a valid triplet if possible. If we denote each element V's smaller preceeding number count as Cnt(V) and the number of valid triplets ending at V as Sum(V). Then we have the following formula:

    Sum(V) = Cnt(1) + Cnt(2) + ..... + Cnt(V - 1).

    In another word, in order to find the number of valid triplets ending in V, we need to sum the contributions from 1 to V - 1 as the middle number. The contribution of each number in [1, V - 1] equals to its count of smaller proceeding numbers. 

    A walk through of provided example with this idea is:

    i = 0,  a[i] = 2;      Sum(2) = 0; Cnt(2) = 0;

    i = 1,  a[i] = 5;     2 < 5 but there isn't enough numbers to make an increasing triplet at this point. Sum(5) = 0; Cnt(5) = 1;

    i = 2, a[i] = 3, T = 0;      2 < 3 but 2 has no smaller number ahead of it to use as the smallest number in a triplet. Sum(3) = 0; Cnt(3) = 1;

    i = 3, a[i] = 4, T = 1;      3 has 1 smaller number 2 ahead of it. Sum(4) = Sum(1) + Sum(2) + Sum(3) = 1; Cnt(4) = 2;

    i = 4, a[i] = 1, T = 0;      no number ahead of 1 is smaller. Sum(1) = 0; Cnt(1) = 0;

    i = 5, a[i] = 6, T = 4;      5 has 2 ahead of it; 3 has 2 ahead of it; 4 has 2 and 3 ahead of it. Sum(6) = Sum(1) + Sum(2) + Sum(3) + Sum(4) + Sum(5) = 1 + 1 + 2 = 4.

    All 1 to 5 can be used as the smallest number in a triplet with 6 being the middle number. Cnt(6) = 5.

    The total count of increasing triplet is the total number of triplets that end on each element, which is 1 + 4 = 5.

    To solve this problem efficiently, we need to achieve the following efficiently.

    1. Get the total count of smaller proceeding numbers(as smallest numbers) of V's smaller proceeding numbers(as middle numbers) in order to get the count of triplets ending at V(as largest number). 

    2. Update the total count of smaller proceeding numbers for V. This will be used later when V is used as a triplet middle number. This is a point update 

    We also need to support the above operations dynamically as we scan the entire array one element at a time.  Binary Indexed Tree fits this purpose perfectly. We can use a BIT bit1 to achieve both operations in log(Max Rating) time. Operation 1 is a range sum query for all numbers' total count of smaller proceeding numbers. Operation 2 is a point update on a number's count of smaller proceeding numbers. To get the count of smaller proceeding numbers of a given number, we need a separate BIT bit2 to perform range query and point update. 

    Essentially, bit1 supports querying/updating the total count of first number of tripets; bit2 supports querying/updating the total count of middle number of triplets. After considering each element V as the last number of a triplet, we then update its smaller proceeding number count in bit2 so V can be used later as a possible middle number in a triplet.

    The runtime is O(N * log (Max Rating)). The max rating is 10^5 so index compression is not needed. If the max rating can go up to 10^7,  we need to apply the compression technique used in the related problem below.

    class Solution {
        private class BinaryIndexedTree {
            int[] ft;
            BinaryIndexedTree(int n) {
                ft = new int[n + 1];
            }
            int rsq(int r) {
                int sum = 0;
                for(; r > 0; r -= (r & (-r))) {
                    sum += ft[r];
                }
                return sum;
            }
            void update(int k, int v) {
                for(; k < ft.length; k += (k & (-k))) {
                    ft[k] += v;
                }
            }
        }
        public int numTeams(int[] rating) {
            int cnt = 0;
            int maxR = 0;
            for(int r : rating) {
                maxR = Math.max(maxR, r);
            }
            cnt += solve(rating, maxR);
            reverse(rating);
            cnt += solve(rating, maxR);
            return cnt;
        }
        private int solve(int[] rating, int n) {
            BinaryIndexedTree bit1 = new BinaryIndexedTree(n);
            BinaryIndexedTree bit2 = new BinaryIndexedTree(n);
            
            int sum = 0;
            for(int i = 0; i < rating.length; i++) {
                sum += bit1.rsq(rating[i] - 1);
                bit1.update(rating[i], bit2.rsq(rating[i] - 1));
                bit2.update(rating[i], 1);
            }
            return sum;
        }
        private void reverse(int[] rating) {
            int i = 0, j = rating.length - 1;
            while(i < j) {
                int temp = rating[i];
                rating[i] = rating[j];
                rating[j] = temp;
                i++;
                j--;
            }
        }
    }

    Related Problem : Moving Points

  • 相关阅读:
    JSON的数据格式
    KMP 算法
    爬虫原理
    快速求小于N的所有素数
    对程序员最具影响的书籍
    实现下拉更新UITableView EGORefreshTableHeaderView
    温习C/C++笔记——浅谈琐碎知识点(1)
    C++内存对齐
    SQL Server 安装程序无法获取 ASPNET 帐户的系统帐户信息
    Asp.Net生命周期事件
  • 原文地址:https://www.cnblogs.com/lz87/p/12602969.html
Copyright © 2020-2023  润新知