题目
你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。
请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。
单词拼写游戏的规则概述如下:
玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。
可以只使用字母表 letters 中的部分字母,但是每个字母最多被使用一次。
单词表 words 中每个单词只能计分(使用)一次。
根据字母得分情况表score,字母 ‘a’, ‘b’, ‘c’, … , ‘z’ 对应的得分分别为 score[0], score[1], …, score[25]。
本场游戏的「得分」是指:玩家所拼写出的单词集合里包含的所有字母的得分之和。
示例1
输入:words = [“dog”,“cat”,“dad”,“good”], letters = [“a”,“a”,“c”,“d”,“d”,“d”,“g”,“o”,“o”], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
输出:23
解释:
字母得分为 a=1, c=9, d=5, g=3, o=2
使用给定的字母表 letters,我们可以拼写单词 “dad” (5+1+5)和 “good” (3+2+2+5),得分为 23 。
而单词 “dad” 和 “dog” 只能得到 21 分。
示例2
输入:words = [“xxxz”,“ax”,“bx”,“cx”], letters = [“z”,“a”,“b”,“c”,“x”,“x”,“x”], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
输出:27
解释:
字母得分为 a=4, b=4, c=4, x=5, z=10
使用给定的字母表 letters,我们可以组成单词 “ax” (4+5), “bx” (4+5) 和 “cx” (4+5) ,总得分为 27 。
单词 “xxxz” 的得分仅为 25 。
示例3
输入:words = [“leetcode”], letters = [“l”,“e”,“t”,“c”,“o”,“d”], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
输出:0
解释:
字母 “e” 在字母表 letters 中只出现了一次,所以无法组成单词表 words 中的单词。
提示
1 <= words.length <= 14
1 <= words[i].length <= 15
1 <= letters.length <= 100
letters[i].length == 1
score.length == 26
0 <= score[i] <= 10
words[i] 和 letters[i] 只包含小写的英文字母。
代码
func maxScoreWords(words []string, letters []byte, score []int) int {
var res int
var wordsQuanju []string
var scoreQuanju []int
//全局变量
wordsQuanju=words
scoreQuanju=score
//1.先统计字母数量
charNumber:=make([]int,len(score))
for _,value:=range letters{
if charNumber[value-'a']!=0{
charNumber[value-'a']++
}else{
charNumber[value-'a']=1
}
}
dfs(0,0,charNumber,wordsQuanju,scoreQuanju,&res)
return res
}
func dfs(index int,number int,charNumber []int,wordsQuanju []string,scoreQuanju []int,res *int){
if index>=len(wordsQuanju){//递归下标超过word切片里面的个数,递归出口
return
}
//遍历每个字母
var charNumbertmp []int
for _,value:=range charNumber{
charNumbertmp=append(charNumbertmp,value)
}
//charNumbertmp=charNumber//先复制一份完整的没遍历过的单词表
//fmt.Println(charNumbertmp)
sumtmp:=0
var j int
for j=0;j<len(wordsQuanju[index]);j++{
if charNumber[wordsQuanju[index][j]-'a']>0{//说明还有字母没被用
sumtmp=sumtmp+scoreQuanju[wordsQuanju[index][j]-'a']//计算sumtmp,后面取max
charNumber[wordsQuanju[index][j]-'a']--//使用之后个数-1
}else{
dfs(index+1,number,charNumbertmp,wordsQuanju,scoreQuanju,res)//放入一个没遍历过的单词表
return //退出
}
}
if *res<number+sumtmp{//number+sumtmp为这次递归的总和,看是否有比最大的大
*res=number+sumtmp
}
dfs(index+1,number,charNumbertmp,wordsQuanju,scoreQuanju,res)
dfs(index+1,number+sumtmp,charNumber,wordsQuanju,scoreQuanju,res)
}
解题思路
- 首先这里dfs函数的参数较多,这是因为测试用例是不可以用全局变量,其他语言不知道,至少我用Go语言的全局测试用例它就会报错
- wordsQuanju是保存单词的切片,scoreQuanju是保存成绩的切片,不需要保存具体的修改内容,因此不需要传递切片指针,而res是我们需要内容,因此需要传递一个整型指针,这样修改的内容外部才能得到
- 解释完了一些参数之后,可以开始具体的代码思路了,首先就是要设置一个map,用这个map来统计letters中单词的个数,因为题目说了一个单词只能用一次,必须考虑到使用map,map具有很多优良的特性,如保存唯一key,统计使用个数等,这里用map,当他的value=0时,说明用完了,这就可以去下一个单词了
- 每次遍历words中的一个单词,index就是单词的下标,去map下寻找出现的字母是否还有,两种情况,有就计算分数(map要对应减掉使用的单词),没有就index+1递归(使用原先没有减掉的map)。
- 这里有一个深坑!要使用原先没有减掉的map,进行循环遍历之前肯定要复制一份,重点来了。***Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。可以看到charNumber是一个切片,他是一种引用传递,因此如果直接赋值给charNumbertmp,那么两者就会保持引用关系,也就是说charNumber改变charNumbertmp也会随之改变,这样我们递归是不是就失去了意义?因为我们递归要传递的就是原先没有改变的切片,如果一直在变,那么就没办法保证能到达原先的状态。
- 说完了最坑的地方,接下来就是计算我们要的结果res,这个res是一个指针,因此我们只要保证每次dfs的时候它都找判断最大值就行了,因为是指针嘛,你改了其他地方都会改了。
- 之后就是一个背包问题了,为了取得所有的可能,我们index+1之后,考虑放入或者不放入,放入则使用改变后的charNumber,不放入则使用原先的charNumbertmp,这样就可以保证每种可能都遍历到,且res是指针,那么我们的结果肯定是所有可能中的最大值了,这就是我们要的结果了。