校招算法笔面试 | 校招笔面试真题-小苯的比赛上分

校招算法笔面试真题解析

题目## 题目

题目链接

题目链接

小苯的比赛上分

题目描述

有一款著名的大型多人电子竞技游戏网站"喜爱福",网站通常会举办一些比赛。通常一名参赛选手只有一个账号,但不难猜到,总会有人"开小号"上分。

小苯就是一位该游戏的忠实玩家,他总共有 n n n 个账号,每个账号的分数分别为 a i a_i ai。他深谙游戏中一位著名玩家 st****lk 的一句名言:“只要你永远打分更低的号,那么你的最高分就是单调不降”。

现在我们记录了小苯 m m m 次的比赛记录,已知小苯每次都会谨记 st****lk 的名言,从而使用分数最低的账号参赛。现在我们想知道小苯每次参赛后,他的最高分是多少。

输入:

  • 第一行两个正整数 n , m n,m n,m,分别表示小苯的账号个数和新参加的比赛记录数
  • 第二行 n n n 个整数 a i a_i ai,表示小苯每个账号目前的分数
  • 第三行 m m m 个整数 b i b_i bi,分别表示小苯每次比赛后,分数的变化值

输出:

  • 输出 m m m 行,每行一个整数,表示小苯参与完第 i i i 场比赛后,他的最高分的值

解题思路

这是一个模拟问题,可以通过以下步骤解决:

  1. 关键发现:

    • 每次都使用分数最低的账号参赛
    • 需要维护所有账号的分数
    • 每次比赛后需要更新最高分
  2. 模拟策略:

    • 使用优先队列(小根堆)维护所有账号分数
    • 每次从堆顶取出最小分数的账号
    • 更新该账号分数后重新入堆
    • 维护当前最高分
  3. 具体步骤:

    • 初始化优先队列和最高分
    • 对每场比赛:
      • 取出最小分数
      • 加上比赛分数变化
      • 更新最高分
      • 将新分数入堆

代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    priority_queue<int, vector<int>, greater<int>> pq;  // 小根堆
    int max_score = 0;
    
    // 读入初始分数
    for(int i = 0; i < n; i++) {
        int score;
        cin >> score;
        pq.push(score);
        max_score = max(max_score, score);
    }
    
    // 处理每场比赛
    for(int i = 0; i < m; i++) {
        int delta;
        cin >> delta;
        
        int min_score = pq.top();  // 取出最小分数
        pq.pop();
        min_score += delta;  // 更新分数
        pq.push(min_score);
        
        max_score = max(max_score, min_score);  // 更新最高分
        cout << max_score << endl;
    }
    
    return 0;
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        
        PriorityQueue<Integer> pq = new PriorityQueue<>();  // 小根堆
        int maxScore = 0;
        
        // 读入初始分数
        for(int i = 0; i < n; i++) {
            int score = sc.nextInt();
            pq.offer(score);
            maxScore = Math.max(maxScore, score);
        }
        
        // 处理每场比赛
        for(int i = 0; i < m; i++) {
            int delta = sc.nextInt();
            
            int minScore = pq.poll();  // 取出最小分数
            minScore += delta;  // 更新分数
            pq.offer(minScore);
            
            maxScore = Math.max(maxScore, minScore);  // 更新最高分
            System.out.println(maxScore);
        }
    }
}
import heapq

n, m = map(int, input().split())
scores = list(map(int, input().split()))
deltas = list(map(int, input().split()))

heapq.heapify(scores)  # 转换为小根堆
max_score = max(scores)  # 初始最高分

# 处理每场比赛
for delta in deltas:
    min_score = heapq.heappop(scores)  # 取出最小分数
    min_score += delta  # 更新分数
    heapq.heappush(scores, min_score)
    
    max_score = max(max_score, min_score)  # 更新最高分
    print(max_score)

算法及复杂度

  • 算法:优先队列(小根堆)
  • 时间复杂度: O ( m log ⁡ n ) \mathcal{O}(m \log n) O(mlogn) - 每场比赛需要一次堆操作
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储所有账号分数

题目链接

题目链接

小苯的区间删除

题目描述

小苯有一个长度为 n n n 的数组 a a a,他想要使得数组有序(单调不降)。为此,他必须选择一段区间 [ l , r ] [l,r] [l,r],将数组的这一段删除,其他的部分(如果存在的话)就按顺序拼在一起。

现在他想知道有多少种不同的选择区间的方案。注:小苯认为,空数组也满足有序,即你可以选择 [ 1 , n ] [1,n] [1,n] 这个区间。

输入:

  • 第一行一个正整数 n n n,表示数组的长度
  • 第二行 n n n 个正整数 a i a_i ai,表示数组 a a a

输出:

  • 一个正整数表示答案

解题思路

这是一个区间问题,可以通过以下步骤优化解决:

  1. 关键发现:

    • 对于每个位置 i,我们可以预处理:
      • p[i] 表示从位置 i 向左最多有多少个连续递增的数
      • s[i] 表示从位置 i 向右最多有多少个连续递增的数
    • 如果要删除区间 [l,r],那么 l-1 和 r+1 位置的数必须能够连接
  2. 优化策略:

    • 枚举左端点 i,如果 p[i-1] < i-1,说明左边部分不是递增的,可以直接退出
    • 对于每个左端点,二分查找右端点:
      • 找到最小的位置 j,使得 s[j] = n-j+1(右边部分递增)且 a[i-1] <= a[j]
      • 从 j 到 n 的所有位置都可以作为右端点
  3. 具体步骤:

    • 预处理 p 和 s 数组
    • 枚举左端点,二分查找右端点
    • 统计合法方案数

代码

#include <bits/stdc++.h>
using namespace std;
const int inf = 2e9;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 2);
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    a[n + 1] = inf;  // 添加哨兵,简化边界处理
    
    // 预处理连续递增长度
    vector<int> p(n + 1), s(n + 2);
    for(int i = 1; i <= n; i++) {
        if(a[i] >= a[i - 1]) {
            p[i] = p[i - 1] + 1;
        } else {
            p[i] = 1;
        }
    }
    for(int i = n; i; i--) {
        if(a[i] <= a[i + 1]) {
            s[i] = s[i + 1] + 1;
        } else {
            s[i] = 1;
        }
    }
    
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        int x = a[i - 1];
        if(p[i - 1] < i - 1) break;  // 左边部分不递增,后面都不用看了
        
        // 二分查找右端点
        int l = i, r = n + 1;
        while(l < r) {
            int mid = l + r >> 1;
            if(s[mid] == (n - mid + 1) && x <= a[mid]) r = mid;
            else l = mid + 1;
        }
        ans += n - l + 1;  // l到n的所有位置都可以作为右端点
        ans += l > i;  // 如果l>i,说明可以不删除i位置
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    solve();
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
    static final int INF = (int)2e9;
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int[] a = new int[n + 2];
        for(int i = 1; i <= n; i++) {
            a[i] = Integer.parseInt(st.nextToken());
        }
        a[n + 1] = INF;
        
        // 预处理连续递增长度
        int[] p = new int[n + 1];
        int[] s = new int[n + 2];
        for(int i = 1; i <= n; i++) {
            if(a[i] >= a[i - 1]) {
                p[i] = p[i - 1] + 1;
            } else {
                p[i] = 1;
            }
        }
        for(int i = n; i > 0; i--) {
            if(a[i] <= a[i + 1]) {
                s[i] = s[i + 1] + 1;
            } else {
                s[i] = 1;
            }
        }
        
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            int x = a[i - 1];
            if(p[i - 1] < i - 1) break;
            
            // 二分查找右端点
            int l = i, r = n + 1;
            while(l < r) {
                int mid = (l + r) >> 1;
                if(s[mid] == (n - mid + 1) && x <= a[mid]) r = mid;
                else l = mid + 1;
            }
            ans += n - l + 1;
            ans += l > i ? 1 : 0;
        }
        System.out.println(ans);
    }
}
import sys
input = sys.stdin.readline

n = int(input())
INF = int(2e9)
a = [0] + list(map(int, input().split())) + [INF]

# 预处理连续递增长度
p = [0] * (n + 1)
s = [0] * (n + 2)
for i in range(1, n + 1):
    if a[i] >= a[i - 1]:
        p[i] = p[i - 1] + 1
    else:
        p[i] = 1

for i in range(n, 0, -1):
    if a[i] <= a[i + 1]:
        s[i] = s[i + 1] + 1
    else:
        s[i] = 1

ans = 0
for i in range(1, n + 1):
    x = a[i - 1]
    if p[i - 1] < i - 1:
        break
    
    # 二分查找右端点
    l, r = i, n + 1
    while l < r:
        mid = (l + r) >> 1
        if s[mid] == (n - mid + 1) and x <= a[mid]:
            r = mid
        else:
            l = mid + 1
    ans += n - l + 1
    ans += l > i

print(ans)

算法及复杂度

  • 算法:前缀预处理 + 二分查找
  • 时间复杂度: O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn) - 预处理 O(n),每个左端点二分查找 O(log n)
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储预处理数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值