题目描述:
题目来源:力扣(LeetCode)
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
解题思路:
我先是按比较传统的解题方法:在第一个字符串里依次提取每一个字符,然后去第二个里面寻找相同的;如果找不到,则无解;
实际上,很快也就写出来了,这里记录一下解题踩到的坑:
第一次提交:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
for i in ransomNote:
for k in magazine:
if i == k:
break
elif k == magazine[-1]:
return False
else:
pass
return True
未通过的测试用例:
输入:ransomNote = "aa", magazine = "ab"
输出:True
预期结果:false
问题很明显:我重复使用了 magazine 中的字符,没有在使用后删除它。
于是,为了删除使用过的字符,一个小时后,我又提交了这样的代码:
第二次提交:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
new = magazine
for ran in ransomNote:
# 初始化数据
x = 0
magazine = new
new = ''
for mag in magazine:
# 如果有,则删除该字母并执行下一轮
if ran == mag:
# Delete charater
for n in magazine:
# 不相等时写入
if n != mag:
# new 是去掉了重复字母的 magazine
new += n
# 相等时判断是否第一次出现
else:
# 第一次出现则跳过
if x == 0:
x = 1
# 否则写入
else:
new += mag
break
elif mag == magazine[-1]:
return False
else:
pass
return True
未通过的测试用例:
输入:ransomNote = "bg", magazine = "efjbdfbdgfjhhaiigfhbaejahgfbbgbjagbddfgdiaigdadhcfcj"
输出:False
预期结果:True
第一眼:好怪的测试用例。
经过分析,错误出现在了对结尾的判断。由于我是直接用字符来遍历的,测试用例中,字符 j 出现在了我们要找的 b 前面,而且这个这个字符恰好也是结尾的字符。
所以这行判断语句:
elif mag == magazine[-1]:
return False
误将此处判断成了字符串的结尾,得出了“遍历这个字符串都没有相同的字符”这一错误的结论
为解决误判结尾的问题,我只好老老实实全都替换成了字符串的下标:
第三次提交:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
new = ''
for r in range(len(ransomNote)):
for m in range(len(magazine)):
if ransomNote[r] != magazine[m]:
if m == len(magazine)-1:
return False
else:
for k in range(len(magazine)):
if k != m :
new += magazine[k]
else:
pass
magazine = new
new = ''
break
return True
这次我全部重写了代码,使用下标是不会误判结尾的。
但是,未通过的测试用例:
输入:ransomNote = "abc", magazine = "ab"
输出:True
预期结果:False
这个没通过很出乎我所预料。仔细检查了几遍,发现问题出在了重复字母的处理上:
我是这样写的:每次使用过一个字符后,就删掉它,字符串的长度就减小了一位。
问题就出在了这儿:如果第一个字符串比第二个字符串要长,那么长出来的字符就不会进入到循环去寻找,直接跳到了结尾(误以为全部找完了,都能找到相同的),然后返回了 True。
第三次提交:
于是,我在第一层循环中加入了这样的判断:
if len(magazine) == 0:
return False
因为每次使用后,都会删除第二个字符串中对应的字符,所以只要长度删除到了 0 时,是一定找不到的,应当返回 False
至此,算法通过了全部的测试用例,完整代码如下:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
new = ''
for r in range(len(ransomNote)):
if len(magazine) == 0:
return False
for m in range(len(magazine)):
if ransomNote[r] != magazine[m]:
if m == len(magazine)-1:
return False
else:
for k in range(len(magazine)):
if k != m :
new += magazine[k]
else:
pass
magazine = new
new = ''
break
return True
这道题花了我两个多小时🤯,但是运行速度并不尽人意:
其它解法
其实在第三次提交的算法中,可以在开头进行长度的判断:因为字符只能用一次,所以。如果第一个字符串的长度大于第二个字符串的长度,则一定是无解的。
if len(ransomNote) > len(magazine):
return False
根据这个原理,我们可以直接统计两个字符串中,出现的各个字符的次数,对比这个数就可以得出结论。
import collections
class Solution:
def canConstruct(self, ransomNote, magazine):
temp = dict(collections.Counter(ransomNote).items()) #分别对ransomeNote和magazine进行计数,并转化为字典
temp2 = dict(collections.Counter(magazine).items())
for index in temp.keys(): #对ransomeNote字典的键进行遍历
if index not in temp2.keys(): #如果键只存在于ransomeNote字典,直接返回False
return False
if temp.get(index) > temp2.get(index): #如果ransomeNote字典键的值大于magazine的值,也就是出现次数大于magazine,返回False
return False
return True
'''
作者:nekoissocute
链接:https://leetcode.cn/problems/ransom-note/solution/xiao-xue-sheng-du-neng-kan-de-dong-de-da-01q8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
'''
其它解法还有很多,就不在此赘述了(主要是因为哈希表什么的看不太懂)。总之这题做得太累了😂抽空得多学学数据结构