这篇blog来源于leetcode。参加了第198场周赛,结果比前几回周赛惨不少。不过不要紧,及时发现了本身很菜,路漫漫其修远兮!这边blog主要是针对周赛第四题衍发出来的思考。主要包括RMQ问题以及本身思考题目的过程。价值不是很大,随便写写。算法
RMQ(Range Minimum / Maximum Query )主要是用来求区间最值问题研究出来的算法。
对于RMQ问题有不少方法能够求解,好比线段树,或者使用动态规划。对于静态区间的RMQ问题,使用DP是很是好理解的。下面咱们就来聊聊。
好比给定一个无序数组arr。求数组全部区间的最值。若是经过暴力枚举,确定会TLE。可是咱们很容易想到。对于一个大的区间[0,1],咱们能够将其分为两个子区间:[0,1]和[2,3]。那么大区间的最值,其实能够经过两个子区间获得。数组
大问题的结果依赖若干个子问题。函数
既然使用动态规划,咱们就须要列出状态转移方程。
咱们令dpi表示:以第i个数为起点,连续2^j个数 的区间最值。好比dp2就是区间[2,3]的最值。3怎么来。其实就是2+2^1-1 ,减1时减掉第一个数。优化
依据上面定义:dpi表明数组第i个数开始,连续一个数的区间的最值。连续一个数,其实就只有一个,就是他自己。spa
因此咱们获得,这里咱们假设数组arr 是从1开始。code
dp[i][0]=arr[i];
对于dpi,咱们如何作求值?blog
其实能够将这个区间分为2部分。第一部分为dpi,第二部分为dpi+(1<<(j-1))。而后依赖两个区间的结果再求大区间的最值。
你们看到这两个区间可能很懵逼。不着急看,咱们一个一个来分析。leetcode
这里的思想就是一分为2。前提是分出来的区间的结果是提早知道的。rem
因此咱们能够获得状态转移方程(以区间最大值举例):get
dp[i][j]= max {dp[i][j-1],dp[i+(1<<(j-1)][j-1]}
经过上面过程,咱们将最值计算出来,可是咱们如何获取结果呢?
咱们假设len为要查询区间的长度。咱们log(len)也就是咱们dpi中j的长度。可是咱们并不能保证2^log(len)==len。由于len不必定是2的整数幂。因此咱们并不能保证区间的完整性。
若是该长度正好是2的幂。那么没毛病,结果为dpi,不然咱们会遗漏一些区间,以下图。那么如何解决问题呢?
你们能够看到咱们能够使用dpi和dpr-(1<<k)+1。使用后者是为了补充咱们的遗漏。可是你们可能会担忧有重复。可是若是是求最值问题,重复是不会影响结果的。因此,很ok。
https://leetcode-cn.com/probl...
咱们对函数分析后,发现对于l<=r,他的结果是一个递减的序列。由于与运算。与的越多最终值越小。若是一个区间[l,r]按上述函数进行与。结果确定小于等于区间最小值。
既然是一个有序序列,咱们就能够使用二分。咱们枚举右边界。而后经过二分对区间求值并记录结果。时间复杂为nlog(n)
没错,开始我就是这么作的,可是:
看到这个,我就开始定位耗时。应该是在进行区间与运算的时候浪费时间。
因此咱们须要进行优化。
因而我想到了区间最值问题。与运算其实和其是同样的。好比同一个大的区间的与运算结果,咱们能够经过两个小区间的结果再进行与操做。
而且对于重复与相同数字,结果是不会受影响的,这个比较关键,由于咱们在查询区间最值的时候,会重复计算。
因而我用动态规划构建区间结果。最终解决了问题。
RMQ动态规划代码实现
//RMQ问题代码 type RMQ struct { Dp [][]int } func (rmq *RMQ) init(arr []int) { dp := make([][]int, len(arr)) rmq.Dp = dp for i := 0; i < len(arr); i++ { dp[i] = make([]int, 20) } //初始化条件。从i起的一个数(2^0)的最小值 就是该数。 for i := 1; i < len(arr); i++ { dp[i][0] = arr[i] } // for j := 1; (1 << j) < len(arr); j++ { for i := 1; i+(1<<(j-1)) < len(arr); i++ { //这里须要注意 为何临界条件为i+(1<<(j-1)) < len(arr)。 //由于i会被j限制。 j越大。i能取的就越小。咱们只须要保证从i开始到结束的元素全覆盖就能够了。 //这里将范围分红了两部分。 由于咱们基于2的幂。 其实就是参考二进制的性质。经过移位运算符能够进行二分。 dp[i][j] = rmq.withStrategy(i, j) } } } func (rmq *RMQ) withStrategy(i int, j int) int { return rmq.Dp[i][j-1] & rmq.Dp[i+(1<<(j-1))][j-1] } func (rmq *RMQ) withStrategyQuery(l int, r int, k int) int { return rmq.Dp[l][k] & rmq.Dp[r-(1<<k)+1][k] } func (rmq *RMQ) query(l int, r int) int { k := 0 for ; (1 << (k + 1)) <= r-l+1; k++ { } return rmq.withStrategyQuery(l, r, k) }
算法逻辑(二分)
func closestToTarget(arr []int, target int) int { minVal := math.MaxInt32 rmq := RMQ{} tmp := make([]int, len(arr)+1) for k := 0; k < len(arr); k++ { tmp[k+1] = arr[k] } rmq.init(tmp) for r := 1; r < len(tmp); r++ { left := 1 right := r for left <= right { mid := left + (right-left)/2 res := rmq.query(mid, r) if res == target { return 0 } else if res > target { right = mid - 1 } else { left = mid + 1 } } if right == 0 { minVal = min(minVal, rmq.query(left, r)-target) } else if left == r+1 { minVal = min(minVal, target-rmq.query(right, r)) } else { minVal = min(min(rmq.query(left, r)-target, minVal), target-rmq.query(right, r)) } } return minVal } func min(x, y int) int { if x > y { return y } return x }