[饭后算法系列] 数组中"和非负"的最长子数组
1. 问题
给定一列数字数组 a[n], 求这个数组中最长的 "和>=0" 的子数组. (注: "子数组"表示下标必须是连续的. 另一个概念"子序列"则不必连续)
举个例子:
数组 a[n] = {1, 2, -4, 5, -6, 1}, 最长的和非负的子数组为 {1, 2, -4, 5}, 其他子数组要么和<0, 要么长度<4
2. 暴力法
我们先来看看暴力解法和时间复杂度
1. 如果我求出所有的数组前缀和 即P(i) = a[1]到a[i]的和
2. 然后对于数组的所有子数组 a[i..j], 它的和为 P(j) - P(i-1)
第一步预处理的时间复杂度为O(n). 第二步是暴力法的主体, 穷举了所有O(n^2)个子数组, 每个子数组和的计算需要时间O(1). 因此整个算法的时间复杂度就是O(n^2)
由此可见, 任何优化必须使得复杂度小于 O(n^2)
3. 算法优化
算法是一门基于观察的学科, 让我们先从例子入手, 观察一下优化的方法
对于数组a[n], 用动态规划法从第一个数开始往后遍历
| 1 | 2 | -4 | 5 | -6 | 1 |
| (1,1) | (2,3) | (3,-1) | (4,4) | (5,-2) | (6,-1) |
| (1,2) | (2,-2) | (3,3) | (4,-3) | (5,-2) | |
| (1,-4) | (2,1) | (3,-5) | (4,-4) | ||
| (1,5) | (2,-1) | (3, 0) | |||
| (1,-6) | (2,-5) | ||||
| (1, 1) |
1. 遍历到第一个数1时, 填写(1,1), 表示第一个数的和为1
2. 遍历到第二个数2时, 填写(2,3), 表示前两个数的和为3; 填写(1,2), 表示前一个数(即2自己)的和为2
以此类推, 填完所有6列, 可以看到第4列的(4,4)是和为4>=0, 且长度最长的结果
这个做法仍然需要O(n^2), 有什么办法优化呢?
3.1 计算简化
这个表格的计算是可以简化的:
| 1 | 2 | -4 | 5 | -6 | 1 | |
| 0 | ||||||
| -1 | ||||||
| -3 | ||||||
| 1 | ||||||
| -4 | ||||||
| 2 | ||||||
| sum | 1 | 3 | -1 | 4 | -2 | -1 |
这是什么意思呢?
假设运算到第k列的时候, 所有以a[k]结尾的子数组和记为b[k] = [sum(1..k), sum(2..k), ..., sum(k..k)]
我只要记录b[k]的变形c[k]即可, c[k] = [0, -sum(1..1), -sum(1..2), ..., -sum(1..k-1)], 要从c[k]转回b[k], 我只要简单的把c中的每个元素值加上sum(1..k)
这样做的好处是: 我从c[k]前进到c[k+1]的时候, 只要简单地在最后加一个元素-sum(1..k), 而不需要修改前面的元素. 这使得前进一步的开销为O(1)
转化后, 我要找b[k]中>=0的元素, 等同于找c[k]中>=-sum(1..k)的元素, 这个查找过程怎么简化呢?
3.2 单调优化
这个过程是可以发现单调性优化的. 为了方便, 我把表格转回最开始的样子:
| 1 | 2 | -4 | 5 | -6 | 1 |
| (1,1) | (2,3) | (3,-1) | (4,4) | (5,-2) | (6,-1) |
| (1,2) | (2,-2) | (3,3) | (4,-3) | (5,-2) | |
| (1,-4) | (2,1) | (3,-5) | (4,-4) | ||
| (1,5) | (2,-1) | (3, 0) | |||
| (1,-6) | (2,-5) | ||||
| (1, 1) |
被飘灰的这些格子, 在同一列中, 都有长度以及总和都比它大的另一个格子. 这些飘灰的格子一定不在最终的结果中.
比如第3列中, (2,-2)的上面有(3,-1). 也就是-4往前加2个数的和为-2, 往前加3个数的和为-1. 如果最终答案包含了-4往前的2个数, 那我一定能够换成-4往前3个数, 总和比原来大了1, 且长度也比原来长
去掉飘灰的这些格子后, 我们发现, 每一列的第二个数(和)是单调递增的.
因为我要找每列第二个数(和)>=0的格子, 由于有了单调性之后, 我就能用二分查找了, 查找的复杂度为O(lgn)
3.3 总结
综合3.1和3.2, 在这个动态规划过程中, 遍历的每一步, 时间复杂度=O(1)+O(lgn), 总共遍历n次, 因此总的时间复杂度为O(nlgn)
关键字: 算法, 动态规划, 数组
[饭后算法系列] 数组中"和非负"的最长子数组的更多相关文章
- 剑指Offer 28. 数组中出现次数超过一半的数字 (数组)
题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. ...
- C基础知识(3):指针--概念、数组中指针的递增/递减、指针数组&数组指针、指向指针的指针
指针是一个变量,其值为另一个变量的地址. 所有指针的值的实际数据类型,不管是整型.浮点型.字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数. 下面从4个代码例子分别讲述以下 ...
- [饭后算法系列] "头尾移动" 排序列表
1. 问题 一个乱序列表(list), 只支持两种操作: 把一个元素移动到头部, 或者把一个元素移动到尾部. 需要设计一种算法, 使得移动次数最少而使列表有序 举两个例子: 1. {3,5,7,1,9 ...
- js实现往数组中添加非存在的对象,如果存在就改变键值。
let arr = [] // 数组中元素数据类型为{name: 'bb', age: 12} // 现在需求是,将每次获得的新对象{name: '', age: }push到数组arr中,但前提是数 ...
- 4.19——数组双指针——26. 删除有序数组中的重复项 & 27. 删除有序数组中的重复项II & 80. 删除有序数组中的重复项 II
第一次做到数组双指针的题目是80: 因为python的List是可以用以下代码来删除元素的: del List[index] 所以当时的我直接用了暴力删除第三个重复元素的做法,大概代码如下: n = ...
- 《剑指offer》第三_一题(找出数组中重复的数字,可改变数组)
// 面试题3(一):找出数组中重复的数字 // 题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内.数组中某些数字是重复的,但不知道有几个数字重复了, // 也不知道每个数字重复了几次.请 ...
- js 数组 添加或删除 元素 splice 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素 filter
里面可以用 箭头函数 splice 删除 增加 数组 中元素 操作数组 filter 创建新数组 检查指定数组中符合条件的所有元素
- js 根据条件删除数组中某个对象&js filter (find)过滤数组对象的使用
删除 ---- item不设置 arr.splice(1,1) //['a','c','d'] 删除起始下标为1,长度为1的一个值,len设置的1,如果为0,则数组不变 arr. ...
- 剑指 Offer 51. 数组中的逆序对 + 归并排序 + 树状数组
剑指 Offer 51. 数组中的逆序对 Offer_51 题目描述 方法一:暴力法(双层循环,超时) package com.walegarrett.offer; /** * @Author Wal ...
随机推荐
- Xamarin改写安卓Residemenu控件
1.下载最新的Residemenu安卓代码.用intellig打开,重新编译一下. 2.需要将其中的Residemenu,用gradle编译生成*.aar文件格式. 2.1 下载gradle,配置环境 ...
- VS2010 使用TeeChart画图控件 - 之二 - 绘制图形(折线图,柱状图)
1.前期准备 详细可见VS2010 使用TeeChart画图控件 - 之中的一个 控件和类的导入 1. 1 加入TeeChart控件,给控件加入变量m_TeeChart 加入TeeChart控件,右击 ...
- [转] 深入剖析 linux GCC 4.4 的 STL string
本文通过研究STL源码来剖析C++中标准模板块库std::string运行机理,重点研究了其中的引用计数和Copy-On-Write技术. 平台:x86_64-redhat-linux gcc ver ...
- HDU -1864最大报销额(01背包)
这道题属于简单的01背包,但是背包问题还算简单,就是前面的细节处理的时候要注意,题意大致说了三条限制吧 1. 只有a, b, c 三种类型的发票可以报销,其它的一律不报销 2. 物品单项的报销额不超过 ...
- Asp 图形化报表
1 图形化的报表的优点 分析.统计业务数据 表现直观,漂亮,有震撼效果的图形化的方式展现业务数据 复杂的业务数据简单化 2 常用的报表组件 HighCharts:是纯js编写的图形化报表 水晶报表 ...
- C++小技巧之四舍五入与保留小数
四舍五入:保留整数 int a = b+0.5; 保留一位小数 int a=(b+0.05)*10; double c=a/10; 保留二位小数 int a=(b+0.005)*100; doub ...
- 单点登录CAS使用记(四):为登录页面加上验证码
CAS默认的登录页面样式如下,只有用户名与密码两项验证项目. 现在需要为首页登录加上验证码功能. 第一步:首页对默认登录页面的样式进行了调整,使其看上去还算美观. 在页面上加上了验证码项目. 第二步: ...
- 函数递归时,递归次数到900多时,就是抛出异常exception RuntimeError('maximum recursion depth exceeded',)
import subprocess import multiprocessing import urllib import sys import os import pymongo import si ...
- 转:测试计划(出处:: 51Testing软件测试网--zfx081)
测试计划阶段主要处于测试的先期准备阶段,在该阶段中主要是对将要进行的测试工作做一个整体的规划.包括一下内容: 1.测试目的和测试项目简介. 1.1测试目的:××××系统的测试计划有助于实现一下目标 ...
- HTML5 Canvas 中的颜色、样式和阴影的属性和方法
颜色.样式和阴影的属性与方法 fillStyle 设置或返回用于填充绘画的颜色.渐变或模式 strokeStyle 设置或返回用于笔触的颜色.渐变或模式 ...