统计作战单位数

标签: 树状数组 数组 动态规划

难度: Medium

 n 名士兵站成一排。每个士兵都有一个 独一无二 的评分 rating

3 个士兵可以组成一个作战单位,分组规则如下:

  • 从队伍中选出下标分别为 ijk 的 3 名士兵,他们的评分分别为 rating[i]rating[j]rating[k]
  • 作战单位需满足: rating[i] < rating[j] < rating[k] 或者 rating[i] > rating[j] > rating[k] ,其中  0 <= i < j < k < n

请你返回按上述条件可以组建的作战单位数量。每个士兵都可以是多个作战单位的一部分。

 

示例 1:

输入:rating = [2,5,3,4,1]
输出:3
解释:我们可以组建三个作战单位 (2,3,4)、(5,4,1)、(5,3,1) 。

示例 2:

输入:rating = [2,1,3]
输出:0
解释:根据题目条件,我们无法组建作战单位。

示例 3:

输入:rating = [1,2,3,4]
输出:4

 

提示:

  • n == rating.length
  • 3 <= n <= 1000
  • 1 <= rating[i] <= 10^5
  • rating 中的元素都是唯一的

Submission

运行时间: 50 ms

内存: 16.3 MB

class Solution:
    def numTeams(self, rating: List[int]) -> int:
        def add(index: int, val: int) -> None:
            nonlocal bit
            x = index + 1
            while x <= n:
                bit[x] += val
                x += (-x) & x

        def get_sum(index: int) -> int:
            nonlocal bit
            x = index + 1
            ans = 0
            while x:
                ans += bit[x]
                x -= (-x) & x
            return ans

        def sum_range(left: int, right: int) -> int:
            return get_sum(right) - get_sum(left - 1)
        s = sorted(rating)
        map = {k: v for v, k in enumerate(s)}
        bit, n = [0] * (len(s) + 1), len(s)
        add(map[rating[0]], 1)
        ans = 0
        for i in range(1, n - 1):
            cur = map[rating[i]]
            lsmall = get_sum(cur - 1)
            llarge = i - lsmall
            rsmall = cur - lsmall
            rlarge = n - 1 - i - rsmall
            ans += lsmall * rlarge + llarge * rsmall
            add(cur, 1)
        return ans


Explain

题解利用了一种改进的方法,通过排序和二进制索引树(Binary Indexed Tree, BIT 或 Fenwick Tree)来高效计算索引间的关系。首先,它通过将rating数组排序并映射每个值到其排序后的索引,便于后续操作。对每个士兵j,计算在其左侧有多少数小于它(lsmall)和大于它(llarge),同时计算在其右侧有多少数小于它(rsmall)和大于它(rlarge)。这些值可以通过BIT高效地计算。根据题目要求,对于每个j,有效的组合为lsmall * rlarge(形成递增序列)和llarge * rsmall(形成递减序列)。这样,对每个j,计算这两种情况的数量并累加得到最终结果。

时间复杂度: O(n log n)

空间复杂度: O(n)

# Definition for a class that solves the soldier rating problem

class Solution:
    def numTeams(self, rating: List[int]) -> int:
        # Helper function to add values to BIT
        def add(index: int, val: int) -> None:
            nonlocal bit
            x = index + 1
            while x <= n:
                bit[x] += val
                x += (-x) & x

        # Helper function to get prefix sum up to index
        def get_sum(index: int) -> int:
            nonlocal bit
            x = index + 1
            ans = 0
            while x:
                ans += bit[x]
                x -= (-x) & x
            return ans

        # Function to get range sum between two indices
        def sum_range(left: int, right: int) -> int:
            return get_sum(right) - get_sum(left - 1)

        # Map each rating to its index after sorting
        s = sorted(rating)
        map = {k: v for v, k in enumerate(s)}
        bit, n = [0] * (len(s) + 1), len(s)
        add(map[rating[0]], 1)
        ans = 0
        # Main logic loop over each soldier
        for i in range(1, n - 1):
            cur = map[rating[i]]
            lsmall = get_sum(cur - 1)
            llarge = i - lsmall
            rsmall = cur - lsmall
            rlarge = n - 1 - i - rsmall
            ans += lsmall * rlarge + llarge * rsmall
            add(cur, 1)
        return ans

Explore

对评分数组进行排序和映射是为了将数组元素的值转换为有序的索引,这样可以在二进制索引树(BIT)中以索引的方式高效处理。排序确保了我们可以通过数组索引快速访问任何元素,并在整个数组中维持一致的顺序。映射(即将每个元素的值转换为其在排序数组中的位置)则使得可以使用BIT进行区间和频次查询,而不受原始数据大小和分布的影响。这种方式利用BIT处理数组索引而非值本身,简化了问题复杂度,使得我们可以更加高效地计算小于或大于当前元素的元素数量。

在使用BIT时,通过在遍历每个士兵时更新BIT来确保`lsmall`和`llarge`的正确性。对于每个士兵j,`lsmall`是通过BIT的`get_sum`函数计算其左侧所有小于当前士兵评分的士兵数,实现了通过索引来累加计数。`llarge`则是通过计算到目前为止遍历过的士兵数减去`lsmall`得到的,即到当前士兵为止,总共遍历的士兵数减去小于当前士兵评分的士兵数,就是大于当前士兵评分的士兵数。这种方法确保了在计算每个士兵的`lsmall`和`llarge`时,都能够反映出正确的数量,因为BIT在每次迭代时都被及时更新。

二进制索引树(BIT)的`add`函数用于更新树中的值。给定一个索引和一个值,它会将值添加到该索引处,然后向上移动到父节点,直到达到树的顶部。这是通过`x += (-x) & x`实现的,这个操作利用了位运算来快速找到包含当前索引的下一个区间。`get_sum`函数用于查询从数组起始到指定索引的累积和。它从给定索引开始,向下移动到更小的索引,累加这些索引处的值,直至数组开始。这也是通过`x -= (-x) & x`实现的,同样利用位运算快速定位到需要累加的下一个索引。这两个函数的设计使得BIT能够在对数时间复杂度内完成更新和查询操作,非常适合处理频繁更新和需要快速查询的场景。