1570 字
8 分钟
华为笔试

题比较简单。第一题:7min,第二题:13min,第三题:30min

第一题#

题意描述#

一行输入,输入是有 nn 个数字,每个数字按照空格隔开。注意:nn 在输入并未给出。

然后他有个这样的题意:每个数字都是一只老鼠的编号,它们发现有一个地洞,那么这些老鼠就按照输入的数字编号钻到地洞去,然后如果碰到有重复的编号,说明这只老鼠就在以其它上面的老鼠都需要出来,然后当前编号的老鼠再跳进去,到最后所有的老鼠都要出来。问:老鼠出地洞序列

比如:输入:1 2 3 1 3 3 1,则输出为 3 2 1 3 3 1 1。 解释:首先 1 2 3 老鼠入洞,然后碰到 1 ,说明按照 3 2 1 顺序出洞,然后老鼠 1 3 再入洞;碰到 3 ,按照 3 顺序出洞,然后老鼠 3 再入洞;碰到 1,按照 3 1 顺序出洞,老鼠 1 入洞。最后都要出洞,老鼠 1 再出洞。

数据范围#

  • 1≤n≤1051 \le n \le 10^5
  • 1≤Ai≤1051 \le A_i \le 10^5

题解#

栈#

时间复杂度 O(n)O(n)

其实按照题意已经知道是栈了,洞就是一个栈,满足先进后出的规则

代码如下

#include <iostream>
#include <cstring>
using namespace std;
const int N = 10010;
int n;
int q[N], stk[N], tt = 0;
bool in_stk[N];
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int x;
int flag = 0;
while (~scanf("%d", &x)) {
if (in_stk[x]) {
while (in_stk[x]) {
int y = stk[-- tt];
in_stk[y] = false;
if (flag ++ ) putchar(' ');
printf("%d", y);
}
}
stk[tt ++ ] = x;
in_stk[x] = true;
}
while (tt > 0) {
if (flag ++ ) putchar(' ');
printf("%d", stk[-- tt]);
}
}

第二题#

题意描述#

给你一个长度为 nn 的数组,然后一个大小为 intervalinterval 的时间间隔。

求:在数组里面任取一个数 xx,然后有一个集合 {x+interval∗k,k≥0}\{x+ interval*k, k \ge 0\} 的集合,那么说明数组里面有 yy 个数在这个集合里面,求最大的 yy 对应的 xx,如果两个 xx 值的 yy 相等,则求最小的 xx

数据范围#

  • 1≤n≤1051 \le n \le 10^5
  • 0≤x≤1090 \le x \le 10^9
  • 0≤interval≤1050 \le interval \le 10^5

题解#

思维#

时间复杂度 O(nlogn)O(nlogn)

第一步预处理 hh 数组,h[i]h[i] 表示数组里面的数对 intervalinterval 取模后有多少个值为 ii

第二步,有序离散化。因为对于重复值,只需要计算一次。同时使用哈希表记录下每个数出现了多少次

第三步,从小到大枚举有序离散化后的数组,那么对于当前值 xx, yy 一定是等于 h[x%interval]h[x \% interval] 的数量。同时在最后 h[x%interval]h[x \% interval] 要更新,减去原数组里面 xx 的数量。因为 h[i]h[i] 只是保证了取模后的值为 ii 的数量,但是对于一个 xx 而言,我们需要的不仅仅是取模要相等,而且要保证 ≥x\ge x

WARNING

因为这题 interval 可能为 0,因此需要特判

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
const int N = 100010;
int q[N];
int n;
int delta;
int h[N];
unordered_map<int, int> um;
vector<int> alls;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) {
scanf("%d", &q[i]);
alls.push_back(q[i]);
um[q[i]] ++ ;
}
scanf("%d", &delta);
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
int m = alls.size();
if (delta == 0) {
int res = 0, cnt = 0;
for (auto &u: alls)
if (um[u] > cnt) {
res = u, cnt = um[u];
}
printf("%d", res);
} else {
for (int i = 0; i < n; i ++ )
h[q[i] % delta] ++ ;
int res = -1, cnt = 0;
for (int i = 0; i < m; i ++ ) {
int t = alls[i] % delta;
if (h[t] > cnt) {
res = alls[i], cnt = h[t];
}
h[t] -= um[alls[i]];
}
printf("%d", res);
}
return 0;
}

第三题#

题目大意#

给你一个长度为 nn 的时间区间 [lefti,righti][left_i, right_i],表示会议从 leftileft_i 开始,rightiright_i 天结束。如果小明在其中一天参加会议,那么就算小明参加了这场会议,但是小明每天最多只能参加 kk 场会议。问:小明最多能参加多少场会议?

数据范围#

  • 1≤n≤1041\le n \le 10^4
  • 1≤k≤101 \le k \le 10
  • 0≤lefti≤righti≤1080 \le left_i \le right_i \le 10^8

题解#

贪心#

时间复杂度 O(n2logn)O(n^2logn)

贪心策略:按照 leftileft_i 从小达到,若 leftileft_i 则按照区间长度从小到大选择。如果发现 leftileft_i 这天已经被占满了,则更新区间为 [lefti+1,righti][left_i+1,right_i]。

NOTE

证明:假设贪心解为集合 S1S_1,最优解为 S2S_2

则有 ∣S1∣≤∣S2∣|S_1| \le |S_2|

另外,对于集合 S1S_1 和 S2S_2 中的每个区间,都有一个生效的天数,因此,分别对 S1S_1 和 S2S_2 按照生效天数排序,如果生效天数相等,则按照右端点从小到大排序。

经过排序后,贪心解存在有第一个和最优解不同的区间,如果 vaildi<vaildjvaild_i \lt vaild_j ,(其中 validi,validjvalid_i,valid_j 分别为贪心解和最优解的有效值,也即是区间中第几天去参加的会议。)那么现在可以将当前区间加入到 S2S_2 中。

如果 validi=validjvalid_i = valid_j,说明可以将贪心解的该区间把最优解的该区间给替换掉。

如果 validi>validjvalid_i \gt valid_j,这种情况与贪心策略相矛盾

综上,最优解能通过添加区间或者替换区间的方式变成最优解,说明 ∣S2∣≤∣S1∣|S_2| \le |S_1|

因此,∣S1∣=∣S2∣|S_1|=|S_2|

贪心证明如若有误,欢迎批评指正

这个贪心策略用优先队列维护即可。

代码如下

#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
#include <unordered_map>
using namespace std;
const int N = 10010;
struct Node {
int l, r, len;
Node() {};
Node(int a, int b, int c) {
l = a, r = b, len = c;
}
bool operator < (const Node& a) const {
if (a.l == l) return len > a.len;
return l > a.l;
}
};
priority_queue<Node> Q;
unordered_map<int, int> cnt;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int n, k; scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) {
int l, r; scanf("%d%d", &l, &r);
Q.push({l, r, r - l + 1});
}
int res = 0;
while (Q.size()) {
auto q = Q.top(); Q.pop();
// cout << q.l << ' ' << q.r << ' ' << q.len << endl;
if (cnt[q.l] >= k) {
if (q.l < q.r) Q.push({q.l + 1, q.r, q.len - 1});
continue;
}
cnt[q.l] ++ ;
res ++ ;
}
printf("%d", res);
}
TIP

如果 n=10000n=10000,k=1k=1,区间都是[1,1000000000][1,1000000000] 那么,很明显这个时间复杂度就是 O(n2logn)O(n^2logn)

写在最后,第三题有O(n2)O(n^2) 或者以下做法欢迎讨论