合唱队问题(动态规划+分治法详解)

计算最少出列多少位同学,使得剩下的同学排成合唱队形

说明:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,   则他们的身高满足存在i(1<=i<=K)使得T1<T2<......<Ti-1<Ti>Ti+1>......>TK。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

注意不允许改变队列元素的先后顺序

请注意处理多组输入输出!

实际就是,从队伍里直接下来人,让剩下的人刚好满足条件的情况。

8个人,[186,186,150,200,160,130,197,200]

当以每位同学为最高点时,进行判断,从他左边找比他矮的同学(可以多个,且要为递增序列)。再从右边来找比他高的同学。

例如:

先找左边的

我们假设第一位身高为 186 的同学,为最高点(Ti),他左边没有其他人,所以我们设定左边人数为0

第二位身高为 186 的同学,左边只有一个186,和他一样,所以他左边也为0

第三个150,左边也为0

第四个200,左边有186和150的同学,都比他矮,但是这两者不成递增顺序,所以只能留一个,即左边为1

第五个160,左边有150的同学,所以为1

第六个130.左边没有比他矮的了,所以为0

第七个197.左边比他矮的有186,150,160,130,而成递增顺序的为[150,160],故左边为2

第八个200.左边比他矮的有186,150,160,130,197,而成递增顺序的为[150,160,197],左边为3.

综上,从左边找的时候,我们得到的人数表为 00011023

同理,可得从右边找的,我们得到的人数表为 22121000

此时,我们得到的两个序列是啥意思呢??

其实就是当我们将每一个同学假设为最高位时,他左右两边会出现的递增序列和低贱序列。

我们如果将对应的左右两边的人数加起来,在加上i同学自身,我们就可以得到当每一位同学为最高时,所具有的人数列表。同时,用总人数减去这个列表,我们也就得到了每一位同学为最高时,所减少的人数。

1、动态规划

import numpy as np
def left_max(N,H):
    l = np.zeros(N , dtype=np.int32)
    for i in range(N):
        for j in range(i):
            #需要两个循环,使得能对i前所有数据进行判断
          if H[i] > H[j] and l[j] + 1 >l[i]:
              #这里的这个条件为
              # 1、找比当前H[i] 唉的同学。
              # 2、如果dp上有值,表明这个数前面是有比他还要小的数存在,且形成了递增序列。
              #只有两个条件都满足才有意义。
             l[i] += 1
    return l


def right_min(N,H):#找右边最大的
    r = np.zeros(N, dtype=np.int32)
    for i in range(N - 1,-1,-1):
        for j in range(N - 1, i , -1):
            if H[i] > H[j] and r[j] + 1 > r[i]:
                r[i] += 1
    return r

N = 8
H = [186,186,150,200,160,130,197,200]
sum = []
left = left_max(N,H )
right = right_min(N,H)
for i in range(N):
    sum.append(left[i]+right[i]+1)
print(N - max(sum))

 2、分治法

其逻辑其实与上述讲解 的一样,也是从左边找递增,右边找递减。但是分治法,将左右两边变成了两个相互独立的问题。只需要一个找递增的函数即可,另一边可以看错颠倒顺序后找递增序列。

加和的时候要注意一下,要一一对应!!

#分治法:
import bisect
def max_order(H):
    H_num = [] #用于排序,
    H_max = [] #每个元素右边的最大数(包括元素自己)
    for i in H:
        local = bisect.bisect_left(H_num, i)
        #bisect是python的内涵库,用于管理列表的插入和排序
        #bisect_left是插入H_num左边,默认从0开始。
        if local == len(H_num):
            H_num.append(i)
            H_max.append(local+1)
        else:
            H_num[local] = i
            H_max.append(local+1)
    return H_max

while True:
     N = 8
     H_int = [186, 186, 150, 200, 160, 130, 197, 200]
     left = max_order(H_int)
     right = max_order(H_int[::-1])[::-1]
     sum_s = []
     for i in range(len(left)):
         # left_s[i]+right_s[i]-1表示此人是中间位置的人时,合唱队的人数
         sum_s.append(left[i]+right[i]-1)
     print('最少要出列',str(N-max(sum_s)),'位同学')
     break

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值