问题:
合唱队形安排问题(使用动态规划和分治算法求解)
问题描述:N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,其余人位置不变,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<…Ti+1>…>TK(1<=i<=K)。已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
解:
- 动态规划方法:
先从左到右找出当前数字的最长递增序列,并计算出长度,然后同理找出从右到左的最长递增序列,最后再将得出的两个序列按照对应位置相加然后减一得出的列表即为当其为合唱队中间人时,合唱队的长度。之所以减一,是因为相加是,加了两遍自己。
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 25 10:24:45 2022
@author: Dell
"""
# 每个人的左边出现的最多人(输出左_最长递增子序列)
def left_max(l):#l为站成一排的同学序列
ans=[1]*len(l)
for i in range(len(l)):# 每个人的游标(从前往后)
for j in range(i):# 这个人前面每个人的游标
if l[j]<l[i] and ans[j]+1>ans[i]:
ans[i]=ans[j]+1
return ans # 1 1 1 2 2 1 3 4
# 每个人的右边出现的最多人(输出右_最长递增子序列)
def right_max(l):
ans=[1]*len(l)
for i in range(len(l)-1,-1,-1):# 每个人的游标(从后往前)
for j in range(i+1,len(l)):# 这个人后面每个人的游标
if l[j]<l[i] and ans[j]+1>ans[i]:
ans[i]=ans[j]+1
return ans # 3 3 2 3 2 1 1 1
while True:
N=8
#tall_li_str=input().split()
tall_li_int=[165, 182, 178, 149, 193, 179, 174, 158]
left_li=left_max(tall_li_int)
right_li=right_max(tall_li_int)
sum_li=[] #left_li和right_li加和,可以得到每个人如果是中间那个人的话,合唱队最长是多少
for i in range(len(left_li)):
sum_li.append(left_li[i]+right_li[i])
print('最少要出列',N-max(sum_li)+1,'位同学')#题中问的是最少去几人,也就是总人数减去合唱队最多人数
# 另外加和时自己算了两遍,还得再减去一遍
break
运行结果:
- 分治法
import bisect
def max_order(lists):
list_num = [] #用于排序,
list_max = [] #每个元素右边的最大数(包括元素自己)
for i in lists:
local = bisect.bisect_left(list_num, i)
if local == len(list_num):
list_num.append(i)
list_max.append(local+1)
else:
list_num[local] = i
list_max.append(local+1)
return list_max
while True:
N = 8
tall_li_int = [165, 182, 178, 149, 193, 179, 174, 158]
left_li = max_order(tall_li_int)
right_li = max_order(tall_li_int[::-1])[::-1]
sum_s = []
for i in range(len(left_li)):
# left_s[i]+right_s[i]-1表示此人是中间位置的人时,合唱队的人数
sum_s.append(left_li[i]+right_li[i]-1)
print('最少要出列',str(N-max(sum_s)),'位同学')
break
运行结果: