DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • Array

    • 数组
    • 二分查找
    • moveZeros
  • Dynamic-programming

    • 动态规划
  • 刷题指南
  • String

    • 字符串
  • bitwise-operator

    • 位运算符
  • heap
  • history

    • [1014] 最佳观光组合
    • [1022] 从根到叶的二进制数之和
    • [104] 二叉树的最大深度
    • [11] 盛最多水的容器
    • [110] 平衡二叉树
    • [1227] 飞机座位分配概率
    • [129] 求根节点到叶节点数字之和
    • [1306] 跳跃游戏 III
    • [148] 排序链表
    • 155.最小栈
    • [165] 比较版本号
    • 1763. 最长的美好子字符串
    • [1870] 准时到达的列车最小时速
    • [199] 二叉树的右视图
    • [21] 合并两个有序链表
    • 215.数组中的第 k 个最大元素
    • [2306] 公司命名
    • [234] 回文链表
    • [2516] 每种字符至少取 K 个
    • [316] 去除重复字母
    • [3171] 找到按位或最接近 K 的子数组
    • [322] 零钱兑换
    • [41] 缺失的第一个正数
    • [44] 通配符匹配
    • [494] 目标和
    • [509] 斐波那契数
    • [518] 零钱兑换 II
    • [62] 不同路径
    • [676] 实现一个魔法字典
    • 70 爬楼梯
    • [718] 最长重复子数组
    • [78] 子集
    • [82] 删除排序链表中的重复元素 II
    • [871] 最低加油次数
    • [88] 合并两个有序数组
    • [887] 鸡蛋掉落
    • 958.二叉树的完全性检验
    • [98] 验证二叉搜索树
    • [983] 最低票价
    • leetcode practice
    • 约瑟夫问题
    • 移除重复节点
  • linked-list

    • 706. 设计哈希映射
    • 链表
  • stack

    • stack
  • tree

    • Tree Traversal
    • 二叉树的最近公共祖先
    • 二叉树
    • 题目
  • leetCode 刷题
  • 回溯
  • 排序算法

[2306] 公司命名

回溯

超时了

Javascript
var distinctNames = function (ideas) {
    let ans = 0;
    let map = ideas.reduce((map, val, i) => {
        map.set(val, i);
        return map;
    }, new Map());
    backtrack(new Map());
    return ans;

    function backtrack(path) {
        if (path.size === 2) {
            const [a, b] = path.keys();
            if (!map.has(`${b[0]}${a.slice(1)}`) && !map.has(`${a[0]}${b.slice(1)}`)) {
                ans++;
            }
            return;
        }
        for (const name of ideas) {
            if (!path.has(name)) {
                path.set(name, 1);
                backtrack(path);
                path.delete(name);
            }
        }
    }
};

后缀树

details

解释:

  1. 交换 "coffee" 和 "donuts" 的首字母生成的两个名称是 "doffee" 和 "conuts"。这两个名称都不是已有的创意。
  2. 交换 "coffee" 和 "time" 的首字母生成的两个名称是 "toffee" 和 "cime"。"toffee" 是已有的创意,因此这组创意不可行。
  3. 交换 "donuts" 和 "time" 的首字母生成的两个名称是 "tonuts" 和 "dime"。这两个名称都不是已有的创意。

类似地,其他的可行组合可以生成 6 个新名称。

解题思路

要解决这个问题,我们需要考虑如何有效地检测创意的前缀和后缀的交换,且保证生成的新名称不在原有创意中。

步骤:

  1. 按首字母分类:将 ideas 中的创意按首字母进行分类。
  2. 检测可能的交换:遍历每个类别中的创意,将它们的首字母与其他类别中的创意进行交换,形成新的名称。
  3. 检查重复:确保交换后的两个名称都不在原有创意中。
  4. 计数新名称:统计可以生成的新名称的数量。

代码实现

function distinctNames(ideas) {
    // 按首字母分类,将所有创意分成 26 类
    const groups = Array.from({ length: 26 }, () => new Set());

    for (let idea of ideas) {
        const firstLetterIdx = idea.charCodeAt(0) - 'a'.charCodeAt(0);
        groups[firstLetterIdx].add(idea.slice(1)); // 只存储后缀
    }

    let result = 0;

    // 逐对比较不同首字母的组
    for (let i = 0; i < 25; i++) {
        for (let j = i + 1; j < 26; j++) {
            let mutual = 0;

            // 找到两个不同首字母组中的相同后缀
            for (let idea of groups[i]) {
                if (groups[j].has(idea)) {
                    mutual++;
                }
            }

            // 新生成的名字是有效的组合数
            let validPairCount = (groups[i].size - mutual) * (groups[j].size - mutual);
            result += 2 * validPairCount; // 每对可以生成两个新名字
        }
    }

    return result;
}

代码讲解

  1. 按首字母分类:我们创建一个数组 groups,每个元素是一个集合,用于存储所有以该字母开头的创意的后缀。例如,"coffee" 会被存储为 groups[2].add("offee")。
  2. 计算相同后缀:遍历每一对不同首字母的组,查找它们之间相同的后缀,并记录这些相同后缀的数量。
  3. 计算有效的组合:如果组 i 和组 j 有 mutual 个相同的后缀,那么这两个组可以生成的有效新名称的数量是 (groups[i].size - mutual) * (groups[j].size - mutual)。
  4. 结果加倍:因为每一对组可以生成两个新名称,所以我们将有效组合数乘以 2。

时间复杂度

  • 按首字母分类:O(n),其中 n 是 ideas 中的创意数量。
  • 遍历组和计算新名称:由于我们最多有 26 个首字母组,所以遍历每对不同的组需要 O(26 * 26) 次,即常数时间复杂度。每次比较组中的后缀可能需要 O(n) 时间。

因此,总的时间复杂度为 O(n),适用于较大的输入规模。

Last Updated:
Contributors: rosendo
Prev
215.数组中的第 k 个最大元素
Next
[234] 回文链表