题目描述
给你两个字符串数组 words1 和 words2。
现在,如果 b 中的每个字母都出现在 a 中,包括重复出现的字母,那么称字符串 b 是字符串 a 的 子集 。
例如,“wrr” 是 “warrior” 的子集,但不是 “world” 的子集。
如果对 words2 中的每一个单词 b,b 都是 a 的子集,那么我们称 words1 中的单词 a 是 通用单词 。
以数组形式返回 words1 中所有的通用单词。你可以按 任意顺序 返回答案。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/word-subsets
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
输入:words1 = ["amazon","apple","facebook","google","leetcode"], words2 = ["e","o"]
输出:["facebook","google","leetcode"]
输入:words1 = ["amazon","apple","facebook","google","leetcode"], words2 = ["e","o"]
输出:["facebook","google","leetcode"]
输入:words1 = ["amazon","apple","facebook","google","leetcode"], words2 = ["e","oo"]
输出:["facebook","google"]
输入:words1 = ["amazon","apple","facebook","google","leetcode"], words2 = ["lo","eo"]
输出:["google","leetcode"]
输入:words1 = ["amazon","apple","facebook","google","leetcode"], words2 = ["ec","oc","ceo"]
输出:["facebook","leetcode"]
题目分析
初级分析(错误答案)
- 首先 看第一第二个例子,我们不难看出,就是在words1中找到是否同时存在words2中的单词,一开始我的想法是用到了正则表达式,将words2中的每一个字母遍历然后用正则返回真假
/**
* @param {string[]} words1
* @param {string[]} words2
* @return {string[]}
*/
var wordSubsets = function(words1, words2) {
let res = []
words1.forEach(element => {
let flag = true
words2.forEach(value => {
let i = value.length - 1
while (i >= 0) {
let reg = RegExp(value[i])
i--
if (!reg.test(element)) flag = false
}
})
if (flag) res.push(element)
});
return res
};
- 首先 第一个foreach是用来遍历待判断单词的数组
- 第二个foreach是用来遍历要存在内容的数组
- 而最里面的循环则是将存在内容分割成一个个的单词,然后通过正则进行判断 通过改变flag真假 决定是否放进新的res数组中去
由结果可知,这种思路是错误的 - 提交结果
可以看到,当words2中存在字母相同时,也就是字母个数重复的时候,测试用例不再通过
而我们的测试结果是
题解(正确答案)
思路:
首先,我们要记录在words2中,单词出现重复的个数
看到案例4 和 案例5 ,发现其实我们无需理会word2每个数组中单词里面字母的顺序,只要存在即可
那我们是不是可以只记录每个单词存在的最大个数,看words1中的哪些单词是否满足然后将其提取出来呢
我们可以想到,先创建26个大小空的元素的数组B,里面记录的是在word2中,每个字母出现的最大次数
//创建一个26大小的全0的数组
let B=new Array(26).fill(0)
//foreach对word2进行遍历
//value指的是每一次遍历word2对应的内容
words2.forEach(value=>{
//创建一个临时的空的数组--用来防止重复的字母个数添加
let tmp=new Array(26).fill(0)
//对value中的每一个字母进行遍历
//w指的是每一次遍历中value对应的单词
for(let w of value){
//idx是用来记录这个字母对应的下标
//比如a的asc2码值就是97 那97-97=0
//所以在数组中表示a出现的次数的位置就是B[0]
let idx=w.charCodeAt()-97
//判断对应位置中,B和tmp对应位置的单词个数是否相同
if(tmp[idx]===B[idx]){
B[idx]++
}
//(下面解释)
tmp[idx]++
}
})
有效的防止了重复添加单词个数的问题
对于tmp功能的解释:
当第一次循环遍历的时候,tmp和B都是空数组,所以B都是正常增加,而第二次循环的时候,B中已经有了第一次循环的数,而tmp则是重新创建,任然是一个空的数组,那么功能到底是什么呢?
举个例子,第一次遍历到是aa,那B[0]=2,
第二次遍历到是aaa,b[0]=2,tmp[0]=0
第一次循环:得到:b[0]=2,tmp[0]=1
判断tmp[idx]===B[idx]不相等,所以B不加一,而tmp加一
第二次循环:得到:b[0]=2,tmp[0]=2
判断tmp[idx]===B[idx]不相等,所以B不加一,而tmp加一
第二次循环:得到:b[0]=3,tmp[0]=3
判断tmp[idx]===B[idx]相等,所以B加一,tmp加一
然后我们再遍历word1中每个单词,每个单词又遍历每个字母,如果该字母的对应位置是数组B中有记录的,则对应下标位置数量减一,如果说,循环结束,得到一个全0的数组,那么就可以说明该单词符合条件
return words1.filter(value=>{
let tmp=B.slice()
//slice() 方法,能够基于当前数组中的一或多个项创建一个新数组。
for(let w of value){
let idx=w.charCodeAt()-97
if(tmp[idx]>0){
tmp[idx]--
}
}
return tmp.every(net=>net===0)
})
every()–用于检测数组所有元素是否都符合指定条件(通过函数提供)。测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
filter()-- 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
slice()–,能够基于当前数组中的一或多个项创建一个新数组。
全部代码
/**
* @param {string[]} words1
* @param {string[]} words2
* @return {string[]}
*/
var wordSubsets = function(words1, words2) {
let B=new Array(26).fill(0)
words2.forEach(value=>{
let tmp=new Array(26).fill(0)
for(let w of value){
let idx=w.charCodeAt()-97
if(tmp[idx]===B[idx]){
B[idx]++
}
tmp[idx]++
}
})
return words1.filter(value=>{
let tmp=B.slice()
for(let w of value){
let idx=w.charCodeAt()-97
if(tmp[idx]>0){
tmp[idx]--
}
}
return tmp.every(net=>net===0)
})
};