[前端]_一起刷leetcode 1487. 保证文件名唯一

500 阅读4分钟

这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

题目

1487. 保证文件名唯一

给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。

由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数 。

返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。

 

示例 1:

输入: names = ["pes","fifa","gta","pes(2019)"]
输出: ["pes","fifa","gta","pes(2019)"]
解释: 文件系统将会这样创建文件名:
"pes" --> 之前未分配,仍为 "pes"
"fifa" --> 之前未分配,仍为 "fifa"
"gta" --> 之前未分配,仍为 "gta"
"pes(2019)" --> 之前未分配,仍为 "pes(2019)"

示例 2:

输入: names = ["gta","gta(1)","gta","avalon"]
输出: ["gta","gta(1)","gta(2)","avalon"]
解释: 文件系统将会这样创建文件名:
"gta" --> 之前未分配,仍为 "gta"
"gta(1)" --> 之前未分配,仍为 "gta(1)"
"gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)""avalon" --> 之前未分配,仍为 "avalon"

示例 3:

输入: names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"]
输出: ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"]
解释: 当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。

示例 4:

输入: names = ["wano","wano","wano","wano"]
输出: ["wano","wano(1)","wano(2)","wano(3)"]
解释: 每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。

示例 5:

输入: names = ["kaido","kaido(1)","kaido","kaido(1)"]
输出: ["kaido","kaido(1)","kaido(2)","kaido(1)(1)"]
解释: 注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。

 

提示:

  • 1 <= names.length <= 5 * 10^4
  • 1 <= names[i].length <= 20
  • names[i] 由小写英文字母、数字和/或圆括号组成。

思路

  • 这道题目比较容易陷入一个误区,就是我得拆解后面的几个括号,然后提取里面的数字进行一个累加。实际上我们并不需要关心括号里面的数字是多少;

  • 我们只需要用一个set对象来记录当前的文件名之前有没有出现过,如果出现过的话,我们就把它丢进文件名累加的方法里面去,如果没有出现过的话,我们就记录它的存在即可;

  • 然后再说一下文件名累加的方法,我们判断当前的文件名后面加(count)是否存在,如果存在的话count++,不存在则直接修改即可。这个count的值一开始是从1开始,然后我们遍历去判断它是否已经存在即可。

  • 这样子我们就可以通过一轮循环实现我们的结果,当然在count累加的时候,最坏情况是每次都计算一遍数量,所以复杂度上最坏的话是遍历两次嵌套数组O(n²)。

实现

/**
 * @param {string[]} names
 * @return {string[]}
 */
var getFolderNames = function(names) {
    // 记录当前的对象是否出现过
    let set = new Set();

    // 处理添加函数后缀的方法, 遇到需要加的放进来
    function handleAddNumber(index, count = 1) {
        // 当前的索引已经有值了,那么就递增
        while (set.has(`${names[index]}(${count})`)) {
            count++;
        }

        names[index] = `${names[index]}(${count})`;
        set.add(names[index]);
    }

    for (let i = 0; i < names.length; i++) {
        // 判断这个元素是否出现过,出现过的话丢到递增里面去
        if (set.has(names[i])) {
            handleAddNumber(i);
        } else {
            // 没出现的话记录下来即可
            set.add(names[i]);
        }
    }

    return names;
};

优化

  • 上面说到了我们每次找元素添加后缀的时候,每次都得从头遍历一次,那么我们其实可以想着把set方法改成map方法,然后每个元素我们去记录他到了第几位了,这样子我们就可以省去一个查找的过程;

  • 但是是完全不查找吗?其实也不是,因为我们前面记录完后,原本的位置突然直接插进来一个元素,我们是不知道的,除非我们切掉最后一个括号去给它的前缀做累加,但是显然这样子代码看起来就更麻烦了;

  • 所以的话我们还是会检查,只是检查之前每一次都会提前记录一遍,帮我们省下重复操作的那一部分时间。

最终代码

/**
 * @param {string[]} names
 * @return {string[]}
 */
var getFolderNames = function(names) {
    // 记录当前的对象的最后一个索引
    let map = new Map();

    for (let i = 0; i < names.length; i++) {
        // 判断这个元素是否出现过,出现过的话丢到递增里面去
        if (map.has(names[i])) {
            let count = map.get(names[i]) + 1;

            while (map.has(names[i] + `(${count})`)) {
                count++;
            }
            
            map.set(names[i], count);
            names[i] += `(${count})`;
        }
           
        // 记录下当前的元素
        map.set(names[i], 0);
    }

    return names;
};

最终结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。