2026.9.20 华为OD机试真题 新系统 【简单 URL 路径提取器】

简单 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

说明

单独 / 也作为合法路径统计。

题解

思路:字符串处理 + 自定义排序

  1. 遍历输入的每个url,正确拆分出每个url包含的路径前缀,并使用哈希表或者类似结构记录每个前缀出现的次数。
  2. 根据统计的前缀信息提取出出现次数>=2的前缀,使用{前缀,出现次数}结构存储至数组中。
  3. 将提取出来的前缀按照 出现次数降序,次数相同时,路径前缀字典序升序规则进行自定义排序。
  4. 按照题目要求将排序后前缀数组转换为要求格式的字符串数组返回。

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;
}
相关推荐
无限码力3 天前
2026华为OD机试真题 新系统 9月27日【坚果巧克力】
算法·华为od·华为od机考·华为od机试真题·华为od机试·华为od机考真题题解·华为社招笔试真题
无限码力13 天前
9.16华为OD机试真题 新系统 - 矩阵螺旋遍历 (Java/Py/C/C++/Js/Go)
华为od机考·华为od机试·华为od机试真题题解·华为od笔试·华为od机试真题 新系统·华为od9月16号真题
c++开发工程师18 天前
Scrum 核心角色与分工
华为od·scrum
无限码力20 天前
华为OD机试真题 新系统【图的遍历】
华为od·华为od机试·华为od上机考试真题·华为od机考真题·华为od机试真题题解·华为od机考真题题解·华为机试题库
哪 吒23 天前
华为OD机试 - 云服务安全策略最优选择 - 深度优先搜索DFS(Java 新系统 200分)
java·华为od·深度优先
无限码力1 个月前
8.26华为OD机试真题 新系统【字符串回文判断】
算法·华为od·华为od机考·华为od机试·华为od上机考试真题·华为od最新机试真题题解·华为od机试真题题库
s180579163622 个月前
餐饮小程序GEO优化
华为od
令狐掌门3 个月前
2026华为OD面试题020:日志文件异常检测
算法·leetcode·华为od
令狐掌门3 个月前
2026华为OD面试题018:智能成绩表
华为od