Python算法之旅元组的风暴之最长上升子序列

元组的风暴之最长上升子序列

        小美:还记得我们上次做的那道题目吗?求最长连续递增子序列的长度。

        阿福:记得啊,当时我们用了两种方法,分别是在a[i] <=a[i-1]和a[i] > a[i-1]时更新max_len,古老师还表扬我们了呢。

        小美:没错,当时你是出尽了风头啊。但是后来我又学会了一种新的方法,叫做动态规划,效率更高,代码也更简明。

        阿福:真的吗?还有这么好的方法?快说给我听听。

        小美:动态规划的概念很复杂,我一时半会儿也说不清楚,但我知道它是一种以空间换时间的方法,它把每个子问题的解都记录下来了,这样就能快速地求出更大规模问题的解,直到求出最优解。具体到这个题目,就是设置一个列表d,用d[i]记录元素a[i]在子序列中的位置,则最大的d[i]值就是最长子序列长度。


题目1:

求最长连续递增子序列的长度。例如,在元组(1,9,2,5,7,3,4,6,8,0)中最长连续递增子序列为(3,4,6,8),其长度为4。

函数功能:求最长连续递增子序列的长度

函数名:def sub_num(a: tuple) -> int

参数表:a -- 元组。

返回值:返回最长连续递增子序列的长度。

示例:输入a=(1,9,2,5,7,3,4,6,8,0),返回4


代码1:

def sub_num(a: tuple) -> int:

    if not a: #a是空元组

        return 0

    d = [1] *len(a) # d[i]表示元素a[i]在子序列中的位置

    for i in range(1, len(a)):

        if a[i]> a[i-1]:

            d[i] = d[i-1]+ 1

return max(d)

         阿福:确实不错啊!虽然需要多设置一个列表d,但确实提高了效率,代码也更简洁了。

        小美:那还用说。

        古老师:士别三日,当刮目相待。小美的进步很大啊!动态规划是解决最优解问题的经典算法,相比暴力枚举和回溯算法,它的效率要高得多,应用也非常广泛。我们不妨来看一道类似的问题。


题目2:

求最长上升子序列的长度。例如,在元组(1,9,2,5,7,3,4,6,8,0)中最长上升子序列为(1,2,3,4,6,8),其长度为6。

函数功能:求最长上升子序列的长度度

函数名:def lengthOfLIS (a: tuple) -> int

参数表:a -- 元组。

返回值:返回最长上升子序列的长度。

示例:输入a=(1,9,2,5,7,3,4,6,8,0),返回6


       小美:“最长连续递增子序列”和“最长上升子序列”有什么区别啊?

       阿福:我的理解是前者必须连续,后者可以不连续。

       古老师:没错,它们的区别就在于是否具有连续性,“连续子序列”又称为“子串”,要求是一个连续的序列;而“子序列”则不一定连续。

        小美:哦,那判断a[i]是否属于某个上升序列的时候,就不仅仅是跟a[i-1]比较,而要与它前面的每一个元素都比较一次,加入到最长的上升序列中。我可以使用动态规划算法,只需要在代码1的基础上修改就行了。


代码2:

def lengthOfLIS(self, a: tuple) -> int:

    if not a:

        return 0

    d = [1] *len(a) #d[i]表示以a[i]为尾元素的上升子序列长度

    for i in range(1, len(a)):

        for j in range(i-1, -1, -1):#逆序扫描a[i]之前的元素,更新d[i]值

            if a[i]> a[j] and d[j] >= d[i]:

               d[i] = d[j] + 1

    return max(d)

        阿福:小美是顺序扫描元组的,用d[i]记录元素a[i]在上升子序列中的位置,或者说d[i]表示以a[i]为尾元素的上升子序列长度。

        我也可以逆序扫描元组,求最长下降子序列长度,用d[i]记录元素a[i]在下降子序列中的位置,或者说用d[i]表示以a[i]为首元素的上升子序列长度,这样只要在a[i]后面的元素中找到大于a[i],且对应d[j]最大的元素a[j]就行了,我们同样更新d[i] = d[j] + 1。


代码3:

def lengthOfLIS(self, a: tuple) -> int:

    if not a:

        return 0

    d = [1] *len(a) #d[i]表示以a[i]为首元素的上升子序列长度

    for i in range(len(a)-2, -1, -1):

        for j in range(i+1, len(a)):#顺序扫描a[i]之后的元素,更新d[i]值

            if a[i]< a[j] and d[j] >= d[i]:

               d[i] = d[j] + 1

return max(d)

        古老师:都很不错!两种算法中,虽然d[i]的含义不同,但都能正确地实现程序功能,体现了动态规划算法的基本特征。但你们提供的两种算法时间复杂度都是O(n^2),能不能将算法的时间复杂度降低到 O(n log n)呢?

        我只给你们一个提示:使用对分查找算法。

        好了,我该走了,再见。



彩蛋:

       小美:要将算法的时间复杂度降低到 O(n log n),那就不能写成二重循环的形式了。

       阿福:是的,原来的内层循环实际上是一个顺序查找最优解的过程,现在要把顺序查找改成对分查找,这样就能实现O(n log n)的时间复杂度了。

       小美:对分查找适用于有序列表,可现在列表d并不是有序列表啊。

       阿福:没错。所以不能再使用列表d了,需要设置一个新的列表s,存储某些有序的元素。可哪些元素是有序的呢?

       小美:我知道了,上升子序列就是有序的。列表s就用来存储最长的上升子序列。

       阿福:漂亮!这就好办了。我们可以先把a[0]存储到s[0]中,之后如果a[i]> s[-1],就把a[i]添加到列表s尾部,否则用a[i]替换s中第一个不小于a[i]的元素,使得s中的每个元素都是最小的,这样可以确保获得最长序列。

       小美:什么意思啊?

       阿福:没听懂是吧?我举个例子给你看看。对于元组a=(1,5,2,6,3,8,2,7,9),我们用列表s存储最长上升子序列。我们初始化s = [a[0]],之后遍历a[1:],若a[i]> s[-1],则执行s.append(a[i]),否则用a[i]替换s中第一个不小于a[i]的元素。这样可以确保s的尾元素总是最小的,让尽可能多的元素加入列表s,从而获得最长的子序列。具体操作如下图。

       小美:这样我就能看懂了。那我们在列表s中查找第一个不小于a[i]的元素,是不是需要定义一个函数来实现对分查找功能呢?对分查找算法我学过,有好几种写法呢。

       阿福:不用自定义函数,我记得Python提供了一个标准模块bisect,可以用实现对分查找算法。其中bisect_left(d, x)函数可以返回列表d中第一个不小于x的元素下标。

       小美:还有这么好用的标准库啊,那就省事多了。代码还是我来写吧,你帮我把把关就行了。


代码4:

def lengthOfLIS(self, a: tuple) -> int:

    import bisect

    if not a:

        return 0

    s = [a[0]]#s[m]表示上升子序列中第m个元素的值

    for e in a[1:]:

        if e >s[-1]:#可将e加入的上升子序列尾部

           s.append(e)

        else: #用e替换掉s中第一个不小于e的元素,使得s中的每个元素都是最小,这样可以确保获得最长序列

           s[bisect.bisect_left(s, e)] = e

return len(s)

       阿福:真棒!这段代码通过“力扣”网站测试,在所有 python3 提交中击败了98.42%的用户呢。


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

推荐阅读更多精彩内容