解题记录:赎金信

题目描述:

题目来源:力扣(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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
'''

其它解法还有很多,就不在此赘述了(主要是因为哈希表什么的看不太懂)。总之这题做得太累了😂抽空得多学学数据结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值