日志关键词统计

2026 华为OD机试真题 6月22日华为OD上机新系统考试真题 100 分题型

点击查看华为 OD 机试真题完整目录:2026最新华为OD机试新系统卷 + 双机位C卷 真题题库目录|全覆盖题库 + 逐点算法考点详解

题目描述

给定日志字符串数组 logs 和关键词数组 keywords,统计每个关键词在所有日志中出现的总次数,并找出经常一起出现的关键词组合(至少在 2 条日志中同时出现)。

注意

  1. 关键词匹配大小写不敏感,完整单词匹配(不能是部分匹配)
  2. 单词边界:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)或字符串首尾,英文标点,无中文标点
  3. 关联组合:两个关键词在至少 2 条不同日志中同时出现才算"经常关联"
  4. 返回结果:前 keywords.length 个元素为关键词出现次数,后续元素每两个一组表示关联组合索引(索引小的在前,索引大的在后);多个组合时先按第一个索引升序,再按第二个索引升序

数据规模

1≤logs.length ≤1000,1≤keywords.length ≤100,每条日志长度 ≤1000 字符

输入描述

日志字符串数组 logs 和关键词数组 keywords

输出描述

经常一起出现的关键词组合

示例1

输入

Error in system,warning: error detected,No errors found
error,warning

输出

2,1

说明

关键词统计:error 出现 2 次,warning 出现 1 次

关联分析:error 和 warning 只在第 2 条日志中同时出现 1 次,未达到至少 2 次共现的标准,因此无关联组合输出

示例2

输入

Error: system failure,Warning: error in network,System error detected again,Network warning error found
error,system,warning,network

输出

4,2,2,2,0,1,0,2,0,3,2,3

说明

关键词统计:error 出现 4 次,system 出现 2 次,warning 出现 2 次,network 出现 2 次 关联组合:

  • (error,system):在第 1、3 条日志中同时出现,共现 2 次 → 输出 [0,1]
  • (error,warning):在第 2、4 条日志中同时出现,共现 2 次 → 输出 [0,2]
  • (error,network):在第 2、3、4 条日志中同时出现,共现 3次 → 输出 [0,3]
  • (warning,network):在第 2、4 条日志中同时出现,共现 2 次 → 输出 [2,3] 最终结果:统计次数 [4,2,2,2] + 关联组合索引 [0,1,0,2,0,3,2,3]

示例3

输入

Error in module A,Module error B error,Error module C error,Module error D
error,module

输出

6,4,0,1

说明

关键词统计:error 出现 6 次(第 1 条 1 次,第 2 条 2 次,第 3 条 2 次,第 4 条 1 次),module 出现 4 次

关联组合:(error,module)在第 1、2、3、4 条日志中都同时出现,共现 4 次 → 输出 [0,1] 最终结果:统计次数 [6,4] + 关联组合索引 [0,1]

示例4

输入

Test log one,Test log two,Test log three
test,log,one,two,three

输出

3,3,1,1,1,0,1

说明

关键词统计:test 出现 3 次,log 出现 3 次,one 出现 1 次,two 出现 1 次,three 出现 1 次

关联组合:(test,log)在第 1、2、3 条日志中都同时出现,共现 3 次 → 输出 [0,1]

其他关键词组合未达到 2 次共现标准

最终结果:统计次数 [3,3,1,1,1] + 关联组合索引 [0,1]

解题思路

本题需要解决两个核心问题:关键词计数关联组合发现

1. 关键词计数

  • 大小写不敏感:所有文本和关键词统一转为小写进行比较
  • 完整单词匹配:使用正则表达式 [\s,.!?;:]+ 将日志切分为单词,只匹配完整单词,不做子串匹配
  • 统计方法:遍历切分后的单词列表,累加每个关键词的出现次数

2. 关联组合发现

  • 预处理优化:将每条日志中出现过的关键词用集合(Set)记录,便于快速判断
  • 共现判断:对于每一对关键词 (i, j),遍历所有日志,检查该对关键词同时出现的次数
  • 过滤条件:只有共现次数 >= 2 的关键词对才被保留

3. 结果组织

  • 前 N 个元素为每个关键词的出现次数
  • 后续元素每两个一组,代表一个满足条件的关联组合(索引小的在前)

复杂度分析

  • 时间复杂度:O(L + K * N + K² * L)
    • L 为日志总数,K 为关键词数量
    • 单词切分和计数 O(L)
    • 关联组合枚举 O(K² * L)
  • 空间复杂度:O(L + K + K²) 用于存储切分结果、计数数组和关联组合

Java

import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 * 日志关键词统计解决方案
 *
 * 核心思路:
 * 1. 使用正则表达式按单词边界切分日志
 * 2. 统计每个关键词的出现次数(大小写不敏感)
 * 3. 枚举所有关键词对,统计共现次数,找出满足条件的关联组合
 */
public class Main {
    // 单词边界正则:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
    private static final Pattern WORD_PATTERN = Pattern.compile("[\\s,.!?;:]+");

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取日志数组(第一行,逗号分隔)
        String[] logsArray = scanner.nextLine().split(",");
        // 读取关键词数组(第二行,逗号分隔)
        String[] keywordsArray = scanner.nextLine().split(",");

        // 调用解决方案
        List<Integer> result = analyzeLogKeywords(logsArray, keywordsArray);

        // 输出结果
        StringBuilder output = new StringBuilder();
        for (int i = 0; i < result.size(); i++) {
            if (i > 0) output.append(",");
            output.append(result.get(i));
        }
        System.out.println(output.toString());
    }

    /**
     * 分析日志关键词
     *
     * @param logs     日志字符串数组
     * @param keywords 关键词数组
     * @return 前keywords.length个元素为关键词出现次数,后续每两个元素表示关联组合索引
     */
    public static List<Integer> analyzeLogKeywords(String[] logs, String[] keywords) {
        // 预处理:关键词统一转为小写
        String[] normalizedKeywords = new String[keywords.length];
        for (int i = 0; i < keywords.length; i++) {
            normalizedKeywords[i] = keywords[i].toLowerCase();
        }

        // 第一步:统计每个关键词的出现次数
        int[] keywordCounts = new int[keywords.length];
        // 预处理:每条日志中出现的关键词集合
        List<boolean[]> presenceList = new ArrayList<>();

        for (String log : logs) {
            // 切分日志为单词列表(小写)
            List<String> words = new ArrayList<>();
            String[] parts = WORD_PATTERN.split(log);
            for (String part : parts) {
                if (!part.isEmpty()) {
                    words.add(part.toLowerCase());
                }
            }

            // 统计该日志中每个关键词的出现次数
            for (int i = 0; i < normalizedKeywords.length; i++) {
                String kw = normalizedKeywords[i];
                for (String w : words) {
                    if (w.equals(kw)) {
                        keywordCounts[i]++;
                    }
                }
            }

            // 记录该日志中出现了哪些关键词
            Set<String> wordSet = new HashSet<>(words);
            boolean[] present = new boolean[keywords.length];
            for (int i = 0; i < normalizedKeywords.length; i++) {
                present[i] = wordSet.contains(normalizedKeywords[i]);
            }
            presenceList.add(present);
        }

        // 第二步:找出经常一起出现的关键词组合
        List<Integer> pairs = new ArrayList<>();
        for (int i = 0; i < keywords.length; i++) {
            for (int j = i + 1; j < keywords.length; j++) {
                int coOccurrence = 0;
                // 统计两个关键词同时出现在多少条日志中
                for (boolean[] presence : presenceList) {
                    if (presence[i] && presence[j]) {
                        coOccurrence++;
                    }
                }
                // 至少在2条日志中同时出现才算"经常关联"
                if (coOccurrence >= 2) {
                    pairs.add(i);
                    pairs.add(j);
                }
            }
        }

        // 合并结果:统计次数 + 关联组合索引
        List<Integer> result = new ArrayList<>();
        for (int count : keywordCounts) {
            result.add(count);
        }
        result.addAll(pairs);

        return result;
    }
}

Python

import re
from typing import List

# 与题面一致的单词切分规则:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
WORD_PATTERN = re.compile(r"[\s,.!?;:]+")


def analyze_log_keywords(logs: List[str], keywords: List[str]) -> List[int]:
    """
    分析日志关键词

    Args:
        logs: 日志字符串数组
        keywords: 关键词数组

    Returns:
        前keywords.length个元素为关键词出现次数,后续每两个元素表示关联组合索引
    """
    # 预处理:关键词统一转为小写,便于后续不区分大小写比较
    normalized_keywords = [kw.lower() for kw in keywords]

    # 第一步:统计每个关键词在所有日志中出现的总次数
    keyword_counts = [0] * len(keywords)
    # 预处理:每条日志中出现了哪些关键词
    presence_list = []

    for log_line in logs:
        # 将日志切分为单词列表,并转为小写
        words = [word.lower() for word in WORD_PATTERN.split(log_line) if word]

        # 统计该日志中每个关键词的出现次数
        for idx, kw in enumerate(normalized_keywords):
            # 完整单词匹配,累加出现次数
            keyword_counts[idx] += sum(1 for w in words if w == kw)

        # 记录该日志中出现了哪些关键词(去重)
        word_set = set(words)
        presence_list.append([kw in word_set for kw in normalized_keywords])

    # 第二步:找出经常一起出现的关键词组合
    result_pairs = []
    for i in range(len(keywords)):
        for j in range(i + 1, len(keywords)):
            # 统计两个关键词同时出现在多少条日志中
            co_occurrence = sum(
                1 for presence in presence_list
                if presence[i] and presence[j]
            )
            # 至少在2条日志中同时出现才算"经常关联"
            if co_occurrence >= 2:
                result_pairs.extend([i, j])

    # 返回:统计次数 + 关联组合索引
    return keyword_counts + result_pairs


def main():
    """主函数:处理输入输出"""
    # 读取日志数组(第一行,逗号分隔)
    logs = input().split(',')
    # 读取关键词数组(第二行,逗号分隔)
    keywords = input().split(',')

    # 调用解决方案
    result = analyze_log_keywords(logs, keywords)

    # 输出结果
    print(','.join(map(str, result)))


if __name__ == "__main__":
    main()

JavaScript

/**
 * 日志关键词统计解决方案
 *
 * 核心思路:
 * 1. 使用正则表达式按单词边界切分日志
 * 2. 统计每个关键词的出现次数(大小写不敏感)
 * 3. 枚举所有关键词对,统计共现次数,找出满足条件的关联组合
 */

// 单词边界正则:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
const WORD_PATTERN = /[\s,.!?;:]+/;

/**
 * 分析日志关键词
 * @param {string[]} logs - 日志字符串数组
 * @param {string[]} keywords - 关键词数组
 * @returns {number[]} 前keywords.length个元素为关键词出现次数,后续每两个元素表示关联组合索引
 */
function analyzeLogKeywords(logs, keywords) {
    // 预处理:关键词统一转为小写
    const normalizedKeywords = keywords.map(kw => kw.toLowerCase());

    // 第一步:统计每个关键词的出现次数
    const keywordCounts = new Array(keywords.length).fill(0);
    // 预处理:每条日志中出现的关键词集合
    const presenceList = [];

    for (const log of logs) {
        // 切分日志为单词列表(小写)
        const words = log.split(WORD_PATTERN)
            .filter(word => word)
            .map(word => word.toLowerCase());

        // 统计该日志中每个关键词的出现次数
        for (let i = 0; i < normalizedKeywords.length; i++) {
            const count = words.filter(w => w === normalizedKeywords[i]).length;
            keywordCounts[i] += count;
        }

        // 记录该日志中出现了哪些关键词(去重)
        const wordSet = new Set(words);
        const present = normalizedKeywords.map(kw => wordSet.has(kw));
        presenceList.push(present);
    }

    // 第二步:找出经常一起出现的关键词组合
    const resultPairs = [];
    for (let i = 0; i < keywords.length; i++) {
        for (let j = i + 1; j < keywords.length; j++) {
            // 统计两个关键词同时出现在多少条日志中
            let coOccurrence = 0;
            for (const presence of presenceList) {
                if (presence[i] && presence[j]) {
                    coOccurrence++;
                }
            }
            // 至少在2条日志中同时出现才算"经常关联"
            if (coOccurrence >= 2) {
                resultPairs.push(i, j);
            }
        }
    }

    // 合并结果:统计次数 + 关联组合索引
    return [...keywordCounts, ...resultPairs];
}

// 主函数
function main() {
    const readline = require('readline');
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    const lines = [];
    rl.on('line', (line) => {
        lines.push(line);
    });

    rl.on('close', () => {
        // 解析输入
        const logs = lines[0].split(',');
        const keywords = lines[1].split(',');

        // 调用解决方案
        const result = analyzeLogKeywords(logs, keywords);

        // 输出结果
        console.log(result.join(','));
    });
}

main();

C++

#include <iostream>
#include <vector>
#include <string>
#include <regex>
#include <unordered_set>
#include <algorithm>

using namespace std;

/**
 * 日志关键词统计解决方案
 *
 * 核心思路:
 * 1. 使用正则表达式按单词边界切分日志
 * 2. 统计每个关键词的出现次数(大小写不敏感)
 * 3. 枚举所有关键词对,统计共现次数,找出满足条件的关联组合
 */

// 单词边界正则:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
const regex WORD_PATTERN(R"([\s,.!?;:]+)");

/**
 * 字符串转小写
 */
string toLower(const string& str) {
    string result = str;
    transform(result.begin(), result.end(), result.begin(), ::tolower);
    return result;
}

/**
 * 分析日志关键词
 *
 * @param logs 日志字符串数组
 * @param keywords 关键词数组
 * @return 前keywords.size()个元素为关键词出现次数,后续每两个元素表示关联组合索引
 */
vector<int> analyzeLogKeywords(const vector<string>& logs, const vector<string>& keywords) {
    // 预处理:关键词统一转为小写
    vector<string> normalizedKeywords;
    for (const string& kw : keywords) {
        normalizedKeywords.push_back(toLower(kw));
    }

    int keywordCount = keywords.size();

    // 第一步:统计每个关键词的出现次数
    vector<int> keywordCounts(keywordCount, 0);
    // 预处理:每条日志中出现的关键词集合
    vector<vector<bool>> presenceList;

    for (const string& log : logs) {
        // 切分日志为单词列表(小写)
        vector<string> words;
        sregex_token_iterator it(log.begin(), log.end(), WORD_PATTERN, -1);
        sregex_token_iterator end;
        for (; it != end; ++it) {
            if (!it->str().empty()) {
                words.push_back(toLower(it->str()));
            }
        }

        // 统计该日志中每个关键词的出现次数
        for (int i = 0; i < keywordCount; i++) {
            const string& kw = normalizedKeywords[i];
            for (const string& w : words) {
                if (w == kw) {
                    keywordCounts[i]++;
                }
            }
        }

        // 记录该日志中出现了哪些关键词(去重)
        unordered_set<string> wordSet(words.begin(), words.end());
        vector<bool> present(keywordCount);
        for (int i = 0; i < keywordCount; i++) {
            present[i] = wordSet.count(normalizedKeywords[i]) > 0;
        }
        presenceList.push_back(present);
    }

    // 第二步:找出经常一起出现的关键词组合
    vector<int> resultPairs;
    for (int i = 0; i < keywordCount; i++) {
        for (int j = i + 1; j < keywordCount; j++) {
            // 统计两个关键词同时出现在多少条日志中
            int coOccurrence = 0;
            for (const vector<bool>& presence : presenceList) {
                if (presence[i] && presence[j]) {
                    coOccurrence++;
                }
            }
            // 至少在2条日志中同时出现才算"经常关联"
            if (coOccurrence >= 2) {
                resultPairs.push_back(i);
                resultPairs.push_back(j);
            }
        }
    }

    // 合并结果:统计次数 + 关联组合索引
    vector<int> result;
    for (int count : keywordCounts) {
        result.push_back(count);
    }
    for (int p : resultPairs) {
        result.push_back(p);
    }

    return result;
}

/**
 * 分割字符串
 */
vector<string> split(const string& str, char delimiter) {
    vector<string> tokens;
    string token;
    istringstream tokenStream(str);
    while (getline(tokenStream, token, delimiter)) {
        tokens.push_back(token);
    }
    return tokens;
}

int main() {
    // 读取日志数组(第一行,逗号分隔)
    string logsLine;
    getline(cin, logsLine);
    vector<string> logs = split(logsLine, ',');

    // 读取关键词数组(第二行,逗号分隔)
    string keywordsLine;
    getline(cin, keywordsLine);
    vector<string> keywords = split(keywordsLine, ',');

    // 调用解决方案
    vector<int> result = analyzeLogKeywords(logs, keywords);

    // 输出结果
    for (size_t i = 0; i < result.size(); i++) {
        if (i > 0) cout << ",";
        cout << result[i];
    }
    cout << endl;

    return 0;
}

Go

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"
)

/**
 * 日志关键词统计解决方案
 *
 * 核心思路:
 * 1. 使用正则表达式按单词边界切分日志
 * 2. 统计每个关键词的出现次数(大小写不敏感)
 * 3. 枚举所有关键词对,统计共现次数,找出满足条件的关联组合
 */

// 单词边界正则:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
var wordPattern = regexp.MustCompile(`[\s,.!?;:]+`)

/**
 * 分析日志关键词
 *
 * @param logs 日志字符串数组
 * @param keywords 关键词数组
 * @return 前len(keywords)个元素为关键词出现次数,后续每两个元素表示关联组合索引
 */
func analyzeLogKeywords(logs []string, keywords []string) []int {
	keywordCount := len(keywords)

	// 预处理:关键词统一转为小写
	normalizedKeywords := make([]string, keywordCount)
	for i, kw := range keywords {
		normalizedKeywords[i] = strings.ToLower(kw)
	}

	// 第一步:统计每个关键词的出现次数
	keywordCounts := make([]int, keywordCount)
	// 预处理:每条日志中出现的关键词集合
	presenceList := make([][]bool, 0)

	for _, log := range logs {
		// 切分日志为单词列表(小写)
		parts := wordPattern.Split(log, -1)
		words := make([]string, 0)
		for _, part := range parts {
			if part != "" {
				words = append(words, strings.ToLower(part))
			}
		}

		// 统计该日志中每个关键词的出现次数
		for i, kw := range normalizedKeywords {
			for _, w := range words {
				if w == kw {
					keywordCounts[i]++
				}
			}
		}

		// 记录该日志中出现了哪些关键词(去重)
		wordSet := make(map[string]bool)
		for _, w := range words {
			wordSet[w] = true
		}
		present := make([]bool, keywordCount)
		for i, kw := range normalizedKeywords {
			present[i] = wordSet[kw]
		}
		presenceList = append(presenceList, present)
	}

	// 第二步:找出经常一起出现的关键词组合
	resultPairs := make([]int, 0)
	for i := 0; i < keywordCount; i++ {
		for j := i + 1; j < keywordCount; j++ {
			// 统计两个关键词同时出现在多少条日志中
			coOccurrence := 0
			for _, presence := range presenceList {
				if presence[i] && presence[j] {
					coOccurrence++
				}
			}
			// 至少在2条日志中同时出现才算"经常关联"
			if coOccurrence >= 2 {
				resultPairs = append(resultPairs, i, j)
			}
		}
	}

	// 合并结果:统计次数 + 关联组合索引
	result := make([]int, 0, keywordCount+len(resultPairs))
	result = append(result, keywordCounts...)
	result = append(result, resultPairs...)

	return result
}

/**
 * 分割字符串
 */
func split(s string, c byte) []string {
	result := make([]string, 0)
	start := 0
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			result = append(result, s[start:i])
			start = i + 1
		}
	}
	result = append(result, s[start:])
	return result
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 读取日志数组(第一行,逗号分隔)
	logsLine, _ := reader.ReadString('\n')
	logsLine = strings.TrimSpace(logsLine)
	logs := split(logsLine, ',')

	// 读取关键词数组(第二行,逗号分隔)
	keywordsLine, _ := reader.ReadString('\n')
	keywordsLine = strings.TrimSpace(keywordsLine)
	keywords := split(keywordsLine, ',')

	// 调用解决方案
	result := analyzeLogKeywords(logs, keywords)

	// 输出结果
	for i, v := range result {
		if i > 0 {
			fmt.Print(",")
		}
		fmt.Print(v)
	}
	fmt.Println()
}

C语言

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>

/**
 * 日志关键词统计解决方案
 *
 * 核心思路:
 * 1. 使用自定义函数按单词边界切分日志
 * 2. 统计每个关键词的出现次数(大小写不敏感)
 * 3. 枚举所有关键词对,统计共现次数,找出满足条件的关联组合
 */

#define MAX_LOGS 1000
#define MAX_LOG_LEN 1000
#define MAX_KEYWORDS 100
#define MAX_WORDS 500

// 单词边界字符集:空格、标点符号(逗号、句号、感叹号、问号、分号、冒号)
bool isWordBoundary(char c) {
    return isspace(c) || c == ',' || c == '.' || c == '!' || 
           c == '?' || c == ';' || c == ':';
}

/**
 * 字符串转小写
 */
void toLowerCase(char *dest, const char *src, int maxLen) {
    int i = 0;
    while (src[i] && i < maxLen - 1) {
        dest[i] = tolower((unsigned char)src[i]);
        i++;
    }
    dest[i] = '\0';
}

/**
 * 字符串相等(忽略大小写)
 */
bool strEqualIgnoreCase(const char *s1, const char *s2) {
    while (*s1 && *s2) {
        if (tolower((unsigned char)*s1) != tolower((unsigned char)*s2)) {
            return false;
        }
        s1++;
        s2++;
    }
    return *s1 == *s2;
}

/**
 * 切分字符串为单词
 * 返回单词数量
 */
int splitWords(const char *str, char words[][MAX_LOG_LEN], int maxWords) {
    int count = 0;
    int len = strlen(str);
    int i = 0;

    while (i < len && count < maxWords) {
        // 跳过边界字符
        while (i < len && isWordBoundary(str[i])) {
            i++;
        }
        if (i >= len) break;

        // 记录单词起始位置
        int start = i;
        // 找到单词结束位置
        while (i < len && !isWordBoundary(str[i])) {
            i++;
        }

        // 提取单词并转为小写
        int wordLen = i - start;
        if (wordLen > 0 && wordLen < MAX_LOG_LEN) {
            toLowerCase(words[count], str + start, wordLen + 1);
            count++;
        }
    }

    return count;
}

int main() {
    char logsLine[MAX_LOG_LEN * MAX_LOGS];
    char keywordsLine[MAX_LOG_LEN];

    // 读取日志数组(第一行,逗号分隔)
    fgets(logsLine, sizeof(logsLine), stdin);
    logsLine[strcspn(logsLine, "\n")] = '\0';
    
    // 读取关键词数组(第二行,逗号分隔)
    fgets(keywordsLine, sizeof(keywordsLine), stdin);
    keywordsLine[strcspn(keywordsLine, "\n")] = '\0';

    // 解析日志数组
    char *logs[MAX_LOGS];
    int logCount = 0;
    char *logToken = strtok(logsLine, ",");
    while (logToken != NULL && logCount < MAX_LOGS) {
        logs[logCount++] = logToken;
        logToken = strtok(NULL, ",");
    }

    // 解析关键词数组
    char *keywords[MAX_KEYWORDS];
    int keywordCount = 0;
    char *kwToken = strtok(keywordsLine, ",");
    while (kwToken != NULL && keywordCount < MAX_KEYWORDS) {
        keywords[keywordCount++] = kwToken;
        kwToken = strtok(NULL, ",");
    }

    // 预处理:关键词统一转为小写
    char normalizedKeywords[MAX_KEYWORDS][MAX_LOG_LEN];
    for (int i = 0; i < keywordCount; i++) {
        toLowerCase(normalizedKeywords[i], keywords[i], MAX_LOG_LEN);
    }

    // 第一步:统计每个关键词的出现次数
    int keywordCounts[MAX_KEYWORDS] = {0};
    // 预处理:每条日志中出现的关键词
    bool presence[MAX_LOGS][MAX_KEYWORDS];
    int presenceCount = 0;

    for (int l = 0; l < logCount; l++) {
        char words[MAX_WORDS][MAX_LOG_LEN];
        int wordCount = splitWords(logs[l], words, MAX_WORDS);

        // 统计该日志中每个关键词的出现次数
        for (int i = 0; i < keywordCount; i++) {
            for (int w = 0; w < wordCount; w++) {
                if (strcmp(words[w], normalizedKeywords[i]) == 0) {
                    keywordCounts[i]++;
                }
            }
        }

        // 记录该日志中出现了哪些关键词(去重)
        bool wordExists[MAX_LOG_LEN] = {false};  // 简单去重
        for (int w = 0; w < wordCount; w++) {
            wordExists[(unsigned char)words[w][0] % MAX_LOG_LEN] = true;
        }
        for (int i = 0; i < keywordCount; i++) {
            presence[presenceCount][i] = false;
            for (int w = 0; w < wordCount; w++) {
                if (strcmp(words[w], normalizedKeywords[i]) == 0) {
                    presence[presenceCount][i] = true;
                    break;
                }
            }
        }
        presenceCount++;
    }

    // 第二步:找出经常一起出现的关键词组合
    int resultPairs[MAX_KEYWORDS * MAX_KEYWORDS];
    int pairCount = 0;

    for (int i = 0; i < keywordCount; i++) {
        for (int j = i + 1; j < keywordCount; j++) {
            // 统计两个关键词同时出现在多少条日志中
            int coOccurrence = 0;
            for (int l = 0; l < presenceCount; l++) {
                if (presence[l][i] && presence[l][j]) {
                    coOccurrence++;
                }
            }
            // 至少在2条日志中同时出现才算"经常关联"
            if (coOccurrence >= 2) {
                resultPairs[pairCount++] = i;
                resultPairs[pairCount++] = j;
            }
        }
    }

    // 输出结果
    for (int i = 0; i < keywordCount; i++) {
        if (i > 0) printf(",");
        printf("%d", keywordCounts[i]);
    }
    for (int i = 0; i < pairCount; i++) {
        printf(",%d", resultPairs[i]);
    }
    printf("\n");

    return 0;
}

完整用例

用例1

Error in system,warning: error detected,No errors found
error,warning

输出:2,1

用例2

Error: system failure,Warning: error in network,System error detected again,Network warning error found
error,system,warning,network

输出:4,2,2,2,0,1,0,2,0,3,2,3

用例3

Error in module A,Module error B error,Error module C error,Module error D
error,module

用例4

Test log one,Test log two,Test log three
test,log,one,two,three

用例5

apple banana,cherry apple,banana date
apple,banana,cherry

用例6

a b c,a b d,a c d
a,b,c

用例7

Hello WORLD,hello world,WORLD hello
hello,world

用例8

one,two;three:four five
one,two,three,four,five

用例9

error errors,no error here,error alone
error

用例10

keyword1 keyword2 keyword3,keyword1 keyword2,keyword1 keyword3,keyword2 keyword3,all three
keyword1,keyword2,keyword3

在这里插入图片描述