力扣 629. K个逆序对数组

本文解析了LeetCode题目中关于构造含有k个逆序对的1到n整数数组的动态规划方法,通过状态转移方程dp[i][j]解释了如何递推求解,并展示了关键的dp状态更新过程。

题目来源:https://leetcode-cn.com/problems/k-inverse-pairs-array/

大致题意:
给定整数 n 和 k,求出用 1 - n 组成的含有 k 个逆序对的不同数组个数
逆序对是指对于第 i 个和第 j 个元素,有 i < j,但是 i 位置的元素大于 j 位置的元素

思路

大概知道是动态规划,但是完全没有解题思路,甚至还读错了题(我以为是要求逆序对的差值为 k,这样有多少个)
然后看题解给我看蒙了,好久才明白。反正就是 dp

DP

既然是 dp,那么就需要找到状态转移方程
使用 dp[i][j] 表示 i 个数组成的含有 j 个逆序对的数组的个数
然后考虑第 i 个数,如何求出当前新加的 i 与之前的 dp 状态的关系

可知放在位置 k (0 <= k <= i - 1)就表示第 i 个数和第 k 个数换位置

  • 如果 i 就放在最后的位置 i - 1,那么显然 dp[i][j] = dp[i-1][j],因为它是最大的,又放在最后面,不会增加逆序对个数,那么想有 j 个逆序对,就需要前 i - 1 个数形成的逆序对有 j 个
  • 如果 i 就放在开始的位置 0,那么显然逆序对个数增加了 i - 1 个,那么想有 j 个逆序对,就需要前 i - 1 个数形成的逆序对有 j - i + 1 个
  • 如果 i 放在第 k 个位置(0 < k < i - 1),那么位置 k 之后的数都与 i 形成逆序对,有 i - 1 - k 个,那么想有 j 个逆序对,就需要前 i - 1 个数形成的逆序对有 j - i + k + 1个 ,显然 k 为 0 和 i - 1 时也成立


在这里插入图片描述
另 i - 1 - k = t,再将 t 置换为 k,有

f[i][j]
在这里插入图片描述
然后 可得 f[i][j] - f[i][j - 1] = f[i - 1][j] - f[i - 1][j - i]
即 f[i][j] 只会从 f[i - 1][…] 和 f[i][…] 得到,可以减少时空复杂度

public int kInversePairs(int n, int k) {
        int MOD = 1000000007;
        // 新一轮的状态只与上一轮有关,因此只存两轮状态即可
        int[][] dp = new int[2][k + 1];
        // 边界条件, 0 个数形成 0 个逆序对只有一种
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            // 交替变换待更新的当前状态
            int cur = i & 1;
            int prev = cur ^ 1;
            for (int j = 0; j <= k; j++) {
                // 判断,当 dp[i][j] 的 j 小于 0 时,种类为 0
                dp[cur][j] = (j > 0 ? dp[cur][j - 1] : 0) - (j >= i ? dp[prev][j - i] : 0) + dp[prev][j];
                // 取模
                if (dp[cur][j] >= MOD) {
                    dp[cur][j] -= MOD;
                } else if (dp[cur][j] < 0) {
                    dp[cur][j] += MOD;
                }
            }
        }
        return dp[n & 1][k];
    }
<think>我们参考了多个关于删除数组元素的引用,并结合JavaScript语言特点进行说明。在编程中删除数组元素是常见操作,但需要特别注意删除后数组长度变化对循环索引的影响。以下是几种常用方法:###方法1:使用`splice()`删除指定位置元素`splice()`方法可以删除数组中任意位置的元素,同时会改变原数组。```javascriptletarr=[1,2,3,4,5];//删除索引2开始的1个元素(即第三个元素)arr.splice(2,1);console.log(arr);//输出:[1,2,4,5]```###方法2:使用`pop()`和`shift()`删除首尾元素-`pop()`删除数组最后一个元素-`shift()`删除数组第一个元素```javascriptletarr=[1,2,3];arr.pop();//删除末尾元素console.log(arr);//[1,2]arr.shift();//删除头部元素console.log(arr);//[2]```###方法3:使用`length`属性截断数组直接将数组的`length`属性设置为较小值会丢弃超出新长度的元素。```javascriptletarr=[1,2,3,4,5];arr.length=3;//截断数组console.log(arr);//[1,2,3]```###循环中删除元素的注意事项当在循环中删除元素时,建议使用倒序循环避免索引错位:```javascriptletarr=[1,2,3,4,5];for(leti=arr.length-1;i>=0;i--){if(arr[i]%2===0){//删除偶数元素arr.splice(i,1);}}console.log(arr);//[1,3,5]```###力扣例题:删除有序数组重复项(原地操作)引用[4]中的力扣26题可以通过双指针技巧实现:```javascriptvarremoveDuplicates=function(nums){if(nums.length===0)return0;letk=1;//慢指针for(leti=1;i< nums.length;i++){//i是快指针if(nums[i]!==nums[i-1]){nums[k]=nums[i];k++;}}returnk;//返回不重复元素的个数};```>**要点总结**:>1.`splice()`是最灵活的元素删除方法;>2.循环中删除时应逆序遍历;>3.双指针技巧可用于需要保持顺序的原地操作;>4.根据需求选择删除方式,例如只需清空尾部时可用`length`截断。§§相关问题§§1.如何清空整个数组?2.删除数组元素后如何撤销删除操作?3.在JavaScript中,`deletearr[index]`和`splice(index,1)`有什么区别?</think> 在编程中删除数组数组元素的方法因语言而异,以下是基于引用资料和最佳实践的详细解答(以JavaScript为例): ### 一、删除数组元素的常用方法 1. **splice()方法**(推荐使用) 删除任意位置的元素,支持批量删除: ```javascript let arr = [1, 2, 3, 4, 5]; // 删除索引2开始的1个元素 arr.splice(2, 1); console.log(arr); // [1, 2, 4, 5] ``` 2. **pop()和shift()方法** - `pop()`:删除末尾元素 ```javascript arr.pop(); // [1, 2, 4] ``` - `shift()`:删除开头元素 ```javascript arr.shift(); // [2, 4] ``` 3. **length属性截断** 快速删除尾部元素: ```javascript let arr = [1, 2, 3, 4, 5]; arr.length = 3; // [1, 2, 3] ``` 4. **delete操作符(慎用)** 不会改变数组长度,留下空洞: ```javascript delete arr[1]; console.log(arr); // [1, undefined, 3] ``` ### 二、循环中删除元素的技巧 在遍历数组时直接删除可能导致索引错乱,安全方案: ```javascript // 反向遍历避免索引错位 let arr = [1, 2, 3, 4, 5]; for (let i = arr.length - 1; i >= 0; i--) { if (arr[i] % 2 === 0) { // 删除偶数 arr.splice(i, 1); } } // 结果: [1, 3, 5] ``` ### 三、特殊场景处理(参考力扣26题) 删除有序数组重复项(原地操作): ```javascript function removeDuplicates(nums) { if (nums.length === 0) return 0; let k = 1; // 慢指针 for (let i = 1; i < nums.length; i++) { if (nums[i] !== nums[k-1]) { nums[k] = nums[i]; // 覆盖非重复值 k++; } } return k; // 返回唯一元素数量 } ``` > **逻辑说明**:快指针`i`遍历数组,慢指针`k`记录唯一元素位置,时间复杂度 $O(n)$[^4] ### 四、不同方法的特性对比 | 方法 | 改变原数组 | 适用场景 | 索引影响 | |-------------|------------|------------------------|----------| | `splice()` | ✅ | 任意位置删除 | 自动调整 | | `pop()` | ✅ | 仅删除末尾元素 | 无偏移 | | `length` | ✅ | 批量截断尾部元素 | 直接修改 | | `delete` | ❌ | 不推荐(产生undefined)| 索引保留 | > **最佳实践建议**:在需要维护索引稳定性的场景(如循环操作),优先使用 `splice()` 结合反向遍历;需要保持数组连续时避免使用 `delete`[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

三更鬼

谢谢老板!

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

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

打赏作者

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

抵扣说明:

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

余额充值