在线视频服务的智能分配

48 阅读9分钟

在线视频服务的智能分配

引言:一种智能的网络通道分配策略

想象一下,你正在运营一个提供在线视频服务的公司。为了保证用户体验,你为不同清晰度的视频准备了专用的网络通道,因为它们对带宽的要求不同。你的系统非常智能,不仅能为用户分配通道,还能在通道释放时动态地进行优化,把“占着茅坑”的用户挪到更合适、更经济的通道上,实现资源的最大化利用。

你的任务就是实现这套智能系统的核心逻辑。

第一部分:系统的基本构成

  1. 视频类型与带宽等级 (Video Types & Bandwidth Tiers):

    • 系统中有三种视频服务,它们代表了从低到高的带宽需求:

      • 0: 标清 (SD) - 带宽最低
      • 1: 高清 (HD) - 带宽中等
      • 2: 超清 (UHD) - 带宽最高
  2. 通道池 (Channel Pools):

    • 每种视频类型都有一个专属的、数量有限的通道池。例如,可能有 8 个标清通道,1 个高清通道,1 个超清通道。
  3. 计费规则 (Billing Rule):

    • 重要: 费用始终按照用户最初申请的视频类型来计算,即使用户因为通道不足而被“升级”到了更高带宽的通道。这是一种对用户的优惠策略。

第二部分:你需要实现的功能

VideoService(int[] channels, int[] charge) - 系统初始化
  • 功能: 这是系统的启动命令。

  • 参数:

    • channels: 一个数组,按 [标清, 高清, 超清] 的顺序指定了每种类型初始可用的通道数量。
    • charge: 一个数组,按 [标清, 高清, 超清] 的顺序指定了每种类型的单位时间费用。
allocateChannel(int time, int userId, int videoType) - 分配通道
  • 功能:time 时刻,用户 userId 发起了一个类型为 videoType 的视频服务申请。

  • 智能分配规则 (向上兼容):

    1. 首先,检查用户申请的 videoType 是否有空闲通道。如果有,分配成功。
    2. 如果没有,系统会自动尝试为其分配一个更高带宽的可用通道(例如,申请高清没位置了,就看超清有没有位置)。
    3. 如果所有更高级别的通道也都满了,则分配失败。
  • 反馈 (返回值): 分配成功(无论实际占用了哪种通道)返回 true;分配失败返回 false

freeChannel(int time, int userId) - 释放通道与降级优化
  • 功能:time 时刻,用户 userId 结束了他的视频服务。

  • 这是一个两阶段的过程:

    1. 结算费用:

      • 首先,系统会找到该用户的服务记录,计算其使用时长 (time - startTime)。
      • 根据用户最初申请的视频类型,计算并返回本次服务的总费用。
      • 如果系统中找不到该用户正在使用的服务,返回 -1。
    2. 降级优化 (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): 1null
3allocateChannel(3, 107, 1)用户 107 申请高清(1)。高清有通道,分配成功。可用通道变为 [8, 0, 1]true
3allocateChannel(3, 108, 1)用户 108 申请高清(1)。高清通道已满(0)。系统自动查找更高带宽的超清(2)通道,发现可用。分配成功,用户108实际占用超清通道。可用通道变为[8, 0, 0]true
5allocateChannel(5, 110, 1)用户 110 申请高清(1)。高清(1)和超清(2)通道都已满。分配失败。false
-queryChannel(108)查询用户 108。系统记录其正在使用超清(2)通道。2
13freeChannel(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;
    }
}