一、题目

题目描述:

你是一个运维工程师,你同时负责n个系统的运维工作,已知每个系统每天会都从现场采集大量的现网运行日志(错误日志、接口日志等)下来生成一个日志文件,每个系统采集下来的日志文件大小均不相同。为了解析这些日志,你给每个系统配备了一台默认服务器进行日志解析,且此台服务器只能给本系统使用,由于所配置的服务器规则均相同,因为解析日志的速度也是相同的,即每秒钟可以解析defaultCnt条日志。

现在你发现解析的速度达不到预期,但你手头上还有一部分额外的资源可以使用,这些资源可以在任意时刻配置给任意一台服务器。但有个限制,那就是同一时刻只能配给其中一台服务器器,且服务器器是能整合全部额外资源,当然在下一秒钟即可配备给另外一台服务器。某一台服务器配备了额外资源以后,则每秒钟会增加解析extraCnt条日志,即每秒可解析(defaultCnt+extraCnt)条日志。

输入描述
输入一共2行

第一行为3个正整数n、defaultCnt、extraCnt,

第二行为n个正整数,a1,a2,…,an,分别表示每个系统采集的日志条目数

已知:1 ≤ n ≤ 1×10⁵,1 ≤ defaultCnt, extraCnt ≤ 100,1 ≤ ai ≤ 1000

输出描述
一个正整数,表示解析完成全部日志的最少时间。
示例1

输入:

3 2 1
1 2 3

输出:

1

说明:

每个服务器每秒可解析2条日志,直接将额外的资源配备给第三台服务器器,则第三台服务器每秒可解析(2+1=3)条日志,则只需1秒即可解析完三个系统的全部日志。

示例2

输入:

1 1 1
4

输出:

2

这是一道经典的“最小化最大值”(Minimize the Maximum)问题。这类问题通常具有单调性:如果时间 TT 能完成任务,那么任何大于 TT 的时间也一定能完成;反之,如果 TT 不能完成,小于 TT 的时间也一定完不成。因此,最优解法是二分答案(Binary Search on Answer)


二、解题思路

1. 核心逻辑转化

题目问的是“最少需要多少秒”。我们可以反过来思考:“给定一个时间 TT ,我们能否在 TT 秒内解析完所有日志?”

  • 默认能力:每台服务器在 TT 秒内,默认能解析 T×defaultCntT×defaultCnt 条日志。
  • 缺口计算:对于第 ii 个系统,如果日志量 ai>T×defaultCntai​>T×defaultCnt ,说明默认速度不够,存在缺口:

缺口i=ai−(T×defaultCnt)缺口i​=ai​−(T×defaultCnt)

  • 额外资源需求:为了填补这个缺口,我们需要使用额外资源。额外资源每秒能多解析 extraCntextraCnt 条。
    需要的额外秒数(即需要占用额外资源的时长)为:

需额外秒数i=⌈缺口iextraCnt⌉需额外秒数i​=⌈extraCnt缺口i​​⌉

(注意:这里向上取整,因为不足一秒也需要占用一整秒的额外资源额度)

  • 全局约束:题目规定“同一时刻只能配给其中一台服务器”。这意味着,所有系统需要的“额外秒数”之和,不能超过总时间 TT 。

∑需额外秒数i≤T∑需额外秒数i​≤T

如果满足该不等式,说明时间 TT 是可行的。

2. 二分查找设计

  • 左边界 ( LL ):最小可能是 1 秒(或者 min⁡(ai)/(defaultCnt+extraCnt)min(ai​)/(defaultCnt+extraCnt) ,但直接从 1 开始最安全)。
  • 右边界 ( RR ):最坏情况是不使用任何额外资源,只靠默认速度处理最大的那个日志文件。

R=⌈max⁡(ai)defaultCnt⌉R=⌈defaultCntmax(ai​)​⌉

由于 ai≤1000,defaultCnt≥1ai​≤1000,defaultCnt≥1 ,最大时间不超过 1000,范围很小。

  • 判断函数 check(time)
    遍历所有 aiai​ ,累加需要的额外秒数。如果总和 ≤time≤time ,返回 true,否则 false

3. 算法流程

  1. 读取输入 n,defaultCnt,extraCntn,defaultCnt,extraCnt 和数组 aa 。
  2. 确定二分范围 [L,R][L,R] 。
  3. 当 L≤RL≤R :
    • 取中点 mid=L+(R−L)/2mid=L+(R−L)/2 。
    • 调用 check(mid)
    • 若 true:记录答案 ans=midans=mid ,尝试更短时间 R=mid−1R=mid−1 。
    • 若 false:时间不够,需延长时间 L=mid+1L=mid+1 。
  4. 输出 ansans 。

三、Code实现 

1、Java ✅

import java.util.Scanner;

public class LogParserSolution {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        if (!sc.hasNext()) return;

        // 1. 读取基础参数
        int n = sc.nextInt();
        long defaultCnt = sc.nextLong();
        long extraCnt = sc.nextLong();

        // 2. 读取日志数量数组
        long[] logs = new long[n];
        long maxLog = 0;
        for (int i = 0; i < n; i++) {
            logs[i] = sc.nextLong();
            if (logs[i] > maxLog) {
                maxLog = logs[i];
            }
        }

        // 3. 确定二分边界
        // 左边界:最少1秒
        long left = 1;
        // 右边界:最坏情况,不用额外资源,只处理最大的那个文件
        // 向上取整公式:(a + b - 1) / b
        long right = (maxLog + defaultCnt - 1) / defaultCnt;
        
        long ans = right;

        // 4. 二分查找
        while (left <= right) {
            long mid = left + (right - left) / 2;
            if (canFinish(mid, logs, defaultCnt, extraCnt)) {
                ans = mid;      // 记录可行解
                right = mid - 1; // 尝试更小的时间
            } else {
                left = mid + 1; // 时间不够,增大时间
            }
        }

        System.out.println(ans);
    }

    /**
     * 检查在 targetTime 秒内是否能完成所有任务
     */
    private static boolean canFinish(long targetTime, long[] logs, long defaultCnt, long extraCnt) {
        long totalExtraSecondsNeeded = 0;

        for (long logCount : logs) {
            // 默认情况下,targetTime 秒能处理的日志数
            long defaultCapacity = targetTime * defaultCnt;

            if (logCount > defaultCapacity) {
                long remaining = logCount - defaultCapacity;
                // 计算需要多少秒的额外资源来填补剩余部分 (向上取整)
                long secondsNeeded = (remaining + extraCnt - 1) / extraCnt;
                totalExtraSecondsNeeded += secondsNeeded;
            }

            // 剪枝优化:如果累计需要的额外时间已经超过总时间,直接返回 false
            if (totalExtraSecondsNeeded > targetTime) {
                return false;
            }
        }

        return totalExtraSecondsNeeded <= targetTime;
    }
}

2、Go ✅

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	// 使用 bufio 提高读取效率
	scanner := bufio.NewScanner(os.Stdin)
	
	// 读取第一行:n, defaultCnt, extraCnt
	if !scanner.Scan() {
		return
	}
	var n int
	var defaultCnt, extraCnt int64
	// 格式化扫描一行中的三个整数
	fmt.Sscanf(scanner.Text(), "%d %d %d", &n, &defaultCnt, &extraCnt)

	// 读取第二行:日志数组
	// 注意:如果数据量很大,可能需要循环 scanner,但题目描述是一行
	// 为了稳健,我们直接使用 fmt.Fscan 从 stdin 逐个读取后续数字
	logs := make([]int64, n)
	maxLog := int64(0)
	
	for i := 0; i < n; i++ {
		if !scanner.Scan() {
			// 如果换行了,继续扫描直到读完 n 个数
			// 实际上 fmt.Fscan 会自动跳过空白符(包括换行),所以直接用 Fscan 更方便
			break 
		}
		// 上面的 Scan 只是预读,实际解析用 Fscan 更简单,这里重构一下读取逻辑
	}
	
	// 重新整理读取逻辑,适配多行或单行输入
	reader := bufio.NewReader(os.Stdin)
	// 重置变量以便重新读取(如果上面没读成功)
	// 在实际机考环境中,通常输入流是连续的,直接用 Fscan 即可
	fmt.Fscan(reader, &n, &defaultCnt, &extraCnt)
	
	logs = make([]int64, n)
	maxLog = 0
	for i := 0; i < n; i++ {
		fmt.Fscan(reader, &logs[i])
		if logs[i] > maxLog {
			maxLog = logs[i]
		}
	}

	// 二分查找边界
	left := int64(1)
	// 右边界:(maxLog + defaultCnt - 1) / defaultCnt
	right := (maxLog + defaultCnt - 1) / defaultCnt
	ans := right

	for left <= right {
		mid := left + (right-left)/2
		if canFinish(mid, logs, defaultCnt, extraCnt) {
			ans = mid
			right = mid - 1 // 尝试更小
		} else {
			left = mid + 1 // 需要更多时间
		}
	}

	fmt.Println(ans)
}

// 检查函数
func canFinish(targetTime int64, logs []int64, defaultCnt, extraCnt int64) bool {
	totalExtraSecondsNeeded := int64(0)

	for _, logCount := range logs {
		defaultCapacity := targetTime * defaultCnt
		if logCount > defaultCapacity {
			remaining := logCount - defaultCapacity
			// 向上取整:(a + b - 1) / b
			secondsNeeded := (remaining + extraCnt - 1) / extraCnt
			totalExtraSecondsNeeded += secondsNeeded
		}

		// 剪枝
		if totalExtraSecondsNeeded > targetTime {
			return false
		}
	}

	return totalExtraSecondsNeeded <= targetTime
}

 四、复杂度分析

指标 分析
时间复杂度 O(N⋅log⁡(max⁡(A)))O(N⋅log(max(A)))
其中 NN 是系统数量, max⁡(A)max(A) 是最大日志量。
二分次数为 log⁡(max⁡(A)/defaultCnt)log(max(A)/defaultCnt) ,每次检查遍历 NN 个元素。
对于 N=105,A=1000N=105,A=1000 ,运算量极小,完全满足要求。
空间复杂度 O(N)O(N)
用于存储日志数组。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐