在线视频服务的智能分配
引言:一种智能的网络通道分配策略
想象一下,你正在运营一个提供在线视频服务的公司。为了保证用户体验,你为不同清晰度的视频准备了专用的网络通道,因为它们对带宽的要求不同。你的系统非常智能,不仅能为用户分配通道,还能在通道释放时动态地进行优化,把“占着茅坑”的用户挪到更合适、更经济的通道上,实现资源的最大化利用。
你的任务就是实现这套智能系统的核心逻辑。
第一部分:系统的基本构成
-
视频类型与带宽等级 (Video Types & Bandwidth Tiers):
-
系统中有三种视频服务,它们代表了从低到高的带宽需求:
- 0: 标清 (SD) - 带宽最低
- 1: 高清 (HD) - 带宽中等
- 2: 超清 (UHD) - 带宽最高
-
-
通道池 (Channel Pools):
- 每种视频类型都有一个专属的、数量有限的通道池。例如,可能有 8 个标清通道,1 个高清通道,1 个超清通道。
-
计费规则 (Billing Rule):
- 重要: 费用始终按照用户最初申请的视频类型来计算,即使用户因为通道不足而被“升级”到了更高带宽的通道。这是一种对用户的优惠策略。
第二部分:你需要实现的功能
VideoService(int[] channels, int[] charge) - 系统初始化
-
功能: 这是系统的启动命令。
-
参数:
channels: 一个数组,按[标清, 高清, 超清]的顺序指定了每种类型初始可用的通道数量。charge: 一个数组,按[标清, 高清, 超清]的顺序指定了每种类型的单位时间费用。
allocateChannel(int time, int userId, int videoType) - 分配通道
-
功能: 在
time时刻,用户userId发起了一个类型为videoType的视频服务申请。 -
智能分配规则 (向上兼容):
- 首先,检查用户申请的
videoType是否有空闲通道。如果有,分配成功。 - 如果没有,系统会自动尝试为其分配一个更高带宽的可用通道(例如,申请高清没位置了,就看超清有没有位置)。
- 如果所有更高级别的通道也都满了,则分配失败。
- 首先,检查用户申请的
-
反馈 (返回值): 分配成功(无论实际占用了哪种通道)返回
true;分配失败返回false。
freeChannel(int time, int userId) - 释放通道与降级优化
-
功能: 在
time时刻,用户userId结束了他的视频服务。 -
这是一个两阶段的过程:
-
结算费用:
- 首先,系统会找到该用户的服务记录,计算其使用时长 (
time - startTime)。 - 根据用户最初申请的视频类型,计算并返回本次服务的总费用。
- 如果系统中找不到该用户正在使用的服务,返回 -1。
- 首先,系统会找到该用户的服务记录,计算其使用时长 (
-
降级优化 (Downgrade Cascade):
-
当该用户占用的通道被释放后,系统会立即检查: “有没有其他用户可以被‘降级’到这个新空出来的、更便宜的通道上?”
-
降级资格: 一个用户
X有资格被降级到这个新释放的通道C,必须满足:- 用户
X当前占用的通道带宽 高于 通道C的带宽。 - 用户
X最初申请的视频类型带宽 不高于 通道C的带宽。
- 用户
-
降级选择: 如果有多个用户都有资格降级:
- 优先选择当前占用通道带宽最高的用户。
- 如果带宽仍然相同,则选择
userId最小的用户。
-
连锁反应 (Cascade): 当一个用户
X被成功降级后,他原来占用的那个更高级的通道也被释放了。此时,系统需要再次为这个新释放的通道,重复上述的降级优化检查。这个过程会像多米诺骨牌一样持续下去,直到没有用户可以被降级为止。
-
-
queryChannel(int userId) - 查询状态
- 功能: 查询用户
userId当前实际占用的是哪种类型的通道。 - 反馈 (返回值): 返回占用的通道类型(0, 1, 或 2);如果该用户当前没有使用任何服务,返回 -1。
输入与输出格式
-
输入:
channels,charge: 初始化参数,length固定为 3。time,userId,videoType: 函数调用的参数,范围如题目所述。
-
输出:
- 根据每个函数的具体要求返回相应的值(
boolean,int,long等)。评测系统会将这些返回值按顺序收集并展示。
- 根据每个函数的具体要求返回相应的值(
样例解读
输入样例 1
["VideoService", "allocateChannel", "allocateChannel", "allocateChannel", "queryChannel", "freeChannel"]
[[[8, 1, 1], [10, 15, 30]], [3, 107, 1], [3, 108, 1], [5, 110, 1], [108], [13, 108]]
输出样例 1
[null, true, true, false, 2, 150]
解释
| 时刻 | 调用 | 系统状态变化与解释 | 返回值 |
|---|---|---|---|
| - | VideoService([8,1,1],[10,15,30]) | 系统初始化。可用通道数: 标清(0): 8, 高清(1): 1, 超清(2): 1。 | null |
| 3 | allocateChannel(3, 107, 1) | 用户 107 申请高清(1)。高清有通道,分配成功。可用通道变为 [8, 0, 1]。 | true |
| 3 | allocateChannel(3, 108, 1) | 用户 108 申请高清(1)。高清通道已满(0)。系统自动查找更高带宽的超清(2)通道,发现可用。分配成功,用户108实际占用超清通道。可用通道变为[8, 0, 0]。 | true |
| 5 | allocateChannel(5, 110, 1) | 用户 110 申请高清(1)。高清(1)和超清(2)通道都已满。分配失败。 | false |
| - | queryChannel(108) | 查询用户 108。系统记录其正在使用超清(2)通道。 | 2 |
| 13 | freeChannel(13, 108) | 用户 108 停服。 | 150 |
1. 计费: 他申请的是高清(1),费用是15。使用时长 = 13 - 3 = 10。总费用 = 15 * 10 = 150。
2. 释放通道: 他占用的超清(2)通道被释放。可用通道变为[8, 0, 1]。
3. 降级检查: 检查是否有人能降级到这个新释放的超清(2)通道。无人占用比超清更高的通道,所以无降级发生。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 模拟一个高可靠的在线视频服务系统.
* 系统的核心是管理一个分层的、有限的通道资源池,
* 并处理用户的分配、释放以及因释放而触发的级联降级逻辑。
*/
public class VideoService {
/**
* 内部类,用于封装一次成功的用户会话信息.
* 关键在于区分用户“请求的类型”和“实际占用的类型”。
*/
private static class Session {
int userId; // 用户ID
int requestType; // 用户申请的视频类型 (用于计费)
int actualType; // 系统实际分配的通道类型 (用于资源管理和降级)
int startTime; // 服务开始时间 (用于计费)
public Session(int userId, int requestType, int actualType, int startTime) {
this.userId = userId;
this.requestType = requestType;
this.actualType = actualType;
this.startTime = startTime;
}
}
// 数组下标 0, 1, 2 分别对应标清、高清、超清
private final int[] availableChannels; // 各类型通道的可用数量
private final int[] charge; // 各类型视频的单位时间费用
// 使用Map快速通过userId查找用户的会话信息
private final Map<Integer, Session> activeSessions;
/**
* 初始化系统.
* @param channels 各类型视频的初始可用通道数
* @param charge 各类型视频的单位时间费用
*/
public VideoService(int[] channels, int[] charge) {
this.availableChannels = channels.clone(); // 使用clone避免外部修改
this.charge = charge.clone();
this.activeSessions = new HashMap<>();
}
/**
* 用户申请使用视频服务.
* 采用“向上兼容”策略:如果申请的类型通道不足,则尝试更高带宽的通道。
* @param time 申请时刻
* @param userId 用户ID
* @param videoType 申请的视频类型
* @return 如果分配成功返回true, 否则返回false
*/
public boolean allocateChannel(int time, int userId, int videoType) {
// 遍历从申请类型到最高类型的所有通道
for (int type = videoType; type < 3; type++) {
if (availableChannels[type] > 0) {
// 找到了可用通道
availableChannels[type]--; // 占用该通道
Session newSession = new Session(userId, videoType, type, time);
activeSessions.put(userId, newSession);
return true;
}
}
// 所有更高级别的通道都不可用,分配失败
return false;
}
/**
* 用户停止视频服务.
* 这是最复杂的操作,释放通道后会触发一个级联的降级优化过程。
* @param time 停止时刻
* @param userId 用户ID
* @return 如果用户存在并成功停服,返回此次服务费用;否则返回-1
*/
public long freeChannel(int time, int userId) {
if (!activeSessions.containsKey(userId)) {
return -1; // 用户不存在或未在服务中
}
// 1. 获取会话信息并从活跃会话中移除
Session session = activeSessions.remove(userId);
// 2. 计算费用 (根据请求的类型计费)
long duration = time - session.startTime;
long cost = duration * charge[session.requestType];
// 3. 释放占用的通道
int freedChannelType = session.actualType;
availableChannels[freedChannelType]++;
// 4. 触发级联降级优化
// 只要有通道被释放(无论是最初的释放还是降级导致的释放),就尝试进行一次降级
// 直到某次降级尝试没有成功(即找不到合适的降级用户),循环终止
while (freedChannelType != -1) {
freedChannelType = findAndPerformDowngrade(freedChannelType);
}
return cost;
}
/**
* 辅助方法:寻找最优用户并执行降级,这是级联降级的核心步骤.
* @param newlyFreedChannelType 刚刚被释放出来的通道类型
* @return 如果成功降级,返回被降级用户“腾出”的更高带宽通道类型;如果没有发生降级,返回-1
*/
private int findAndPerformDowngrade(int newlyFreedChannelType) {
Session bestCandidate = null;
// 遍历所有活跃用户,寻找最佳降级候选人
for (Session currentSession : activeSessions.values()) {
// 筛选条件:
// 1. 当前占用的通道 (actualType) 必须比空闲出来的通道更好
// 2. 申请的通道 (requestType) 必须不高于空闲出来的通道 (即有资格降级)
if (currentSession.actualType > newlyFreedChannelType && currentSession.requestType <= newlyFreedChannelType) {
if (bestCandidate == null) {
bestCandidate = currentSession;
} else {
// 优先选择占用带宽最高的用户
if (currentSession.actualType > bestCandidate.actualType) {
bestCandidate = currentSession;
}
// 带宽相同,选择userId最小的
else if (currentSession.actualType == bestCandidate.actualType && currentSession.userId < bestCandidate.userId) {
bestCandidate = currentSession;
}
}
}
}
// 如果找到了合适的降级候选人
if (bestCandidate != null) {
// 记录下降级用户即将腾出的、更高带宽的通道类型
int vacatedChannelType = bestCandidate.actualType;
// 执行降级:更新该用户的实际占用类型
bestCandidate.actualType = newlyFreedChannelType;
// 更新资源池:腾出的通道数+1,被占用的通道数-1
availableChannels[vacatedChannelType]++;
availableChannels[newlyFreedChannelType]--;
// 返回被腾出的通道类型,以供上一层循环继续进行下一轮的级联降级
return vacatedChannelType;
}
// 找不到合适的降级用户,级联过程终止
return -1;
}
/**
* 查询用户正在使用的通道类型.
* @param userId 用户ID
* @return 返回实际占用的通道类型;若用户不存在或已停服,返回-1
*/
public int queryChannel(int userId) {
if (activeSessions.containsKey(userId)) {
return activeSessions.get(userId).actualType;
}
return -1;
}
}