最大子数组问题

问题描述

一个只包含正负数的数组,求出连续元素之和最大的子数组。

解决1:暴力求解方法

尝试每个元素的组合,最终选出合适的子数组,这种的时间复杂度为:O(n^2),代码如下:

(defun find-maximum-subarray-1 (arr)
  "暴力穷举"
  (let ((len (length arr))
    (start 0)
    (end 0)
    (max-sum (aref arr 0)))
    (dotimes (i len (values start end max-sum))
      (let ((tmp 0))
    (do ((j i (1+ j)))       
        ((>= j len))
      (setf tmp (+ tmp (aref arr j)))
      (if (> tmp max-sum)
          (progn
        (setf max-sum tmp)
        (setf start i)
        (setf end j))))))))

解决2:分治策略求解方法

如果有一个数组a[low ... high],用分治策略求解子数组时,需要将a分解成两个规模相等的子数组,也就是说,找到相对父数组的中央位置,比如mid,然后考虑寻找两个子数组a[low ... mid]和a[mid + 1 ... high]最大子数组。假设此时已经无法分解子数组,设最大连续子数组为a[i ... j],那么它的为止必定是下列三种情况之一:

  • 1 完全位于a[low ... mid]中,(low <= i <= j <= mid)
  • 2 完全位于a[mid + 1 ... high]中,(mid < i <= j <= high)
  • 3 跨越中点,(low <= i <= mid < j <= high)

前两种情况可以通过分治策略递归的求解问题,第三种情况因为有必须跨越中点这个限制,所以也很容易线性时间内求解问题:

lisp版本:

(defun find-max-crossing-subarray (arr low mid high)
  "
寻找跨越下标mid的最大子数组
FIND-MAX-CROSSING-SUBARRAY(A, low, mid, higt)
    left-sum = -∞
    sum = 0
    for i = mid downto low
        sum = sum + A[i]
        if sum > left-sum
            left-sum = sum
            max-left = i

    right-sum = -∞
    sum = 0
    for j = mid + 1 to high
        sum = sum + A[j]
        if sum > right-sum
            right-sum = sum
            max-right = j

    return (max-left, max-right, left-sum + right-sum)
"
  (let ((left-sum (aref arr mid))
    (right-sum (aref arr (+ mid 1)))
    (max-left mid)
    (max-right (+ mid 1))
    (sum1 0)
    (sum2 0))
    (do ((i mid (1- i)))
    ((< i low))
      (progn
    (setf sum1 (+ sum1 (aref arr i)))
    (if (> sum1 left-sum)
        (progn
          (setf left-sum sum1)
          (setf max-left i)))))
    (do ((j (+ mid 1) (1+ j)))
    ((> j high))
      (progn
    (setf sum2 (+ sum2 (aref arr j)))
    (if (> sum2 right-sum)
        (progn
          (setf right-sum sum2)
          (setf max-right j)))))
    (values max-left max-right (+ left-sum right-sum))))

scala版本:

    /**
      * @param arr 数组
      * @param low 数组左闭下标
      * @param mid 中点
      * @param high 数组右闭下标
      * @return (左下标,右下标,元素最大之和)
      */
    def `find-max-crossing-subarray`(arr: Array[Int], low: Int, mid: Int, high: Int): (Int, Int, Int) = {
        var ls = arr(mid)       //left sum
        var rs = arr(mid + 1)   //right sum
        var ml = mid            //max left index
        var mr = mid + 1        //max right index
        var sum1 = 0            //tmp sum for left
        var sum2 = 0            //tmp sum for right

        for (i <- mid to low by -1) {
            sum1 = sum1 + arr(i)

            ls = sum1 match {
                case sum: Int if sum > ls => {
                    ml = i
                    sum
                }
                case _ => ls
            }
        }

        for (i <- (mid + 1) to high) {
            sum2 = sum2 + arr(i)

            rs = sum2 match {
                case sum: Int if sum > rs => {
                    mr = i
                    sum
                }
                case _ => rs
            }
        }
        (ml, mr, ls + rs)
    }

下面,就可以写出最大子数组的完整代码了:

(defun find-maximum-subarray (arr low high)
  "
寻找最大子数组 分治算法
FIND-MAXIMUM-SUBARRAY(A, low, high)
    if low == high
        return (low, high, A[low]) //only one element

    else
        mid = floor((low+high)/2)
        (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
        (right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
        (cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)

        if left-sum >= right-sum and left-sum >= cross-sum
            return (left-low, left-high, left-sum)
        elseif right-sum >= left-sum and right-sum >= cross-sum
            return (right-low, right-high, right-sum)
        else
            return (cross-low, cross-high, cross-sum)
"
  (if (= low high)
      (values low high (aref arr low))
      (let ((mid (floor (/ (+ low high) 2))))
    (multiple-value-bind (left-low left-high left-sum) (find-maximum-subarray arr low mid)
      (multiple-value-bind (right-low right-high right-sum) (find-maximum-subarray arr (+ mid 1) high)
        (multiple-value-bind (cross-low cross-high cross-sum) (find-max-crossing-subarray arr low mid high)
          (cond ((and (> left-sum right-sum) (> left-sum cross-sum)) (values left-low left-high left-sum))
            ((and (> right-sum left-sum) (> right-sum cross-sum)) (values right-low right-high right-sum))
            (t (values cross-low cross-high cross-sum)))))))))

或者:

    /**
      * @param arr 数组
      * @param low 数组左闭下标
      * @param high 数组右闭下标
      * @return (左下标,右下标,元素最大之和)
      */
    def `find-maximum-subarray` (arr: Array[Int], low: Int, high: Int): (Int, Int, Int) = {
        if (low == high)
            return (low, high, arr(low))
        else {
            val mid = math.floor((low + high) / 2).toInt

            val left = `find-maximum-subarray`(arr, low, mid)
            val right = `find-maximum-subarray`(arr, mid + 1, high)
            val cross = `find-max-crossing-subarray`(arr, low, mid, high)

            if (left._3 >= right._3 && left._3 >= cross._3)
                return (left._1, left._2, left._3)
            else if (right._3 >= left._3 && right._3 >= cross._3)
                return (right._1, right._2, right._3)
            else
                return (cross._1, cross._2, cross._3)
        }
    }

find-max-crossing-subarray时间复杂度为线性阶,根据递归树可以分析出find-maximum-subarray的时间复杂度为nlgn,所以分治策略求解最大子数组问题为O(nlgn),比暴力求解有更改效率。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容