算法导论笔记2 - T(n) = O(n) 的最大子数组问题解法

import random

__author__ = ‘Administrator‘

LENGTH = 500
base = []

for i in range(0, LENGTH * 2):
    base.append(random.randint(-1 * LENGTH, LENGTH))

print(base)
bsa_i = 0
bsa_j = 1
bsa = base[0]
bord = base[0]
bord_i = 0

for i in range(1, len(base)):
    if bord < 0:
        bord_i = i
        bord = base[i]
    else:
        bord += base[i]
    if bord >= bsa:
        bsa_i = bord_i
        bsa_j = i + 1
        bsa = bord
print(bsa_i, bsa_j, bsa)
print(base[bsa_i: bsa_j])

# verify:
bsa = -1 * LENGTH - 1
for i in range(0, len(base)):
    for j in range(i + 1, len(base) + 1):
        it = 0
        for k in range(i, j):
            it += base[k]
        if it > bsa:
            bsa = it
            bsa_i = i
            bsa_j = j
print(bsa_i, bsa_j, bsa)
print(base[bsa_i: bsa_j])

指针 i 右移时,记录以 i + 1 为右边界的最大子数组 bord,初始为 bord[0, 1],当 sum bord < 0 时, bord 左边界替换为 i 。 这种求法意为去掉左边的所有和为负的子数组,剩下的便是以 i + 1 为右边界的最大子数组。正确性可以这样验证:

反证法

原命题: bord[i, j] 中,如果不存在 k 属于 [i, j] 使得 sum A[i, k] <= 0, 这时不存在 l 属于 [i, j] 使得 sum B[l, j] >= sum bord[i, j]。

假设:bord[i, j] 中,如果不存在 k 属于 [i, j] 使得 sum A[i, k] <= 0, 这时存在 l 属于 [i, j] 使得 sum B[l, j] >= sum bord[i, j]。

如果 存在 l 属于 [i, j] 使得 sum B[l, j] >= sum bord[i, j],那么 sum C[i, l] = sum bord[i, j] - sum B[l, j] <= 0

令 l = k, 则假设矛盾不成立,原命题正确性得证。

bsa 数组记录的是 [0, i] 中的最大子数组,而 border 是 右边界为 i + 1 的最大子数组,若 sum border > sum bsa,则 border 成为 bsa。这样在一次遍历中,就完成了对整个数组的最大子数组 bsa 的查找,时间复杂度为 O(n),之后的验证算法采用三重循环暴力求解,时间复杂度为O(n ^ 3)。而三次方级别的复杂度对于一个稍大的 n 来讲,几乎就是一个无用的算法,所以设计高效的子数组求法是具有很强的实际意义的。

时间: 2024-10-13 16:03:23

算法导论笔记2 - T(n) = O(n) 的最大子数组问题解法的相关文章

MIT算法导论笔记

详细MIT算法导论笔记 (网络链接) 第一讲:课程简介及算法分析 第二讲:渐近符号.递归及解法

散列表(算法导论笔记)

散列表 直接寻址表 一个数组T[0..m-1]中的每个位置分别对应全域U中的一个关键字,槽k指向集合中一个关键字为k的元素,如果该集合中没有关键字为k的元素,则T[k] = NIL 全域U={0,1,…,9}中的每个关键字都对应于表中的一个下标值,由实际关键字构成的集合K={2,3,5,8}决定表中的一些槽,这些槽包含指向元素的指针,而另一些槽包含NIL 直接寻址的技术缺点非常明显:如果全域U很大,则在一台标准的计算机可用内存容量中,要存储大小为|U|的一张表T也许不太实际,甚至是不可能的.还有

算法导论笔记第6章 堆和堆排序

堆排序结合了插入排序和归并排序的有点:它空间复杂度是O(1), 时间复杂度是O(nlgn). 要讲堆排序,先讲数据结构"堆" 堆: 堆是用数组来存放一个完全二叉树的数据结构.假设数组名是A,树的根节点存放在A[1].它的左孩子存放在A[2],右孩子存放在A[3] 即:对于某个下标位i的节点,它的左孩子是A[2i],  右孩子是A[2i+1].  父节点是A[i/2] PARENT(i) return ?i/2? LEFT(i) return 2i RIGHT(i) return 2i

【LeetCode-面试算法经典-Java实现】【053-Maximum Subarray(最大子数组和)】

[053-Maximum Subarray(最大子数组和)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Find the contiguous subarray within an array (containing at least one number) which has the largest sum. For example, given the array [?2,1,?3,4,?1,2,1,?5,4], the contiguous subarra

MIT公开课:算法导论 笔记(一)

课程链接:http://open.163.com/special/opencourse/algorithms.html 第一课:算法分析基础 1.介绍插入排序与归并排序,计算并比较最坏运行时间 2.算法分析重点与渐近分析方法 以下为个人笔记,根据字幕整理 第一课 算法分析 总结 解决问题的方法和方式 算法:关于计算机程序性能和资源利用的研究 算法:性能.速度 在程序设计方面,什么比性能更重要呢? 正确性,可维护,健壮性 模块化,安全,用户友好 为什么关注性能? 1.直接决定方法可行不可行 算法能

算法导论笔记(二)二路归并排序

二路归并排序 归并排序采用了一种”分而治之“的策略:将原问题分解成N个规模较小而结构与原问题相似的子问题:递归求解这些子问题,然后合并其结果,从而得到原问题的解. 分治模式一般遵循以下三个步骤: 分解(Divide):将原问题分解成若干子问题: 解决(Conquer):递归地求解各子问题.若子问题足够小,则直接求解: 合并(Combine):将子问题的解合并成原问题的解. ”二路归并"的算法也遵循以下三个步骤: 分解:将原序列中拥有的N个元素分解各含N / 2个元素的子序列: 解决:用合并排序法

算法导论笔记(三)冒泡排序

冒泡排序 重复走访要排序的数列,比较相邻两个元素,如果顺序错误就交换,直到该数列无需再交换为止. 升序冒泡 void BubbleSorting(int arr[], int len) { if (len < 1) throw "Param is wrong. Length is not correct."; if (len == 1) return; int temp; for (int i = 0; i < len - 1; i++) { for (int j = 0;

算法导论笔记——第十五章 动态规划

通常用来解决最优化问题.在做出每个选择的同时,通常会生成与原问题形式相同的子问题.当多于一个选择子集都生成相同的子问题时,动态规划技术通常就会非常有效.其关键技术就是对每个这样的子问题都保存其解,当其重复出现时即可避免重复求解. 分治:划分为互不相交的子问题,递归求解子问题,再将他们的解组合起来. 动态规划(dynamic programming,表格法而非编程)用于子问题重叠的情况. 四个步骤来设计一个动态规划算法: 1 刻画一个最优解的结构特征 2 递归地定义最优解的值 3 计算最优解的值,

算法导论笔记1 - 插入排序 vs 归并排序

import random import time __author__ = 'Administrator' LENGTH = 3000 base = [] for i in range(0, LENGTH): base.append(random.randint(0, LENGTH)) def ins_sort(array): for border in range(1, len(array)): j = border - 1 key = array[border] while j >= 0