LeetCode题练习与总结:K 个逆序对数组--629

一、题目描述

对于一个整数数组 nums逆序对是一对满足 0 <= i < j < nums.length 且 nums[i] > nums[j]的整数对 [i, j] 。

给你两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个 逆序对 的不同的数组的个数。由于答案可能很大,只需要返回对 10^9 + 7 取余的结果。

示例 1:

输入:n = 3, k = 0
输出:1
解释:
只有数组 [1,2,3] 包含了从1到3的整数并且正好拥有 0 个逆序对。

示例 2:

输入:n = 3, k = 1
输出:2
解释:
数组 [1,3,2] 和 [2,1,3] 都有 1 个逆序对。

提示:

  • 1 <= n <= 1000
  • 0 <= k <= 1000

二、解题思路

这个问题可以通过动态规划来解决。动态规划的核心思想是将问题分解为子问题,并保存子问题的解,以避免重复计算。

解题思路如下:

  1. 定义一个二维数组 dp,其中 dp[i][j] 表示从 1 到 i 的数字组成的数组恰好有 j 个逆序对的方案数。
  2. 初始化 dp 数组,dp[i][0] 表示从 1 到 i 的数字组成的数组逆序对为 0 的方案数,显然只有一种,即数组按升序排列。
  3. 对于每一个数字 i(从 1 到 n),我们可以将其插入到之前已经排好序的数组中,插入的位置决定了逆序对的数量。例如,将数字 i 插入到数组 [1, 2, ..., i-1] 中,插入到第 j 个位置会增加 j 个逆序对。
  4. 根据这个性质,我们可以更新 dp 数组:dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + ... + dp[i-1[0],但是直接这样计算会导致时间复杂度过高。我们可以通过前缀和优化这个计算过程。
  5. 最终答案是 dp[n][k]

三、具体代码

class Solution {
    public int kInversePairs(int n, int k) {
        final int MOD = 1000000007;
        int[][] dp = new int[n + 1][k + 1];
        dp[0][0] = 1;

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= k; j++) {
                dp[i][j] = dp[i - 1][j]; // 当前数字不增加逆序对的情况
                if (j > 0) {
                    dp[i][j] = (dp[i][j] + dp[i][j - 1]) % MOD; // 加上增加一个逆序对的情况
                }
                if (j >= i) {
                    dp[i][j] = (dp[i][j] - dp[i - 1][j - i] + MOD) % MOD; // 减去超过 i 个逆序对的情况
                }
            }
        }
        return dp[n][k];
    }
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 代码中包含两个嵌套循环,外层循环遍历数字 n(从 1 到 n),内层循环遍历逆序对的数量 k(从 0 到 k)。
  • 在内层循环中,除了初始化 dp[i][j] 为 dp[i-1][j] 之外,还可能执行两次模运算(一次加法,一次减法),这些操作的时间复杂度都是常数时间 O(1)。
  • 因此,内层循环的每一次迭代的时间复杂度是 O(1),总共执行了 n*(k+1) 次。
  • 综上所述,整个算法的时间复杂度是 O(n*k)。
2. 空间复杂度
  • 代码中使用了一个二维数组 dp 来存储子问题的解,这个数组的大小是 (n+1) 行和 (k+1) 列。
  • 因此,数组 dp 占用的空间是 (n+1)*(k+1)。
  • 除此之外,算法中没有使用额外的空间,所有的临时变量都是常数空间。
  • 综上所述,整个算法的空间复杂度是 O(n*k)。

五、总结知识点

  • 动态规划(Dynamic Programming)

    • 动态规划是一种算法设计技术,用于解决具有重叠子问题和最优子结构性质的问题。
    • 在本代码中,dp[i][j] 表示从 1 到 i 的数字组成的数组恰好有 j 个逆序对的方案数。
  • 二维数组

    • 代码中使用了一个二维数组 dp 来存储中间结果,这是动态规划中常见的做法。
  • 模运算(Modulo Operation)

    • 由于结果可能非常大,使用模运算来确保结果在 10^9+7 的范围内,这是编程竞赛中常见的技巧。
  • 边界条件初始化

    • dp[0][0] = 1 初始化为 1,表示没有数字时逆序对的数量为 0 的方案数为 1。
  • 嵌套循环

    • 两个嵌套循环用于填充动态规划表,外层循环遍历数字 i,内层循环遍历逆序对的数量 j。
  • 状态转移方程

    • 代码中包含了状态转移方程的实现,即如何从一个状态(dp[i-1][...])转移到另一个状态(dp[i][...])。
  • 取模运算中的负数处理

    • 在减法操作后立即加上模数,然后取模,是为了防止结果出现负数。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值