-9 逆序输出一个整数的各位数字_每日算法LeetCode629:K个逆序对数组(难度系数3/5)...

导读:算法哥今天继续开启leetcode hard模式,今天给大家准备了一道有趣的数学题,希望今天的分享会让大家对动态规划耳目一新。

题目描述:

给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字的排列,且恰好拥有 k 个逆序对的不同的数组的个数。

逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。

由于答案可能很大,只需要返回 答案 mod 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 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。

题目分析:

咋一看,这题目不是个排列组合的数学题吗?没错,还真是!可是今天算法哥要分享一个利用动态规划的思想来解决这道题目的方法。啥?这也能动态规划!!!


要动态规划,就必须设计好状态转移的方程式,首先我们定义一个二维数组f[i][j]表示用[1,2,3...i]这i个整数排列后有j个逆序对的数量。为啥要这么定义呢?继续往下看!


定义好f[i][j]的状态后,我们就得思考怎么进行状态转移,假设你现在知道f[i - 1][j]了,也就是说你知道了用[1,2,3...i-1]前i - 1个数排列后有j个逆序对的数量是f[i-1][j],此时,把整数i,直接放到前面用i-1个数排列后有j个逆序对的数组末尾,此时是不是还是j个逆序对,因为i是最大的,它放在最后,不会产生新的逆序对!这是不是就得到了用[1,2,3...i]前i个整数排列产生j个逆序对的一种方式?举个例子!


f[3][1]=2,因为用[1,2,3]组成1个逆序对的排列有两种,[1,3,2],[2,1,3],现在我们在这两个排列的末尾都补上4,变成[1,3,2,4],[2,1,3,4],此时每个排列的逆序对还是1,因为添加的4是最大的,不会产生新的逆序对,此时是不是推导出了f[4][1]里的一种情形?那f[4][1]还有其他情形吗?肯定有,比如4不在排列的末尾!

2904fe131c178ffb3ebc41a1c0046348.png

f[3][1]推导f[4][1]


f[3][0]=1,因为用[1,2,3]组成0个逆序对的排列只有一种,[1,2,3],现在要推导出f[4][1],怎么推导?直接把4放到2和3的中间,变成[1,2,4,3]是不是就推导出了f[4][1]的又一种情形?

ca3ab56e557132578fcd1c08eff7599c.png

f[3][0]推导f[4][1]


那还有其他情形吗?没有了!聪明的读者发现规律了吗?

f[4][1] = f[3][1] + f[3][0]同理,我们有:f[4][4] = f[3][4] + f[3][3] + f[3][2] + f[3][1] 为什么这里不加f[3][0] ?因为f[3][0]对应排列[1,2,3]此时把4插入到头部变成[4,1,2,3]逆序对也只有3个,不满足4个的要求!

我们再抽象一点:

f[i][j] = f[i-1][j] + f[i-1][j-1] + f[i-1][j-2]+......+f[i-1][0],(i-1 >= j)

f[i][j] = f[i-1][j] + f[i-1][j-1] + f[i-1][j-2]+......+f[i-1][j-(i - 1)],(i - 1 < j)

两个推导式合并

f[i][j] = f[i-1][j] + f[i-1][j-1] + f[i-1][j-2]+......+f[i-1][max(0, j-(i - 1))]

到这里就结束了吗?如果忽略时间复杂度,确实可以结束了,因为f[i][j]可以转化成规模更小的f[i - 1][j],f[i - 1][j -1]...来推导,这正好是动态规划的思路,但是这么做的复杂度是O(n*k^2)的,怎么推导的请读者自己思考。下面将给出算法哥优化好的一个解法!

前面的推导式,我们把j用j+1替代!

f[i][j+1] = f[i-1][j+1] + f[i-1][j] + f[i-1][j-1]+......+f[i-1][max(0, j+1-(i - 1))]

我们用后一个推导式减去前一个推导式得到:

f[i][j+1] = f[i-1][j+1] + f[i][j] - (i - 1<= j ? 1:0) * f[i-1][max(0, j + 1-(i - 1))]

把j + 1,用j替换得到:

f[i][j] = f[i-1][j] + f[i][j-1] - (i <= j ? 1:0) * f[i-1][max(0, j - i)] 

到这里,最关键的状态转移方程终于出来了!

直接上源码:

40a16f7b4c486da84fde2681ccf524c6.png

O(nk)


复杂度分析:

f[i][j]里,i<=1000, j<=1000,结合代码看两层循环嵌套,所以复杂度是O(nk)的!轻松击败leetcode上98%的提交!


题目总结:

这个题目有几个难点:

  1. 用前i - 1个数的逆序对去推导前i个数的逆序对,发现这个规律是解决问题的关键;
  2. 发现规律后,将推导式做出进一步推导,得到一个简化的状态转移方程;
  3. 聪明的读者肯定发现算法哥的代码里数组只开了2维,因为每次推导的过程中只有当前这一个维度的数组,和前一个维度的数组在使用,所以可以用滚动数组的技巧来优化空间原先需要n维的数组变成来2维,这么做既减少空间,时间上也会有提升(因为数组小了,cpu cache命中率提高了)!

坚持到这里,相信你肯定收获满满的,那就赶紧关注,转发,点赞吧!让算法哥有激情继续分享下去!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值