题目描述【中等】
给定一个字符串数组
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
的初衷,我将我的思考过程写入,方便日后在回看时,能快速回想起思路
所以我建议大家除了很简单的题之外,还是自己写题解,这样才能真正领悟到题目体现的算法的精髓
光是只看别人题解,自己不手写,是永远不可能掌握算法的!!!
最后,如果我在哪里写的有问题,欢迎指出,共同进步,谢谢阅读~