问题一:最大子数组问题
问题分析
我们的目标是找出一段连续的时间[A, B],使得price[B]- price[A]差值最大。将股票的价格数组转换成A[i]表示price[i]- price[i - 1],则问题转换成A数组的最大子数组问题
将问题更新,给定一个数组A[left…right],求出A数组的最大子数组(需要返回子数组的起始下标,终点下标,最大子数组之和)
分治算法中,子问题规模划分是否均匀会极大的影响算法效率,为了获得更好的时间,需要将子问题尽可能均匀的划分,记划分中点为mid,将数组划分为A[left, mid],A[mid+1,right]。最大子数组一定是下列三种情况之一
- 在A[left, mid]中
- 在A[mid+1, right]中
- 跨过mid,将最大子数组记为A[i,j],满足left i mid < j right
算法伪代码
FindMaxSubArray(A, left, right)
mid = (right + left) / 2
//分治法算子问题
(begin_left, end_left, max_sum_left) = FindMaxSubArray(A, left, mid)
(begin_right, end_right, max_sum_right) = FindMaxSubArray(A, mid + 1, right)
//处理子问题的解,得到最终解
(begin_mid, end_mid, max_sum_mid) = FindMaxCrossSubArray(A, mid, left, right)
if max_sum_mid >= max_sum_left && max_sum_mid >= max_sum_right
return (begin_mid, end_mid, max_sum_mid)
else if max_sum_left >= max_sum_right
return (begin_left, end_left, max_sum_left)
else
return (begin_right, end_right, max_sum_right)
//获得A数组中下标跨过mid的最大子数组
FindMaxCrossSubArray(A, mid, left, right)
sum = 0
max_sum_left = 负无穷
begin = mid
for i = mid downto left
sum += A[i]
if sum > max_sum_left
max_sum_left = max_sum
begin = i
sum = 0
max_sum_right = 负无穷
end = mid + 1
for i = mid + 1 to right
sum += A[i]
if sum > max_sum_right
max_sum_right = max_sum
end = i
return (begin, end, max_sum_left + max_sum_right)
时间复杂度分析
利用Master定理可以得到算法时间复杂度是O(
)
问题扩展一
问题分析
只需要在上述伪代码返回部分判断得到的最大子数组之和是不是小于0,如果小于0,返回[-1,-1,0]即可
问题扩展二
问题分析
(对于数组元素都是负数或者0的数组不予考虑,可以在下面的算法开始前判断一下,时间复杂度是
)
对于任何一个给定的数组,最大子数组一定是以一个正数作为开头,以正数作为结尾,因为如果开头是负数或者是0,将开头去掉,可以得到一个和更大或者和不变的最大子数组,同理,如果结尾是负数或者是0,同理。
算法的自然语言描述
- 初始化maxsum = 0,maxbegin = 0, maxend= 0
- 遍历数组元素,直到找到首个为正数的数组元素,将其加进persum,记录perbegin和perend.
- 比较persum与maxsum,如果persum大于maxsum,将maxsum、maxbegin、maxend更新
- 继续遍历数组,直到数组结束
- 如果遇到的数组元素是正数,将其加进persum,并更新perbegin,perend,跳转到3
- 如果遇到的数组元素是负数或者0
- 如果将该数组元素加进persum,persum仍然是正数,则将其加进去
- 如果是负数,则放弃当前的persum,perbegin,perend
算法伪代码
FindMaxSubArray(A, left, right)
maxsum = 0, maxbegin = 0, maxend = 0
persum = 0, perbegin, perend = 0
for i = left to right
if A[i] > 0
persum += A[i]
if perbegin == 0
perbegin = i
if perend == 0
perend = i
if (maxsum > persum)
maxsum = persum
maxbegin = perbegin
maxend = perend
elif A[i] <= 0
if persum + A[i] > 0
perend = i
persum += A[i]
else
persum = 0
perbegin = 0
perend = 0
return (maxbegin, maxend, maxsum)
还可以使用动态规划,见动态规划部分
问题二:最近点对
给定平面上n个点,求其中的一点对,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。
(1)设计一个时间复杂度为O(n2)的算法求距离最近的点对,要求写出算法伪代码;
(2)利用分治的思想设计一个时间复杂度为O(nlogn)的算法求距离最近的点对,要求写出算法伪代码。
问题分析
第一问比较简单,只要计算平面上所有的点之间的距离即可,算法时间复杂度是
第二问比较复杂,主要思想是尽可能的将点分成左右两部分,采用分治法分别计算出左右两部分的最小距离,但是点之间的最小距离,可能出现在左侧点和右侧点之间,合并阶段要处理这种情况,具体细节见伪代码
算法伪代码
FindMinDisSet(points) //输入是平面上的点的数组
length = points.length
mindis1, mindis2, mindis = 正无穷
for i = 0 to length
for j = 0 to length
if i != j
dis = Dis(points[i], points[j])//返回两点之间的欧氏距离
if (mindis > dis)
mindis = dis
mindis1 = i
mindis2 = j
return (points[i], points[j], mindis)
FindMinDis(points)
//如果只有两个点,直接返回两点之间的距离
if points.length == 2
return Dis(points[1], points[2])
//如果不够两个点,返回一个正无穷
if points.length < 2
return 正无穷
//可以利用选择算法在线性时间找到所有点横坐标的中位数
//根据找到的中位数将点分成左右两部分,下面分别处理
mid = points中所有点横坐标的中位数
for all point in points
if point.x <= mid
points_left.add(point)
else point_right.add(point)
dis_left = FindMinDis(points_left)
dis_right = FindMinDis(points_right)
//保存上面得到的两个距离中的最小值
dis_min = min(dis_left, dis_right)
//遍历所有点,找到所有距离直线X= mid距离小于dis_min的点,放到points_mid中
for all point in points
dis = distance between point and line X = mid
if (dis < dis_min)
points_mid.add(point)
points_mid_sorted = 将points_mid按照y从小到大排序
for all point in points_mid
记录point与其后六个点之间的距离
如果距离小于dis_min,则将其更新
return dis_min