数组最大差值的最优解法(动态规划)
最近在公司要计算一下我们所有用户排列中相连两个人的年龄差的到最大差值以统计公司用户年龄层。
我们公司的客户是数量很大,所以普通的排序求差值或者快排算法其实很难满足要求。
一个简单的排序算法求解如下:
def stepIn(dataInput) dataLen = dataInput.length diff = nil dataResule = {} for i in 0...dataLen-1 for n in i+1...dataLen diff2 = dataInput[n] - dataInput[i] if diff == nil or diff < diff2 diff = diff2 dataResule["#{i},#{n}"] = diff end end end rIdxs = dataResule.sort_by {|k,v| -v} [0][0].split ',' return [dataInput[rIdxs[0].to_i], dataInput[rIdxs[1].to_i]] end
上面的代码已经经过了优化,在每次循环后,保留了之前计算的差值的结果,下面的循环中小于这个差值的索引值就被抛弃了,这样的一个好处是可以减少最后sort时花费的时间。
假如保留所有两数之间的差值,假设使用冒泡排序,输入数组长度是m,排序算法复杂度是O(n2),而这个n会达到(m+1)*m/2,所以总的算法复杂度就成了O(n4)。
而在循环中预处理之后,最后参与排序的元素个数最大不会超过m,总的时间复杂度还是O(n2)。
其实这只是针对最后的sort而言,而这个程序真正的耗时在上面的嵌套循环,这里的复杂度不管有没有优化,其实都是一样的O(n2),下面sort的消费可以忽略不计。
这是一种比较直观的解法了,事实证明,没有经过斟酌的想法都是不完善的。这个算法最后用到实际运算时需要消耗的资源和时间是成指数上升的。
后来仔细想了一下,其实完全可以使用动态规划的思想去解决这个问题。
动态规划的思想通常可以分成下面几部:
- 给问题分阶段
- 确定每个阶段的状态
- 确定相邻阶段的之间的递推关系(也就是找出从前一个阶段转化到后一个阶段的条件)
上面的问题很容易可以分出三个阶段
- 开始阶段,将数组中开头的两个元素作为最大,最小值记录在结果数组中,[A,B]
- 过程阶段,将后面的数与前面的数比较,比如将C与B比较,并将符合条件的值替换结果数组
- 结束阶段,当游标抵达数组最后一个元素时,跳出循环。
而这几个状态之间的转移条件在上面已有了说明,主要在第二个阶段,哪些条件能决定替换结果数组,这些条件称为决策
- 游标所指的数大于结果数组中的最大值,比如后面有C,那么结果数组就变成[A,C]
- 游标所指的数小于结果数组中的最小值,那么它就有可能在后面替换结果数组中的最小值,例如后面出现了A-1,这个时候不能立刻替换掉A,需要找个临时变量将A-1保存下来。
- 游标所指的数与临时最小值之差大于结果数组中两数字之差。这个条件应该优先于决策2和决策1,一旦这个决策生效,将同时替换结果数组中的最大最小值,决策1和决策2在这个时候应该不生效。例如后面出现了D,那么结果数组就应该变成[A-1,D]。假如这个时候决策1优先生效,那么结果数组会变成[A,D],而临时变量A-1将永远没有上位之日了。
有了上面的阶段和决策之后,代码就很容易实现了
1 def stepIn(list) 2 min = 0 # minimal index 3 max = 0 # maximal index 4 differ = 0 # max differ 5 minTmp = nil # temp minimal index 6 for i in 1...list.length 7 if minTmp != nil and list[i] - list[minTmp] > differ # if current index minus temp minimal index is bigger than differ, replace it 8 differ = list[i] - list[minTmp] # new differ 9 min = minTmp # new minimal index 10 max = i # new maximal index 11 elsif list[i] > list[max] # replace the maximal index 12 max = i # new maximal index 13 differ = list[i] - list[min] # new differ 14 elsif list[i] < list[min] and ( minTmp == nil or list[i] < list[minTmp] ) # replace the temp minimal index 15 minTmp = i # change temp minimal index 16 else 17 next 18 end 19 end 20 return [list[min], list[max]] 21 end
可以看出使用第二种算法的时间增长基本是线性的。