简单 URL 路径提取器(Java/Py/C/C++/Js/Go)题解
华为OD机试真题 新系统 华为OD上机考试真题新系统 9月20号 100分题型
华为OD机试新系统真题目录点击查看: 华为OD机试新系统真题题库目录|机考题库 + 算法考点详解
题目内容
在企业安全运营工作中,安全分析人员需要从海量日志中识别异常访问模式,统计出频率较高的 URL 路径模式。
现有一批访问日志,请提取出现次数不少于 2 2 2 次的不同 URL 路径前缀,并统计其出现次数。
路径前缀说明:对于 URL /a/b/c,其所有路径前缀依次为:/a,/a/b,/a/b/c。
输入描述
字符串数组,数组里每个元素表示一条 URL;URL 包含小写英文字母、/ 和 .;URL 总长度之和 ≤ 10 6 \leq 10^6 ≤106;每条 URL 至少包含一个路径前缀;根路径 / 仅来自于输入,不做派生。
输出描述
字符串数组,每个元素表示该前缀字符及其出现次数,格式为:路径前缀 出现次数(用空格分隔);如果没有符合条件的字符串返回空数组。按出现次数从多到少排序;次数相同时,按路径前缀字典序升序排列。
样例1
输入
/api/v1/users/list,/api/v1/users/detail,/api/v1/orders,/api/v2/products
输出
/api 4,/api/v1 3,/api/v1/users 2
说明
URL 提取路径前缀:
-
/api/v1/users/list:/api,/api/v1,/api/v1/users,/api/v1/users/list -
/api/v1/users/detail:/api,/api/v1,/api/v1/users,/api/v1/users/detail -
/api/v1/orders:/api,/api/v1,/api/v1/orders -
/api/v2/products:/api,/api/v2,/api/v2/products统计结果(出现次数 ≥ 2 \geq 2 ≥2):
-
/api: 4 4 4 次 -
/api/v1: 3 3 3 次 -
/api/v1/users: 2 2 2 次 -
输出:
/api 4,/api/v1 3,/api/v1/users 2。
样例2
输入
/admin,/api.js,/admin,/api.js
输出
/admin 2,/api.js 2
说明
路径前缀出现次数相同(均为 2 2 2 次),按字典序升序排列:/admin < /api.js。
样例3
输入
/,/
输出
/ 2
说明
单独 / 也作为合法路径统计。
题解
思路:字符串处理 + 自定义排序
- 遍历输入的每个
url,正确拆分出每个url包含的路径前缀,并使用哈希表或者类似结构记录每个前缀出现的次数。 - 根据统计的前缀信息提取出出现次数>=2的前缀,使用
{前缀,出现次数}结构存储至数组中。 - 将提取出来的前缀按照
出现次数降序,次数相同时,路径前缀字典序升序规则进行自定义排序。 - 按照题目要求将排序后前缀数组转换为要求格式的字符串数组返回。
C++
cpp
#include <bits/stdc++.h>
#include <vector>
using namespace std;
// 通用 切割函数 函数 将字符串str根据delimiter进行切割
vector<string> split(const string& str, const string& delimiter) {
vector<string> result;
size_t start = 0;
size_t end = str.find(delimiter);
while (end != string::npos) {
result.push_back(str.substr(start, end - start));
start = end + delimiter.length();
end = str.find(delimiter, start);
}
// 添加最后一个部分
result.push_back(str.substr(start));
return result;
}
vector<string> solve(vector<string>& urls) {
unordered_map<string, int> cnt;
for (string& url : urls) {
// 根路径 / 仅来自输入,不做派生
if (url == "/") {
cnt[url]++;
continue;
}
// 从每一个路径分隔符 / 开始,寻找下一个 /
for (int i = 1; i <= (int)url.size(); i++) {
if (i == (int)url.size() || url[i] == '/') {
string prefix = url.substr(0, i);
cnt[prefix]++;
}
}
}
// 提取出现次数 >= 2的前缀
vector<pair<string, int>> res;
for (auto& [prefix, count] : cnt) {
if (count >= 2) {
res.push_back({prefix, count});
}
}
// 出现次数从多到少
// 次数相同时,路径前缀字典序升序
sort(res.begin(), res.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second;
}
return a.first < b.first;
});
vector<string> ans;
for (auto& [prefix, count] : res) {
ans.push_back(prefix + " " + to_string(count));
}
return ans;
}
int main() {
string input;
getline(cin, input);
vector<string> urls = split(input, ",");
vector<string> ans = solve(urls);
for (int i = 0; i < ans.size(); i++) {
if (i > 0) {
cout << ",";
}
cout << ans[i];
}
return 0;
}
Java
java
import java.util.*;
public class Main {
static List<String> solve(List<String> urls) {
Map<String, Integer> cnt = new HashMap<>();
for (String url : urls) {
// 根路径 / 仅来自输入,不做派生
if (url.equals("/")) {
cnt.put(url, cnt.getOrDefault(url, 0) + 1);
continue;
}
// 从每一个路径分隔符 / 开始,寻找下一个 /
for (int i = 1; i <= url.length(); i++) {
if (i == url.length() || url.charAt(i) == '/') {
String prefix = url.substring(0, i);
cnt.put(prefix, cnt.getOrDefault(prefix, 0) + 1);
}
}
}
// 提取出现次数 >= 2的前缀
List<Map.Entry<String, Integer>> res = new ArrayList<>();
for (Map.Entry<String, Integer> entry : cnt.entrySet()) {
if (entry.getValue() >= 2) {
res.add(entry);
}
}
// 出现次数从多到少
// 次数相同时,路径前缀字典序升序
res.sort((a, b) -> {
if (!a.getValue().equals(b.getValue())) {
return Integer.compare(b.getValue(), a.getValue());
}
return a.getKey().compareTo(b.getKey());
});
List<String> ans = new ArrayList<>();
for (Map.Entry<String, Integer> entry : res) {
ans.add(entry.getKey() + " " + entry.getValue());
}
return ans;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
List<String> urls = Arrays.asList(input.split(","));
List<String> ans = solve(urls);
for (int i = 0; i < ans.size(); i++) {
if (i > 0) {
System.out.print(",");
}
System.out.print(ans.get(i));
}
}
}
Python
python
def solve(urls):
cnt = {}
for url in urls:
# 根路径 / 仅来自输入,不做派生
if url == "/":
cnt[url] = cnt.get(url, 0) + 1
continue
# 从每一个路径分隔符 / 开始,寻找下一个 /
for i in range(1, len(url) + 1):
if i == len(url) or url[i] == '/':
prefix = url[:i]
cnt[prefix] = cnt.get(prefix, 0) + 1
# 提取出现次数 >= 2的前缀
res = []
for prefix, count in cnt.items():
if count >= 2:
res.append((prefix, count))
# 出现次数从多到少
# 次数相同时,路径前缀字典序升序
res.sort(key=lambda x: (-x[1], x[0]))
ans = []
for prefix, count in res:
ans.append(prefix + " " + str(count))
return ans
input_str = input()
urls = input_str.split(",")
ans = solve(urls)
print(",".join(ans), end="")
JavaScript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on("line", input => {
function solve(urls) {
const cnt = new Map();
for (const url of urls) {
// 根路径 / 仅来自输入,不做派生
if (url === "/") {
cnt.set(url, (cnt.get(url) || 0) + 1);
continue;
}
// 从每一个路径分隔符 / 开始,寻找下一个 /
for (let i = 1; i <= url.length; i++) {
if (i === url.length || url[i] === '/') {
const prefix = url.substring(0, i);
cnt.set(prefix, (cnt.get(prefix) || 0) + 1);
}
}
}
// 提取出现次数 >= 2的前缀
const res = [];
for (const [prefix, count] of cnt) {
if (count >= 2) {
res.push([prefix, count]);
}
}
// 出现次数从多到少
// 次数相同时,路径前缀字典序升序
res.sort((a, b) => {
if (a[1] !== b[1]) {
return b[1] - a[1];
}
return a[0].localeCompare(b[0]);
});
const ans = [];
for (const [prefix, count] of res) {
ans.push(prefix + " " + count);
}
return ans;
}
const urls = input.split(",");
const ans = solve(urls);
console.log(ans.join(","));
rl.close();
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
type Pair struct {
prefix string
count int
}
func solve(urls []string) []string {
cnt := make(map[string]int)
for _, url := range urls {
// 根路径 / 仅来自输入,不做派生
if url == "/" {
cnt[url]++
continue
}
// 从每一个路径分隔符 / 开始,寻找下一个 /
for i := 1; i <= len(url); i++ {
if i == len(url) || url[i] == '/' {
prefix := url[:i]
cnt[prefix]++
}
}
}
// 提取出现次数 >= 2的前缀
res := make([]Pair, 0)
for prefix, count := range cnt {
if count >= 2 {
res = append(res, Pair{prefix, count})
}
}
// 出现次数从多到少
// 次数相同时,路径前缀字典序升序
sort.Slice(res, func(i, j int) bool {
if res[i].count != res[j].count {
return res[i].count > res[j].count
}
return res[i].prefix < res[j].prefix
})
ans := make([]string, 0)
for _, item := range res {
ans = append(ans, fmt.Sprintf("%s %d", item.prefix, item.count))
}
return ans
}
func main() {
in := bufio.NewReader(os.Stdin)
input, _ := bufio.NewReader(in).ReadString('\n')
input = strings.TrimSpace(input)
urls := strings.Split(input, ",")
ans := solve(urls)
for i := 0; i < len(ans); i++ {
if i > 0 {
fmt.Print(",")
}
fmt.Print(ans[i])
}
}
C语言
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char prefix[1000];
int count;
} Pair;
typedef struct {
char prefix[1000];
int count;
} Node;
// 查找前缀
int findPrefix(Node cnt[], int size, const char* prefix) {
for (int i = 0; i < size; i++) {
if (strcmp(cnt[i].prefix, prefix) == 0) {
return i;
}
}
return -1;
}
// 排序比较函数
int cmp(const void* a, const void* b) {
const Pair* x = (const Pair*)a;
const Pair* y = (const Pair*)b;
// 出现次数从多到少
if (x->count != y->count) {
return y->count - x->count;
}
// 次数相同时,路径前缀字典序升序
return strcmp(x->prefix, y->prefix);
}
void solve(char** urls, int n, char ans[][1050], int* ansSize) {
Node cnt[10000];
int cntSize = 0;
for (int u = 0; u < n; u++) {
char* url = urls[u];
// 根路径 / 仅来自输入,不做派生
if (strcmp(url, "/") == 0) {
int index = findPrefix(cnt, cntSize, url);
if (index == -1) {
strcpy(cnt[cntSize].prefix, url);
cnt[cntSize].count = 1;
cntSize++;
} else {
cnt[index].count++;
}
continue;
}
// 从每一个路径分隔符 / 开始,寻找下一个 /
int len = strlen(url);
for (int i = 1; i <= len; i++) {
if (i == len || url[i] == '/') {
char prefix[1000];
strncpy(prefix, url, i);
prefix[i] = '\0';
int index = findPrefix(cnt, cntSize, prefix);
if (index == -1) {
strcpy(cnt[cntSize].prefix, prefix);
cnt[cntSize].count = 1;
cntSize++;
} else {
cnt[index].count++;
}
}
}
}
// 提取出现次数 >= 2的前缀
Pair res[10000];
int resSize = 0;
for (int i = 0; i < cntSize; i++) {
if (cnt[i].count >= 2) {
strcpy(res[resSize].prefix, cnt[i].prefix);
res[resSize].count = cnt[i].count;
resSize++;
}
}
qsort(res, resSize, sizeof(Pair), cmp);
for (int i = 0; i < resSize; i++) {
sprintf(ans[i], "%s %d", res[i].prefix, res[i].count);
}
*ansSize = resSize;
}
int main() {
char input[2000005];
fgets(input, sizeof(input), stdin);
// 去掉换行符
input[strcspn(input, "\n")] = '\0';
char* urls[1000005];
int n = 0;
char* token = strtok(input, ",");
while (token != NULL) {
urls[n++] = token;
token = strtok(NULL, ",");
}
char ans[10000][1050];
int ansSize = 0;
solve(urls, n, ans, &ansSize);
for (int i = 0; i < ansSize; i++) {
if (i > 0) {
printf(",");
}
printf("%s", ans[i]);
}
return 0;
}