企业路由器的统计页面需要动态统计公司访问最多的网页URL的Top N。设计一个算法,能够高效动态统计Top N的页面。
每一行都是一个URL或一个数字:
每行输入对应一行输出,输出按访问次数排序的前N个URL,用逗号分隔。
news.qq.com
news.sina.com.cn
news.qq.com
news.qq.com
game.163.com
game.163.com
www.huawei.com
www.cctv.com
3
www.huawei.com
www.cctv.com
www.huawei.com
www.cctv.com
www.huawei.com
www.cctv.com
www.huawei.com
www.cctv.com
www.huawei.com
3
news.qq.com,game.163.com,news.sina.com.cn
www.huawei.com,www.cctv.com,news.qq.com
news.qq.com
www.cctv.com
1
www.huawei.com
www.huawei.com
2
3
news.qq.com
www.huawei.com,news.qq.com
www.huawei.com,news.qq.com,www.cctv.com
动态统计:
统计范围:
排序规则:
性能优化:
数据结构选择:
unordered_map
)来存储每个URL的访问次数,确保更新和查询的时间复杂度为O(1)。动态更新:
排序规则实现:
输出格式:
统计范围:
性能优化:
边界条件:
本题的核心在于动态统计URL的访问次数,并在每次输入数字N时快速输出Top N的URL。通过合理选择数据结构和排序算法,可以高效地实现这一功能。
这段代码实现了一个简单的URL统计和排序功能。它通过读取用户输入,统计每个URL出现的次数,并根据用户指定的数量n
,返回出现次数最多的前n
个URL。以下是代码的详细注释和讲解:
// 引入readline模块,用于从标准输入读取数据
const rl = require("readline").createInterface({ input: process.stdin });
// 获取异步迭代器,用于逐行读取输入
var iter = rl[Symbol.asyncIterator]();
// 定义一个异步函数readline,用于读取下一行输入
const readline = async () => (await iter.next()).value;
// 使用立即执行函数表达式(IIFE)来执行异步代码
void (async function () {
// 定义一个缓存对象,用于存储URL及其出现的次数
const cache = {};
// 进入一个无限循环,持续读取用户输入
while (true) {
// 等待读取下一行输入
const line = await readline();
// 判断输入是否为纯数字(即用户要求输出前n个URL)
if (/^\d+$/.test(line)) {
// 如果是数字,调用sortURL函数并输出结果
console.log(sortURL(parseInt(line)));
} else {
// 如果不是数字,说明是URL,更新缓存中的统计次数
cache[line] ? cache[line]++ : (cache[line] = 1);
}
}
// 定义一个函数sortURL,用于根据URL的出现次数进行排序并返回前n个URL
function sortURL(n) {
// 将缓存对象转换为数组,每个元素是一个[key, value]的数组
return Object.entries(cache)
// 对数组进行排序
.sort((a, b) => {
// 首先按出现次数降序排序
const res = b[1] - a[1];
// 如果出现次数相同,则按URL的字典序升序排序
return res == 0 ? (a[0] > b[0] ? 1 : -1) : res;
})
// 截取前n个元素
.slice(0, n)
// 将每个元素的key(即URL)提取出来
.map((entry) => entry[0])
// 将URL数组用逗号连接成一个字符串
.join(",");
}
})();
引入readline
模块:
readline
模块用于从标准输入(如终端)逐行读取数据。这里通过createInterface
方法创建了一个接口,并将其输入流设置为process.stdin
。异步迭代器:
rl[Symbol.asyncIterator]()
返回一个异步迭代器,允许我们使用await
逐行读取输入。readline
函数:
立即执行函数表达式 (IIFE):
void (async function () { ... })();
是一个立即执行的异步函数表达式,用于在代码加载时立即执行异步操作。缓存对象 cache
:
cache
是一个普通的JavaScript对象,用于存储URL及其出现的次数。键是URL,值是该URL出现的次数。主循环:
/^\d+$/
判断),则调用sortURL
函数并输出结果。cache
中该URL的计数。sortURL
函数:
n
,表示要返回的前n
个URL。Object.entries(cache)
将cache
对象转换为一个数组,数组的每个元素是一个[key, value]
的数组,其中key
是URL,value
是出现次数。sort
方法对数组进行排序:
b[1] - a[1]
)。a[0] > b[0] ? 1 : -1
)。slice(0, n)
截取排序后的前n
个元素。map((entry) => entry[0])
提取每个元素的URL。join(",")
将URL数组用逗号连接成一个字符串并返回。假设输入如下:
https://example.com
https://example.com
https://example.org
https://example.net
https://example.net
https://example.net
2
程序会输出:
https://example.net,https://example.com
解释:
https://example.net
出现了3次,https://example.com
出现了2次,https://example.org
出现了1次。2
,表示要输出出现次数最多的前2个URL,因此输出https://example.net,https://example.com
。这段代码通过简单的缓存和排序机制,实现了对URL出现次数的统计和排序功能。它适用于处理小规模的输入数据,但对于大规模数据,可能需要更高效的算法和数据结构。
这段Java代码实现了一个与之前JavaScript代码类似的功能:统计URL出现的次数,并根据用户输入的正整数n
,返回出现次数最多的前n
个URL。以下是代码的详细注释和讲解:
import java.util.HashMap;
import java.util.Objects;
import java.util.Scanner;
import java.util.StringJoiner;
import java.util.regex.Pattern;
public class Main {
// 正则表达式,用于判断一个字符串是否为正整数
static Pattern reg = Pattern.compile("^\\d+$");
// 使用HashMap缓存每个URL及其出现次数
static HashMap<String, Integer> cache = new HashMap<>();
public static void main(String[] args) {
// 创建Scanner对象,用于从标准输入读取数据
Scanner sc = new Scanner(System.in);
// 循环读取每一行输入
while (sc.hasNextLine()) {
String tmp = sc.nextLine();
// 判断输入是否为正整数
if (reg.matcher(tmp).find()) {
// 如果是正整数,调用sortURL方法并输出结果
System.out.println(sortURL(Integer.parseInt(tmp)));
} else {
// 如果是URL,更新缓存中的统计次数
cache.put(tmp, cache.getOrDefault(tmp, 0) + 1);
}
}
}
// 根据URL出现次数排序并返回前n个URL
public static String sortURL(int n) {
// 使用StringJoiner将结果用逗号连接
StringJoiner sj = new StringJoiner(",");
// 对缓存中的URL进行排序
cache.entrySet().stream()
.sorted(
(a, b) ->
// 首先按出现次数降序排序
Objects.equals(a.getValue(), b.getValue())
// 如果出现次数相同,则按URL的字典序升序排序
? a.getKey().compareTo(b.getKey())
: b.getValue() - a.getValue())
// 限制只取前n个URL
.limit(n)
// 提取URL
.map(ele -> ele.getKey())
// 将URL添加到StringJoiner中
.forEach(url -> sj.add(url));
// 返回最终的字符串结果
return sj.toString();
}
}
Pattern reg
Pattern.compile("^\\d+$")
用于匹配一个字符串是否为正整数。^
表示字符串的开始,\d+
表示一个或多个数字,$
表示字符串的结束。reg.matcher(tmp).find()
用于判断输入字符串 tmp
是否为正整数。HashMap cache
cache
是一个 HashMap
,用于存储URL及其出现的次数。key
)是URL(String
类型),值(value
)是该URL出现的次数(Integer
类型)。main
Scanner
从标准输入逐行读取数据。sortURL
方法并输出结果。cache
中该URL的计数:
cache.getOrDefault(tmp, 0)
:如果 tmp
已经存在于 cache
中,则返回其值;否则返回默认值 0
。cache.put(tmp, cache.getOrDefault(tmp, 0) + 1)
:将URL的计数加1。sortURL
方法n
,表示要返回的前 n
个URL。StringJoiner
将结果用逗号连接。cache
中的URL进行排序:
cache.entrySet().stream()
:将 cache
转换为流(Stream
),便于后续操作。sorted(...)
:对流中的元素进行排序:
b.getValue() - a.getValue()
)。a.getKey().compareTo(b.getKey())
)。limit(n)
:限制只取前 n
个URL。map(ele -> ele.getKey())
:提取每个元素的URL。forEach(url -> sj.add(url))
:将URL添加到 StringJoiner
中。StringJoiner
的结果。假设输入如下:
https://example.com
https://example.com
https://example.org
https://example.net
https://example.net
https://example.net
2
程序会输出:
https://example.net,https://example.com
解释:
https://example.net
出现了3次,https://example.com
出现了2次,https://example.org
出现了1次。2
,表示要输出出现次数最多的前2个URL,因此输出 https://example.net,https://example.com
。这段Java代码通过 HashMap
统计URL的出现次数,并通过流(Stream
)对结果进行排序和限制,最终输出前 n
个URL。代码结构清晰,功能明确,适合处理小规模数据。对于大规模数据,可能需要进一步优化性能。
这段Python代码实现了一个与之前JavaScript和Java代码类似的功能:统计URL出现的次数,并根据用户输入的正整数n
,返回出现次数最多的前n
个URL。以下是代码的详细注释和讲解:
# 缓存字典,记录每个URL出现的次数
cache = {}
# 题解列表(未使用,可以删除)
ans = []
# 定义sortURL函数,用于根据URL出现次数排序并返回前n个URL
def sortURL(n):
# 将cache字典转换为列表,每个元素是一个(key, value)的元组
urlCount = list(cache.items())
# 对列表进行排序
# key=lambda x: (-x[1], x[0]) 表示:
# 1. 首先按出现次数降序排序(-x[1])
# 2. 如果出现次数相同,则按URL的字典序升序排序(x[0])
urlCount.sort(key=lambda x: (-x[1], x[0]))
# 使用map提取前n个URL,并用逗号连接成一个字符串
return ",".join(map(lambda x: x[0], urlCount[:n]))
# 主循环,持续读取用户输入
while True:
# 读取一行输入
tmp = input()
# 判断输入是否为正整数
if tmp.isnumeric():
# 如果是正整数,调用sortURL函数并输出结果
print(sortURL(int(tmp)))
else:
# 如果是URL,更新缓存中的统计次数
if cache.get(tmp) is None:
# 如果URL不在缓存中,初始化次数为1
cache[tmp] = 1
else:
# 如果URL已经在缓存中,次数加1
cache[tmp] += 1
cache
cache
是一个字典,用于存储URL及其出现的次数。key
)是URL(str
类型),值(value
)是该URL出现的次数(int
类型)。sortURL
函数n
,表示要返回的前 n
个URL。cache.items()
将 cache
字典转换为一个列表,列表的每个元素是一个 (key, value)
的元组。urlCount.sort(key=lambda x: (-x[1], x[0]))
对列表进行排序:
-x[1]
)。x[0]
)。map(lambda x: x[0], urlCount[:n])
提取前 n
个URL。",".join(...)
将URL列表用逗号连接成一个字符串并返回。while True
进入一个无限循环,持续读取用户输入。tmp = input()
读取一行输入。sortURL
函数并输出结果。cache
中该URL的计数:
cache
中,初始化次数为1。cache
中,次数加1。假设输入如下:
https://example.com
https://example.com
https://example.org
https://example.net
https://example.net
https://example.net
2
程序会输出:
https://example.net,https://example.com
解释:
https://example.net
出现了3次,https://example.com
出现了2次,https://example.org
出现了1次。2
,表示要输出出现次数最多的前2个URL,因此输出 https://example.net,https://example.com
。输入结束条件:
exit
)来结束程序。异常处理:
性能优化:
collections.Counter
)来统计URL出现次数。代码简化:
cache.get(tmp) is None
可以简化为 tmp not in cache
。map(lambda x: x[0], urlCount[:n])
可以简化为列表推导式 [x[0] for x in urlCount[:n]]
。from collections import Counter
# 缓存字典,记录每个URL出现的次数
cache = Counter()
# 定义sortURL函数,用于根据URL出现次数排序并返回前n个URL
def sortURL(n):
# 对cache字典进行排序
sorted_urls = sorted(cache.keys(), key=lambda x: (-cache[x], x))
# 返回前n个URL,用逗号连接
return ",".join(sorted_urls[:n])
# 主循环,持续读取用户输入
while True:
# 读取一行输入
tmp = input()
# 判断输入是否为正整数
if tmp.isnumeric():
# 如果是正整数,调用sortURL函数并输出结果
print(sortURL(int(tmp)))
else:
# 如果是URL,更新缓存中的统计次数
cache[tmp] += 1
使用 collections.Counter
:
Counter
是一个专门用于计数的字典子类,可以简化代码逻辑。简化排序逻辑:
cache.keys()
进行排序,避免转换为列表。代码更简洁:
Counter
后,代码逻辑更加清晰和简洁。这段Python代码通过字典统计URL的出现次数,并通过排序和限制返回前 n
个URL。代码逻辑清晰,适合处理小规模数据。通过优化,代码可以更加简洁和高效。
#include
#include
#include
#include
#include
using namespace std;
// 缓存map,记录每个URL出现的次数
map<string, int> cache;
// sortURL函数,用于根据URL出现次数排序并返回前n个URL
string sortURL(int n) {
// 将map转换为vector,便于排序
vector<pair<string, int>> urlCount(cache.begin(), cache.end());
// 对vector进行排序
// 使用lambda表达式定义排序规则:
// 1. 首先按出现次数降序排序
// 2. 如果出现次数相同,则按URL的字典序升序排序
sort(urlCount.begin(), urlCount.end(), [](const pair<string, int>& a, const pair<string, int>& b) {
if (a.second != b.second) {
return a.second > b.second; // 按次数降序
} else {
return a.first < b.first; // 按URL升序
}
});
// 提取前n个URL
stringstream result;
for (int i = 0; i < n && i < urlCount.size(); ++i) {
if (i > 0) {
result << ",";
}
result << urlCount[i].first;
}
return result.str();
}
int main() {
string tmp;
// 主循环,持续读取用户输入
while (getline(cin, tmp)) {
// 判断输入是否为正整数
bool isNumber = true;
for (char ch : tmp) {
if (!isdigit(ch)) {
isNumber = false;
break;
}
}
if (isNumber) {
// 如果是正整数,调用sortURL函数并输出结果
int n = stoi(tmp);
cout << sortURL(n) << endl;
} else {
// 如果是URL,更新缓存中的统计次数
cache[tmp]++;
}
}
return 0;
}
map
:
map
存储URL及其出现次数,键为URL(string
类型),值为出现次数(int
类型)。sortURL
函数:
map
转换为 vector>
,便于排序。sort
函数对 vector
进行排序,排序规则为:
stringstream
将前 n
个URL用逗号连接成一个字符串并返回。主循环:
getline(cin, tmp)
逐行读取输入。sortURL
函数并输出结果。cache
中该URL的计数。输入结束条件:
exit
)来结束程序。#include
#include
#include
#define MAX_URL_LENGTH 1000
#define MAX_URLS 10000
// 定义URL结构体
typedef struct {
char url[MAX_URL_LENGTH];
int count;
} URLCount;
// 缓存数组,记录每个URL及其出现次数
URLCount cache[MAX_URLS];
int cacheSize = 0;
// 比较函数,用于排序
int compare(const void* a, const void* b) {
URLCount* urlA = (URLCount*)a;
URLCount* urlB = (URLCount*)b;
// 首先按出现次数降序排序
if (urlA->count != urlB->count) {
return urlB->count - urlA->count;
}
// 如果出现次数相同,则按URL的字典序升序排序
return strcmp(urlA->url, urlB->url);
}
// sortURL函数,用于根据URL出现次数排序并返回前n个URL
void sortURL(int n) {
// 对缓存数组进行排序
qsort(cache, cacheSize, sizeof(URLCount), compare);
// 输出前n个URL
for (int i = 0; i < n && i < cacheSize; ++i) {
if (i > 0) {
printf(",");
}
printf("%s", cache[i].url);
}
printf("\n");
}
// 判断字符串是否为正整数
int isNumeric(const char* str) {
for (int i = 0; str[i] != '\0'; ++i) {
if (str[i] < '0' || str[i] > '9') {
return 0;
}
}
return 1;
}
int main() {
char tmp[MAX_URL_LENGTH];
// 主循环,持续读取用户输入
while (fgets(tmp, sizeof(tmp), stdin)) {
// 去掉换行符
tmp[strcspn(tmp, "\n")] = '\0';
// 判断输入是否为正整数
if (isNumeric(tmp)) {
// 如果是正整数,调用sortURL函数并输出结果
int n = atoi(tmp);
sortURL(n);
} else {
// 如果是URL,更新缓存中的统计次数
int found = 0;
for (int i = 0; i < cacheSize; ++i) {
if (strcmp(cache[i].url, tmp) == 0) {
cache[i].count++;
found = 1;
break;
}
}
if (!found) {
// 如果URL不在缓存中,添加到缓存
strcpy(cache[cacheSize].url, tmp);
cache[cacheSize].count = 1;
cacheSize++;
}
}
}
return 0;
}
URLCount
结构体:
url
:URL字符串。count
:URL出现次数。cache
数组:
cacheSize
记录当前缓存的大小。compare
函数:
qsort
排序的比较函数,规则为:
sortURL
函数:
qsort
对 cache
数组进行排序。n
个URL,用逗号分隔。isNumeric
函数:
主循环:
fgets
逐行读取输入。sortURL
函数并输出结果。cache
中该URL的计数。map
和 vector
,代码简洁高效。n
个URL。华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。
机试是进入技术面的第一关:
华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。
技术面试需要手撕代码:
技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。
入职后的可信考试:
入职华为后,还需要通过“可信考试”。可信考试分为三个等级:
2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:
关注历年真题:
适应新题目:
掌握常见算法:
华为OD考试通常涉及以下算法和数据结构:
保持编程规范:
官方参考:
加入刷题社区:
寻找系统性的教程:
刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。
本地编写代码
调整心态,保持冷静
输入输出完整性
快捷键使用
Ctrl+D
,复制、粘贴和撤销分别为 Ctrl+C
,Ctrl+V
,Ctrl+Z
,这些可以正常使用。Ctrl+S
,以免触发浏览器的保存功能。浏览器要求
交卷相关
时间和分数安排
考试环境准备
技术问题处理
祝你考试顺利,取得理想成绩!