小C的逆序对挑战| 豆包MarsCode AI刷题

82 阅读3分钟

一、问题描述

给定一个长度为 n 的数组 a = [a_1, a_2, ..., a_n],每次操作可以选择以下两种方式之一:

  1. 选择一个元素 a_i,对其加上一个值 x
  2. 选择一个元素 a_i,对其减去一个值 y

逆序对:存在一对下标 (i, j) 满足 1 ≤ i < j ≤ na_i > a_j。 我们的目标是求最少需要多少次操作才能确保数组中出现至少一个逆序对。

二、问题分析

  1. 逆序对的定义:逆序对 (i, j) 满足 i < ja_i > a_j,也就是说,数组不是单调递增的。

  2. 操作的影响

    • x 操作:增大a_i的值。
    • y 操作:减小a_j的值。 可以看到两个操作在改变现有元素对的差值功能方面是等效的,对于需要处理的元素 (a_i , a_j),a_i <= a_j,对a_i加上x和对a_j减去y,都是使a_j-a_i减小。
  3. 任务核心:通过最少的操作次数来确保数组出现逆序对。要使操作次数尽量少则需要改变的量尽量小,即两个元素的差值最小,而且每次改变的量尽量大,也就是说,每次都选xy中较大的一个数(题目没给数据范围,默认xy是正值)。

  4. 边界条件:如果数组已经存在逆序对,则直接返回操作次数为 0

三、解题思路

  1. 检查是否已经存在逆序对

    • 如果数组中已经存在逆序对,则返回 0。我们可以通过遍历数组来检查相邻元素是否满足 a_i > a_j,如果找到了,就可以直接返回。
  2. 寻找最小差值

    • 如果数组是升序的,那么我们需要通过操作来打破这种升序状态。同样需要遍历数组计算相邻元素的差值,故可以一边检查元素是否升序,一边计算差值并比较计算目前的最小差值。
  3. 操作次数的计算

    • 通过选择较大的操作步长(xy 中较大的那个),我们可以最小化操作次数。
  4. 最优解

    • 计算最少的操作次数: (mininum + 1) / max(x, y),使用 math.ceil() 向上取整,以确保能完成打破升序排列的操作。

四、代码实现

import math

def solution(n: int, x: int, y: int, a: list) -> int:
    # 初始化最小差值为无穷大
    mininum = float('inf')
    
    # 遍历数组,寻找相邻元素的最小差值
    for i in range(n-1):
        # 如果已经找到逆序对,直接返回0
        if a[i] > a[i+1]:
            return 0
        # 记录最小的相邻差值
        elif a[i+1] - a[i] < mininum:
            mininum = a[i+1] - a[i]
    
    # 选取操作步长中的较大值
    maxinum = max(x, y)
    
    # 计算需要的最少操作次数
    result = math.ceil((mininum + 1) / maxinum)
    
    return result

五、复杂度分析

  1. 时间复杂度O(n),遍历数组一次,时间复杂度是线性的。
  2. 空间复杂度O(1),只使用了常数级别的额外空间。

由于算法只遍历了数组一次,并且没有额外复杂的数据结构,所以时间复杂度是线性的,空间复杂度是常数级别的。

六、总结与收获

通过这道题,我学到了如何通过简单的遍历和数学运算来实现数组的操作,使得数组满足特定条件。最关键的是通过分析差值,结合 xy 的步长来计算最少的操作次数。这样的思路不仅可以解决本题,也能应用到其他一些基于差值操作的优化问题中。