3043. 最长公共前缀的长度(Leetcode 每日一题)

3043. 最长公共前缀的长度 | 难度:中等

题目链接LeetCode 3043

题意理解

给定两个正整数数组 arr1arr2,从 arr1 中选一个数 x,从 arr2 中选一个数 y,找出所有数对 (x, y) 中最长公共前缀的长度。

前缀的定义:一个整数的最左边一位或多位数字组成的整数。例如 123 是 12345 的前缀,但 234 不是。

用题目给的样例走一遍:

arr1 中的数 arr2 中的数 公共前缀 长度
1 1000 1 1
10 1000 10 2
100 1000 100 3

最长公共前缀是 100,长度为 3

关键观察 :两个数有公共前缀 c,等价于 c 既是 x 的前缀、又是 y 的前缀。比如 100 是 1000 的前缀,也是 100 的前缀,所以 100 是它们的公共前缀。

解法思路

核心想法:把"前缀匹配"变成"集合查重"

如果我们要判断两个数是否有公共前缀,最直接的想法是:把一个数的所有前缀都拿出来,看另一个数的某个前缀是否也在其中

比如 1000 的所有前缀是 {1000, 100, 10, 1},100 的所有前缀是 {100, 10, 1}。取交集得到 {100, 10, 1},其中最长的就是 100。

但逐对比较太慢了------arr1arr2 最多各 5×10⁴ 个元素,两两配对就是 25 亿对。

换个角度:如果我们把 arr1 中所有数的所有前缀都存进一个集合,然后遍历 arr2 中每个数的前缀,看哪些能在集合里找到,就能高效地解决问题:

  1. 遍历 arr1,对每个数,逐次去掉末位(num / 10),把所有前缀存入 unordered_set
  2. 遍历 arr2,对每个数,同样逐次去掉末位,检查当前前缀是否在集合中出现过
  3. 在所有匹配到的前缀中,取最长的那个,返回其长度

为什么能想到用哈希集合? 因为问题本质是"查重"------判断 arr2 的某个前缀是否在 arr1 的前缀中出现过。哈希集合的查找是 O(1),天然适合这类问题。

如何高效获取一个数的所有前缀?

一个正整数的所有前缀,可以通过反复 除以 10 取整 得到:

复制代码
num = 12345
12345 → 1234 → 123 → 12 → 1 → 0(停止)

每次 num /= 10 就砍掉最后一位,得到的就是一个前缀。这个过程的时间复杂度是 O(d),d 是数字的位数,最多 9 位(因为元素范围 ≤ 10⁸)。

一个小优化

遍历 arr2 中的数时,我们是从大到小检查前缀的(先检查完整的数,再去掉末位)。所以一旦发现某个前缀在集合中,它一定是当前数能匹配到的最长公共前缀,可以直接 break,不必继续往更短的前缀查。

代码实现

理解了上面的思路,代码就很好写了:

cpp 复制代码
class Solution {
public:
    int longestCommonPrefix(vector<int>& arr1, vector<int>& arr2) {
        // 第一步:把 arr1 中所有数的所有前缀存入哈希集合
        unordered_set<int> prefixes;
        for (int num : arr1) {
            while (num > 0) {
                prefixes.insert(num);  // 当前数本身就是自己的一个前缀
                num /= 10;            // 砍掉末位,得到更短的前缀
            }
        }

        // 第二步:遍历 arr2,对每个数从长到短检查前缀
        int maxPrefix = 0;
        for (int num : arr2) {
            while (num > 0) {
                if (prefixes.count(num)) {
                    // 从长到短检查,第一个匹配的一定是最长公共前缀
                    maxPrefix = max(maxPrefix, num);
                    break;  // 不必再查更短的前缀
                }
                num /= 10;  // 砍掉末位,试下一个更短的前缀
            }
        }

        // 第三步:将最长的公共前缀转为字符串,取其长度
        if (maxPrefix == 0) return 0;
        return to_string(maxPrefix).size();
    }
};

复杂度分析

  • 时间复杂度:O(U₁ × D + U₂ × D),其中 U₁、U₂ 是 arr1、arr2 中不同前缀的数量(最坏各 5×10⁴ × 9),D 是数字最大位数(≤ 9)。实际运行很快,因为每个数的位数有限。
  • 空间复杂度:O(U₁ × D),哈希集合存储 arr1 的所有前缀。

关于前缀树(Trie)解法

这道题也可以用前缀树来做:把 arr1 的所有数按位插入 Trie,然后对 arr2 中的每个数沿 Trie 往下走,走到的最大深度就是最长公共前缀的长度。

思路是对的,但实现上更复杂------你需要把整数拆成逐位数字,还要维护节点和计数。对于这道题来说,哈希集合已经足够简洁高效,Trie 更适合需要支持"前缀查询"更复杂操作的场景(比如动态插入删除、统计前缀数量等)。如果只是查"是否存在",哈希集合是更直觉、更简单的选择。

下面给出 Trie 解法的代码供参考,可以看到和哈希集合解法相比,实现明显更重:

cpp 复制代码
class Solution {
public:
    // Trie 节点:children[0~9] 对应数字 0~9
    struct TrieNode {
        TrieNode* children[10];
        TrieNode() { fill(children, children + 10, nullptr); }
    };

    int longestCommonPrefix(vector<int>& arr1, vector<int>& arr2) {
        TrieNode* root = new TrieNode();

        // 把 arr1 中的数按位插入 Trie
        for (int num : arr1) {
            // 先把数字拆成逐位数字(高位在前)
            vector<int> digits;
            int temp = num;
            while (temp > 0) {
                digits.push_back(temp % 10);
                temp /= 10;
            }
            reverse(digits.begin(), digits.end());

            // 逐位插入
            TrieNode* node = root;
            for (int d : digits) {
                if (!node->children[d])
                    node->children[d] = new TrieNode();
                node = node->children[d];
            }
        }

        // 对 arr2 中的数沿 Trie 走,记录最大深度
        int res = 0;
        for (int num : arr2) {
            vector<int> digits;
            int temp = num;
            while (temp > 0) {
                digits.push_back(temp % 10);
                temp /= 10;
            }
            reverse(digits.begin(), digits.end());

            TrieNode* node = root;
            int depth = 0;
            for (int d : digits) {
                if (!node->children[d]) break;  // 走不通了,停止
                node = node->children[d];
                depth++;
            }
            res = max(res, depth);
        }

        return res;
    }
};

可以看到,Trie 解法需要额外定义节点结构、手动拆位和建树,代码量明显多于哈希集合解法。两种方法的时间复杂度同级,但哈希集合的实现更简洁直观。

易错点

  1. 忘记 num /= 10 时 num 为 0 要停止 。如果 while 条件写成 while (num >= 0) 而不是 while (num > 0),0 会死循环。
  2. 从 arr2 的数检查前缀时,忘记从大到小检查。如果从小到大检查(先查 1 位数前缀),即使匹配了也不能 break,还得继续查更长的前缀,逻辑反而更复杂。从大到小查,匹配即可 break,更高效也更简洁。

相关题目

总结

这道题的核心是把**"两个数的最长公共前缀"转化为"集合查重"问题**:

  • 一个数的所有前缀可以通过 num /= 10 逐次砍末位得到,无需转字符串
  • 把 arr1 的所有前缀存入哈希集合,再对 arr2 的每个数从长到短查,匹配即可 break
  • Trie 也能做,但实现更重,适合需要复杂前缀操作的场景

一句话:当问题可以归结为"是否存在"时,优先考虑哈希集合,而非更重的数据结构。

相关推荐
测试19987 小时前
软件测试 - 单元测试总结
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
杜子不疼.10 小时前
【C++ AI 大模型接入 SDK】 - DeepSeek 模型接入(上)
开发语言·c++·chatgpt
石山代码11 小时前
C++ 内存分区 堆区
java·开发语言·c++
心中有国也有家11 小时前
cann-recipes-infer:昇腾 NPU 推理的“菜谱集合”
经验分享·笔记·学习·算法
绝知此事11 小时前
【算法突围 01】线性结构与哈希表:后端开发的收纳术
java·数据结构·算法·面试·jdk·散列表
碧海银沙音频科技研究院11 小时前
通话AEC与语音识别AEC的软硬回采链路
深度学习·算法·语音识别
csdn_aspnet12 小时前
Python 算法快闪 LeetCode 编号 70 - 爬楼梯
python·算法·leetcode·职场和发展
张小姐的猫13 小时前
【Linux】多线程 —— 线程互斥
linux·运维·服务器·c++