[20210619]LeetCode每日一题 - 1239. 串联字符串的最大长度

题目描述【中等】

给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s
中的每一个字符都只出现过一次,那么它就是一个可行解。


请返回所有可行解 s 中最长长度。

示例 1:

输入:arr = ["un","iq","ue"]
输出:4
解释:所有可能的串联组合是 "" , "un" , "iq" , "ue" , "uniq""ique",最大长度为 4

示例 2:

输入:arr = ["cha","r","act","ers"]
输出:6
解释:可能的解答有 "chaers""acters"

示例 3:

输入:arr = ["abcdefghijklmnopqrstuvwxyz"]
输出:26

提示:

1 <= arr.length <= 16
1 <= arr[i].length <= 26
arr[i] 中只含有小写英文字母

题目地址:地址


初步分析

首先一看标题,我就想立刻用 动态规划 去解题,因为 动态规划 最基础题也是求字符串之类的最大长度,但细看了题目叙述,发现其实并不是(笑

本题要求组成的 新字串 的所有字母互相不重复,那么我能想到的是先 new 一个新数组 collection ,长度与 arr 相同,然后将 arr 中每个元素用 26 个英文字母为字典表存储到 collection 中,然后再依次遍历 collection ,字典表相互比较,如果没有重复字母,则可以组成新的单词,追加collection 中,最终拿出最长的那个字串,就是答案了。

这只是初步分析,还是有漏洞的


正式分析

初步分析中的漏洞:
  • 仔细看题目描述,没有说 arr 中的单词本身就是没有重复字母的
  • 如何在遍历 collection 时字典表相互比较呢?如果说 26 个英文字母循环一遍进行比较,那么所耗费的时间将会 * arr.length * 26,虽然没试过,但应该是会超时的,那有好的解决方案吗?
对应的解决方案:
  • collection 没有必要以 arr 的长度初始化,在初次遍历 arr 时,就判断每个单词是否有重复字母,如果没有,再 追加collection
  • 类似的我能想到是前不久被训练过的以 二进制 形式进行两数判断的 位运算 ,可以使用:
    • |:或。用来将两个单词合并,得出新的字典表
    • &:和。用来判断两个单词是否有重复字母
    • >>:右移。方便将 二进制 移除指定位数
    • <<:左移。方便将 二进制 增加指定位数

不知道 位运算 具体是如何进行计算的,可以参考这个:地址


具体代码

function maxLength(arr: string[]): number {
    let result = 0
    const collection: number[] = [0]

    arr.forEach(word => {
        const wordLen = word.length
        let dictionary = 0 // 二进制字典表的十进制数
        for (let i = 0; i < wordLen; i++) {
            const chIndex = word.charCodeAt(i) - 'a'.charCodeAt(0) // 得到字母的以 a 为 0 开始的下标
            // dictionary >> chIndex 是为了将二进制字典表降位到需要进行比较的 chIndex 下标
            // 与 1 进行 且( & )运算 ,就是为了看二进制字典表中,第 chIndex 位是否已经标记有对应字母了
            // 如果 !== 0,即有,则此 word 存在字母相同的情况,退出循环,直接找下一个单词即可
            if (((dictionary >> chIndex) & 1) !== 0) {
                dictionary = 0
                break
            }

            // 此时代表 word[i] 与 当前二进制字典表 中没有重复字母
            // 则使用 左移(<<) 与 或( | )运算 快速将 word[i] 放入二进制字典表中
            dictionary |= 1 << chIndex
        }

        if (dictionary === 0) {
            return
        }

        const collectionLen = collection.length
        for (let i = 0; i < collectionLen; i++) {
            // 判断当前 word 的二进制字典表与 collection[i] 是否存在重复字母
            if ((dictionary & collection[i]) === 0) {
                // 不存在则将当前 word 与 collection[i] 结合放入 collection 中成为新的 word
                collection.push(dictionary | collection[i])
                // 维护最长 word
                result = Math.max(result, (dictionary | collection[i]).toString(2).replace(/0/g, '').length)
            }
        }
    })

    return result
};

TS AC,执行用时:156ms, 在所有 TypeScript 提交中击败了 100% 的用户


最后

我一直想吐槽力扣的官方题解,但老是忘记,今天小小吐槽一下:

不知道他们是为了 装自己很厉害的样子 还是 从英文翻译过来词不达意

像我这种水平的算法菜鸟去看时,有时候是看不懂

必须自己先思考怎么做,然后再看题解时,才能恍然大悟

就像是没有思考过程,直接告诉你结果

而且貌似是默认了你掌握了某种算法技能,直接忽略了说明

这也是我写 blog 的初衷,我将我的思考过程写入,方便日后在回看时,能快速回想起思路

所以我建议大家除了很简单的题之外,还是自己写题解,这样才能真正领悟到题目体现的算法的精髓

光是只看别人题解,自己不手写,是永远不可能掌握算法的!!!

最后,如果我在哪里写的有问题,欢迎指出,共同进步,谢谢阅读~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值