小M的多任务下载器挑战
题目描述
问题描述
小M的程序设计大作业是编写一个多任务下载器。在实现过程中,他遇到了一个问题:在一次下载过程中,总共有N个任务,每个任务会在第x秒开始,并持续y秒。小M需要知道,在同一时刻,最多有多少个任务正在同时下载,也就是计算出任务的最高并发数。
n表示任务的数量。array是一个二维列表,每个元素为[x, y],表示任务的开始时间和持续时间,其中:x表示任务的开始时间;y表示任务的持续时间。
测试样例
样例1:
输入:
n = 2 ,array = [[1, 2], [2, 3]]
输出:2
样例2:
输入:
n = 4 ,array = [[1, 2], [2, 3], [3, 5], [4, 3]]
输出:3
样例3:
输入:
n = 5 ,array = [[1, 3], [3, 4], [2, 2], [6, 5], [5, 3]]
输出:3
思路
建立一个状态数组用于记录每个时间段内存在的下载任务数量。最后状态数组内下载任务数量的最大值即为整个下载过程的最大并发数。 比如:
status[0] 用于表示0-1时间段内存在的任务数量 status[1] 用于表示1-2时间段内存在的任务数量
为了得到状态数组的最终态,就需要对任务进行遍历,对每个任务的存续时间对应状态数组的值+1。
得到的最终代码如下
def solution1(n, array):
# Edit your code here
timeLine = [0 for _ in range(1000)]
for i in range(n):
for j in range(array[i][0],array[i][0]+array[i][1]):
timeLine[j] += 1
return max(timeLine)
时间复杂度O(n*m)
优化
对每个时间段的状态进行记录会需要较多的空间。可以用一个变量来记录随着时间变化,在运行的下载任务的数量。这就需要得到每个任务的起始时间和结束时间,同时要将任务开始和结束作区分。
def solution(n, array):
events = []
# 创建事件列表
for start, duration in array:
events.append((start, 1)) # 开始事件
events.append((start + duration, -1)) # 结束事件
print(events)
# 按时间顺序排序事件
events.sort()
print(events)
max_concurrent = 0
current_concurrent = 0
# 扫描事件列表
for time, event_type in events:
current_concurrent += event_type
max_concurrent = max(max_concurrent, current_concurrent)
return max_concurrent
时间复杂度O(nlogn),主要是排序开销。
小M的弹子游戏机挑战
问题描述
小M最近迷上了一款弹子游戏机,规则如下:
玩家可以在版面最上方任意一个位置放置弹珠。弹珠会通过得分点时为玩家赢得分数,目标是获得尽可能高的分数。
弹子游戏机的版面由两种组成要素构成:
- 钉子(用
-1表示),当弹珠碰到钉子时,有可能弹射到左下或者右下的位置。 - 得分点(非负整数),弹珠经过得分点时可以获得对应的分数。
如果弹珠所在的格子为空(即没有钉子或者得分点),弹珠会直接往下落。
小M想知道,在一个给定的版面布局中,他能够获得的最高分数是多少。
n表示版面的高度。m表示版面的宽度。array是一个n x m的二维数组,其中:-1表示该位置为钉子;0表示该位置为空;- 正整数表示该位置为得分点,值为该得分点的分数。
测试样例
样例1:
输入:
n = 3 ,m = 3 ,array = [[-1, 0, -1], [100, 0, 0], [0, 50, 70]]
输出:50
样例2:
输入:
n = 4 ,m = 3 ,array = [[-1, 0, -1], [0, -1, 0], [50, 100, 70], [80, 200, 50]]
输出:130
样例3:
输入:
n = 5 ,m = 5 ,array = [[0, -1, 0, -1, 0], [0, 50, -1, 50, 0], [100, 0, 0, 0, 100], [0, 100, 0, 100, 0], [50, 0, 50, 0, 50]]
输出:150
思路
从上往下走
顺着小球落下的路径来对分数进行统计。 针对于不是钉子的情况,那么就直接往下走 如果是钉子,那么就产生了两种情况,需要分别考虑往左走和往右走。需要分别获取往左走和往右走的收益,然后得到当前位置所能得到的最大收益。 往左走的收益和往右走的收益可以分别用递归来实现。
def solution2(n, m, array):
# Edit your code here
def goDone(y,x):
score = 0
scoreLeft, scoreRight = 0,0
while y < n:
if array[y][x] > 0:
score += array[y][x]
if array[y][x] >= 0:
y +=1
elif array[y][x] == -1:
# go left
if x > 0:
scoreLeft = goDone(y+1,x-1)
# go right
if x < m-1:
scoreRight = goDone(y+1,x+1)
# 下面的结果已经得到了,所以直接退出循环
break
if scoreLeft > scoreRight:
return scoreLeft + score
else:
return scoreRight + score
maxScore = 0
# 需要对最上层每一个开始位置进行一次计算
for i in range(m):
score = goDone(0,i)
maxScore = max(maxScore,score)
return maxScore
自下而上
- 初始化
dp数组,所有位置初始值为0。 - 从底部向上遍历,计算每个位置的最大分数。
- 如果当前位置是得分点,累加分数;如果是钉子,则考虑左下和右下两个方向的最大分数。
- 最终,顶部的最大分数即为所求。
- 需要注意的是,在对最后一行进行处理时略有不同
def solution(n, m, array):
dp = [[0] * m for _ in range(n)]
# 从底部向上遍历
for i in range(n-1, -1, -1):
for j in range(m):
if array[i][j] > 0:
# 如果是得分点,累加分数
if i == n-1:
dp[i][j] = array[i][j]
else:
dp[i][j] = array[i][j] + dp[i+1][j]
elif array[i][j] == -1:
# 如果是钉子,考虑左下和右下两个方向的最大分数
if i + 1 < n and j - 1 >= 0:
dp[i][j] = max(dp[i][j], dp[i+1][j-1])
if i + 1 < n and j + 1 < m:
dp[i][j] = max(dp[i][j], dp[i+1][j+1])
else:
# 如果是空,直接继承下方位置的分数
if i + 1 < n:
dp[i][j] = dp[i+1][j]
# 返回顶部的最大分数
print(max(dp[0]))
return max(dp[0])