# LeetCode 383、赎金信(HJ81. 字符串字符匹配)

# 一、题目描述

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 10^5
  • ransomNotemagazine 由小写英文字母组成

# 二、题目解析

# 三、参考代码

# 方法一

哈希集合遍历解法

# 题目:LC383. 赎金信
# 难度:简单
# 作者:闭着眼睛学数理化
# 算法:哈希集合遍历解法
# 代码看不懂的地方,请直接在群上提问

# 导入collections中的Counter计数器类,使用dict()也可以但是代码就要多一些判断
from collections import Counter

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        # 用两个哈希表保存R和M两个字符串中所有元素出现的频率
        cnt_R, cnt_M = Counter(ransomNote),  Counter(magazine)
        
        # 由于要计算R是否能够被M完全包含,所以我们要遍历R中的字符
        # 遍历cnt_R中的所有字符
        for ch in cnt_R:
            # 如果发现ch在R中的数目大于在M中的数目,则返回False
            if cnt_R[ch] > cnt_M[ch]:
                return False
        
        # 成功退出循环,返回True
        return True

# 方法二

用列表表示哈希表进行统计

# 1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 微信:wzb_3377
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 赎金信(LeetCode 383):https://leetcode.cn/problems/ransom-note/
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {

        // 手动设置哈希表
        // 这里的哈希表的作用是计数器
        // 由于小写字母的个数为 26 个,所以数组大小为 26
        int[] cnt = new int[26];


        // 记录 magazine 里字母出现的次数
        for (char c : magazine.toCharArray()) {
            cnt[c - 'a']++;
        }
        
        // 再用 ransomNote 去验证这个数组是否包含了 ransomNote 所需要的所有字母
        for (char c : ransomNote.toCharArray()) {
            // 在遍历过程中,每遍历一个字母,对应的频次减 1
            cnt[c - 'a']--;

            // 如果发现某个字母的频次小于了 0
            // 说明在 ransomNote 中出现了 magazine 未曾有的字母
            // 即 ransomNote 不能由 magazine 里面的字符构成
            if(cnt[c - 'a'] < 0) {

                // 返回 false 
                return false;
            }
        }

        // 说明可以,返回 true 
        return true;
    }
}

# **2、**C++ 代码

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {

        // 手动设置哈希表
        // 这里的哈希表的作用是计数器
        // 由于小写字母的个数为 26 个,所以数组大小为 26
        vector<int> cnt(26);


        // 记录 magazine 里字母出现的次数
        for (auto & c : magazine) {
            cnt[c - 'a']++;
        }
        
        // 再用 ransomNote 去验证这个数组是否包含了 ransomNote 所需要的所有字母
        for (auto & c : ransomNote) {
            // 在遍历过程中,每遍历一个字母,对应的频次减 1
            cnt[c - 'a']--;

            // 如果发现某个字母的频次小于了 0
            // 说明在 ransomNote 中出现了 magazine 未曾有的字母
            // 即 ransomNote 不能由 magazine 里面的字符构成
            if(cnt[c - 'a'] < 0) {

                // 返回 false 
                return false;
            }
        }

        // 说明可以,返回 true 
        return true;

    }
};

# 3、Python 代码

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        # 手动设置哈希表
        # 这里的哈希表的作用是计数器
        # 由于小写字母的个数为 26 个,所以数组大小为 26
        cnt = [0] * 26

        # 记录 magazine 里字母出现的次数
        for ch in magazine:  
            # 对于数组类型,其下标为 int 类型
            # 可以直接使用 char 类型变量,默认强制转换,存储的就是字母对应的 ASCII 码
            # 比如 ch 是 b 字符,那么 b - a = 1,即 needs[1] 表示记录 b 出现的频次 
            idx = ord(ch) - ord('a')
            cnt[idx] += 1
        

        # 再用 ransomNote 去验证这个数组是否包含了 ransomNote 所需要的所有字母
        for ch in ransomNote:  
            # 在遍历过程中,每遍历一个字母,对应的频次减 1
            cnt[ord(ch) - ord('a')] -= 1

            # 如果发现某个字母的频次小于了 0
            # 说明在 ransomNote 中出现了 magazine 未曾有的字母
            # 即 ransomNote 不能由 magazine 里面的字符构成
            if cnt[ord(ch) - ord('a')] < 0 :

               return False

        # 说明可以,返回 true 
        return True