题解 | #合唱队#

合唱队

http://www.nowcoder.com/practice/6d9d69e3898f45169a441632b325c7b4

#合唱队#

此题是最长递增子序列的变体,基本思路是对原序列从左到右和从右到左分别求出到每个元素的最长递增子序列的长度。例如,原序列为长度为N的序列[8,20,12,15,10,9],从左至右的到序列里每个元素的最长递增子序列为l1=[1,2,2,3,2,2],从右至左为l2=[1,4,3,3,2,1],l1+l2=[2,6,5,6,4,3]。那么合唱队最长队伍是L = max(l1+l2)-1,减1是因为计算l1和l2时重复计算了一次元素本身。因此最少出列人数为原序列长度N-L。

此题关键在于求出l1,l2。可由动态规划求出。用dp[i]表示从左至右到原序列第i个元素的最长递增子序列的长度,从第i个元素往回遍历更新dp[i]的值。由于每个元素都需要往回遍历一次,时间复杂度是o(n^2)。往回遍历如何更新dp[i]的值在其他题解已有很好的介绍,这里主要写用二分法代替往回遍历的过程,时间复杂度是o(nlogn)。

二分法的过程为:首先创建数组arr=[ele_1],ele_1是原序列第一个元素,然后从第二个元素开始从左至右遍历原序列

  1. 如果ele_i > max(arr),将ele_i加到arr最后
  2. 如果ele_i <= max(arr),用二分法找到arr中第一个比ele_i大(或相等)的元素并用ele_i替换

遍历完成后arr的长度即为最长递增子序列的长度(但arr不是最长递增子序列)。第二步替换是因为为遍历到的元素可能会有比ele_i大但比替换元素小的元素,比如原序列为[2,5,8,3,4,6]。

import bisect
def inc_max(l):
    dp = [1]*len(l) # 初始化dp,最小递增子序列长度为1
    arr = [l[0]] # 创建数组
    for i in range(1,len(l)): # 从原序列第二个元素开始遍历
        if l[i] > arr[-1]:
            arr.append(l[i])
            dp[i] = len(arr)
        else:
            pos = bisect.bisect_left(arr, l[i]) # 用二分法找到arr中第一个比ele_i大(或相等)的元素的位置
            arr[pos] = l[i]
            dp[i] = pos+1
    return dp 

while True:
    try:
        N = int(input())
        s = list(map(int, input().split()))
        left_s = inc_max(s) # 从左至右
        right_s = inc_max(s[::-1])[::-1] # 从右至左
        sum_s = [left_s[i]+right_s[i]-1 for i in range(len(s))] # 相加并减去重复计算
        print(str(N-max(sum_s)))
    except:
        break

全部评论
数学一生之敌
9 回复 分享
发布于 2022-06-05 12:04
那个arr数组可以这样理解,当循环到第i个元素时,使得从左到右、到l[i]为止的最长子序列长度的长度为m的l[i]最小值为arr[m-1](最小值划重点)。arr数组只为i后面的元素服务。
3 回复 分享
发布于 2022-02-24 16:51
我解释一下:arr的本质是一个缓冲器,arr的每一个元素arr_i代表着两件事: 1. 在之前的循环里我访问过这个元素。 2. 对arr_i之后的元素来说,arr_i的位置曾经/正在有一个属于LIS的元素。 也就是说缓冲两个信息:1. 该位置上一个元素;2.这个位置的有/无 举个例子,1 3 4 2 最后的arr = 1 2 4 这个arr其实等于每个位置的LIS的重叠:(位置1、2略) 1 3 4 (位置3的LIS) 1 2 (位置4的LIS) 比较容易的理解方式就是,将这些LIS从上到下重叠,遵守两个逻辑: 1. 下面若无,则保留 2. 下面若大,则替代 最后就能得到arr,可以看到arr的长度一定等于LIS的长度(若无则保留)。
3 回复 分享
发布于 2022-08-08 15:45
二分法详细解释可以参看leetcode 300最长上升子序列。
3 回复 分享
发布于 2022-08-14 17:39
对么? 5 1 5 -1 1 2
2 回复 分享
发布于 2023-09-20 17:30 广东
比如arr=[10,11,12,1,2,3,4]。 先说一个结论虽然 [10,11,12] 和[1,2,3]在3的位置往前看都是 最长的子结构,但是[1,2,3]比[10,11,12]要好,因为它最低,对于后来的元素,它增长的可能性最大。当到3的位置时,维护的子结构arr就变成了[1,2,3]。 怎么做到呢, [10,11,12] 当来到1时,因为3比最后一个元素小,那么在arr中查找第一个比1大的元素也就是10,用3替换10,arr变成了[1,11,12],那么长度不变但是变低了。
点赞 回复 分享
发布于 2022-09-01 17:52 河南
牛逼
点赞 回复 分享
发布于 2022-01-21 07:32
1 3 5 7 9 10 1 2 3 4 5 11 6 元素11的最长子升序列是 1 3 5 7 9 10 11,长度为7 按照上面代码看,6的最大子序列 1 3 5 6,长度为4,显然不对,1 2 3 4 5 6 才是最大的
点赞 回复 分享
发布于 2022-02-09 20:09
长度为N的序列[8,20,12,15,10,9],从左至右的到序列里每个元素的最长递增子序列为l1=[1,2,2,3,2,2] 这里没看懂,最长递增子序列为什么会是[1,2,2,3,2,2]?
点赞 回复 分享
发布于 2022-02-17 16:21
看完这个,自己写了一遍,结果最后忘了用N去减。调了半天。。。
点赞 回复 分享
发布于 2022-02-24 16:55
大佬
点赞 回复 分享
发布于 2022-03-14 17:02
假设s=[1,3,5,7,9,10,1,2,3,8],得到结果dp=[1, 2, 3, 4, 5, 6, 1, 2, 3, 5],可知元素8的最长增长子序列长为5,这个子序列本应该是1,3,5,7,8,但是最终的arr=[1, 2, 3, 7, 8, 10],这难道不是表明8的最长增长子序列是1, 2, 3, 7, 8?真搞不清这个arr数组到底含义是什么
点赞 回复 分享
发布于 2022-03-17 20:57
测试数据 : 4 123 122 121 122 结果为1 ,但是事实上,这个应该是不成立的才对
点赞 回复 分享
发布于 2022-07-20 11:59
如果用例输入的身高数组个数大于输入的人数N怎么办呢
点赞 回复 分享
发布于 2023-02-16 00:24 广东
def inc_max(nums): q = [] dp=[] for n in nums: idx = bisect.bisect_left(q, n) dp.append(idx+1) if idx == len(q): q.append(n) else: q[idx] = n return dp 这个是不是好理解一些 新建一个数组,然后第一个数先放进去,然后第二个数和第一个数比较,如果说大于第一个数,那么就接在他后面,如果小于第一个数,那么就替换,一般的,如果有i个数,那么每进来一个新的数,都要用二分查找法来得知要替换在哪个位置的数。
点赞 回复 分享
发布于 2023-02-21 18:14 广东
您好,单看最长递增组序列那个函数,里面arr不是对应的数组吧,只是长度和最终数组长度一致吧?因为[186, 186, 150, 200, 160, 130, 197, 200]数组的最长子序列应该是[150, 160, 197, 200],我看了一下,函数执行完毕结果是[130, 160, 197, 200]
点赞 回复 分享
发布于 2023-03-27 19:58 北京
不对 例如5 {1 5 -1 1 2}就不对
点赞 回复 分享
发布于 2023-09-20 17:34 广东
ab,那么c>a,总之对于后续需要判断的数来说,如果大于b,那肯定大于a,a和b是替换的关系,在维持递增子序列的同时,需要能让后续稍小的数找到合适的位置
点赞 回复 分享
发布于 2023-10-22 14:52 广东
"从左至右的到序列里每个元素的最长递增子序列为l1=[1,2,2,3,2,2],从右至左为l2=[1,4,3,3,2,1],l1+l2=[2,6,5,6,4,3]。那么合唱队最长队伍是L = max(l1+l2)-1" 这一步不太明白为什么最长队伍这么就可以算出来,l1和l2存的也不是每个位置最长子序列,而是以每个位置结尾的最长子序列吧,这样能保证得到最优解吗,会不会wrong answer
点赞 回复 分享
发布于 2024-06-29 07:43 湖北
这里的arr[i]是长度为i的最长子序列的,最后一个元素的最小值,所以情况2需要用ele_i替换
点赞 回复 分享
发布于 2024-09-05 16:48 江苏

相关推荐

MingoTree:看不出你你的技术栈,想找什么工作,然后课设项目别写上去了,自我评价删了,前后端你想好你要干啥,这种简历投上去秒挂的
点赞 评论 收藏
分享
程序员鼠鼠_春招版:我要12k吧我挂了,还招呢,天天被割,这点钱都不舍得出
点赞 评论 收藏
分享
评论
78
37
分享

创作者周榜

更多
正在热议
更多
# 听劝,这个简历怎么改 #
14116次浏览 183人参与
# 面试被问“你的缺点是什么?”怎么答 #
6586次浏览 100人参与
# 水滴春招 #
16676次浏览 378人参与
# 入职第四天,心情怎么样 #
11356次浏览 63人参与
# 租房找室友 #
8060次浏览 53人参与
# 读研or工作,哪个性价比更高? #
26200次浏览 356人参与
# 职场新人生存指南 #
199308次浏览 5519人参与
# 参加完秋招的机械人,还参加春招吗? #
27045次浏览 276人参与
# 文科生还参加今年的春招吗 #
4123次浏览 31人参与
# 简历无回复,你会继续海投还是优化再投? #
48639次浏览 561人参与
# 你见过最离谱的招聘要求是什么? #
144729次浏览 829人参与
# 如果重来一次你还会读研吗 #
155733次浏览 1706人参与
# 机械人选offer,最看重什么? #
69078次浏览 449人参与
# 选择和努力,哪个更重要? #
44330次浏览 494人参与
# 如果再来一次,你还会学硬件吗 #
103653次浏览 1245人参与
# 如果你有一天可以担任公司的CEO,你会做哪三件事? #
20529次浏览 414人参与
# 招聘要求与实际实习内容不符怎么办 #
46804次浏览 494人参与
# 22届毕业,是读研还是拿外包offer先苟着 #
4654次浏览 27人参与
# 你们的毕业论文什么进度了 #
901356次浏览 8961人参与
# 软开人,你觉得应届生多少薪资才算合理? #
81380次浏览 496人参与
# 国企还是互联网,你怎么选? #
109200次浏览 853人参与
牛客网
牛客企业服务