美团240316春招实习笔试——小美的逆序对(树状数组)

题目描述

小美拿到了一个排列,她定义 f ( i ) f(i) f(i)为:将第 i i i个元素取反后,形成的数组的逆序对数量。

逆序对:对于一个整数数组 nums,逆序对是一对满足 0 <= i < j < nums.length 且 nums[i] > nums[j]的整数对 [i, j] 。
排列:排列是指一个长度为n的数组,1到n每个元素恰好出现了一次。

小美希望你求出 f ( 1 ) f(1) f(1) f ( n ) f(n) f(n)的值。

输入描述

第一行输入一个正整数n,代表排列的大小
第二行输入n个正整数 a i a_i ai,代表排列的元素。

约束

1 ≤ n ≤ 2 × 1 0 5 1 ≤ a i ≤ n 1\le n\le2\times10^5\\ 1\le a_i\le n 1n2×1051ain

输出描述

输出 n n n个整数,第 i i i个整数是 f ( i ) f(i) f(i)的值。

样例

输入

3
1 2 3

输出

0 1 2

说明

第一个元素取反,数组将变成[-1,2,3],逆序对数量为 0.
第二个元素取反,数组将变成[1,-2,3],逆序对数量为1.
第三个元素取反,数组将变成[1,2,-3],逆序对数量为2.

解题

定义两个数组leftInverseNumsrightInverseNums,分别记录nums中下标为 i i i 的元素 n u m num num 与其左侧和右侧的数字构成的逆序对数量。
数组nums原本的逆序对数量可由两个数组中的任一获得:totalInverseNum = sum(leftInverseNums)
假设将下标为 i i i 的元素 n u m num num 取反。

  • 对于 n u m num num 左侧的元素, − n u m -num num 会它们都更小,因此左侧均构成逆序对:totalInverseNum += i
    • 因为totalInverseNum已经统计过左侧元素了,避免重复统计,要减去左侧原有的逆序对数:totalInverseNum -= leftInverseNums;
  • 对于 n u m num num 右侧的元素,原本比 n u m num num 小的值不再比 − n u m -num num小,右侧的逆序对不再成立:rightInverseNums -= rightInverseNums

而计算leftInverseNumsrightInverseNums的过程可以使用树状数组进行:

bitLeft = BIT(n)  # 根据题设,数字分布于[1,n]区间,bit[i]表示区间[1,i]有多少数字已被遍历
leftInverseNums = [0 for _ in range(n)]  # 记录nums[i]和左侧数字构成的逆序对数
for i, num in enumerate(nums):
	# [num+1, n] = [1, n] - [1, num],表示此前有多少大于num的数字,即num和它之前的数字构成的逆序对数量。
    val = bitLeft.query(n) - bitLeft.query(num)  
    leftInverseNums[i] = val
    bitLeft.add(num, 1)  # 关键:遍历过的位置置1,那么区间和就是区间内遍历过的元素个数

右侧也是如此:

bitRight = BIT(n)
rightInverseNums = [0 for _ in range(n)]  # 记录nums[i]和右侧数字构成的逆序对数
# 从右向左查询比当前num小的数字个数,即num右侧的逆序对数量。这些逆序对在num取反后不再逆序。
for i, num in reversed(list(enumerate(nums))):  
    val = bitRight.query(num-1)  # 比num小的数字即属于区间[1, num-1]
    rightInverseNums[i] = val
    bitRight.add(num, 1)

完整代码如下:

n = 4
nums = [1, 4, 3, 2]

# 树状数组
class BIT:
    def __init__(self, n):
        self.n = n
        self.tree = [0 for _ in range(n+1)]  # 下标从1开始

    def lowBit(self, x: int) -> int:
        return x & (-x)

    def add(self, i: int, val: int):
        while i <= self.n:
            self.tree[i] += val
            i += self.lowBit(i)  # i的父节点下标为i+lowbit(i)
        
    def query(self, i: int = 1) -> int:  # 查询(0,i]区间内的所有元素的和
        res = 0
        while i > 0:
            res += self.tree[i]
            i -= self.lowBit(i)  # 下一个要查找的坐标为i-lowbit(i)
        return res

bitLeft = BIT(n)  # 根据题设,数字分布于[1,n]区间,bit[i]表示区间[1,i]有多少数字已被遍历
leftInverseNums = [0 for _ in range(n)]  # 记录nums[i]和左侧数字构成的逆序对数
for i, num in enumerate(nums):
    val = bitLeft.query(n) - bitLeft.query(num)  # [num+1, n] = [1, n] - [1, num],表示此前有多少大于num的数字,即num和它之前的数字构成的逆序对数量。
    leftInverseNums[i] = val
    bitLeft.add(num, 1)  # 关键:遍历过的位置置1,那么区间和就是区间内遍历过的元素个数

bitRight = BIT(n)
rightInverseNums = [0 for _ in range(n)]  # 记录nums[i]和右侧数字构成的逆序对数
for i, num in reversed(list(enumerate(nums))):  # 从右向左查询比当前num小的数字个数,即num右侧的逆序对数量。这些逆序对在num取反后不再逆序。
    val = bitRight.query(num-1)  # 比num小的数字即属于区间[1, num-1]
    rightInverseNums[i] = val
    bitRight.add(num, 1)

totalInverse = sum(rightInverseNums)  
# print(bitLeft.tree)
# print(bitRight.tree)
# print(leftInverseNums)
# print(rightInverseNums)
for i in range(n):
    print(totalInverse - rightInverseNums[i] + i - leftInverseNums[i], end = ' ')

参考文章

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值