LeetCode:383 Ransom Note

本文介绍了一种字符串匹配算法,用于判断一个字符串是否能通过另一个字符串中的字符构造出来,同时确保源字符串中的每个字符只能被使用一次。文章提供了两种解决方案,并详细解释了其实现过程。

383. Ransom Note

Given an arbitrary ransom note string and another string containing letters from all the magazines,write a function that will return true if the ransom note can be constructed from the magazines ;otherwise,it will return false.
Each letter in the magazine string can only be used once in your ransom note.
Note:
You may assume that both strings contain only lowercase letters.

题目的意思就是给你两个字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,第二个字符里的每个字符只能使用一次。(假设只包含小写字母)

  1. 那么首先想到的就是穷举法了,两层遍历即可,也就是下面的 canConstruct 方法了。

  2. 再思考一下,有个小技巧。这里只要判断两个字符串中每个字母出现的次数即可解决问题。对于某个字母来说,如果第ransom字符串中出现的次数比magazines中出现的次数要多,很显然就直接返回false了。

看一下 canConstructBetter 方法的实现:

import java.util.List;
import java.util.ArrayList;
public class Solution {

    public boolean canConstructBetter(String ransomNote, String magazine) {
        if (ransomNote.equals(magazine) || "".equals(ransomNote)) {
            return true;
        }
        if ("".equals(magazine) && !"".equals(ransomNote)) {
            return false;
        }

        int[] ransomNoteCharCount = new int[26];
        int[] magazineCharCount = new int[26];

        for(int i = 0 ; i < ransomNote.length() ; i++) {
            ransomNoteCharCount[ransomNote.charAt(i)-'a']++;
        }

        for(int i = 0 ; i < magazine.length() ; i++) {
            magazineCharCount[magazine.charAt(i)-'a']++;
        }

        for(int i = 0 ; i < 26 ; i++) {
            if(ransomNoteCharCount[i] > magazineCharCount[i] ) {
                return false;
            }
        }
        return true;
    }

    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.equals(magazine)) {
            return true;
        }
        if ("".equals(ransomNote)) {
            return true;
        }
        List<Character> ransomNodeChars = new ArrayList<>();
        List<Character> magazineChars = new ArrayList<>();  
        for(int i = 0 ; i < ransomNote.length() ; i++) {
            ransomNodeChars.add(ransomNote.charAt(i));
        }
        this.printList(ransomNodeChars);
        for(int j = 0 ; j < magazine.length() ; j++) {
            magazineChars.add(magazine.charAt(j));
        }
        boolean flag = false;
        Character c = null;
        for(int m = 0 ; m < ransomNote.length() ; m++) {
            c = ransomNodeChars.get(m);
            flag = false;
            for(int n = 0 ; n < magazineChars.size() ; n++) {
                if(magazineChars.get(n) == c) {
                    flag = true;
                    magazineChars.remove(n);
                    break;
                }
            }
            if (flag == false) {
                break;
            }
        }
        return flag;
    }
}

THE END

你提供的这段代码实现了 **“赎金信”问题(LeetCode 383)**,逻辑上是正确的,但存在**严重的效率问题和潜在陷阱**。下面我们来详细分析这个实现,并解释它与之前使用 `Counter` 或哈希表方法的本质区别。 --- ### ✅ 你的代码 ```python class Solution: def canConstruct(self, ransomNote: str, magazine: str) -> bool: for letter in ransomNote: if letter not in magazine: return bool(0) else: magazine = magazine.replace(letter, '', 1) return bool(1) ``` 等价于: ```python return False # 而不是 bool(0) ... return True # 而不是 bool(1) ``` 因为 `bool(0)` 是 `False`,`bool(1)` 是 `True`,虽然结果一样,但写法不规范,建议直接写 `return True/False`。 --- ## 🔍 逐行解析 ```python for letter in ransomNote: ``` - 遍历 `ransomNote` 中每个字符。 ```python if letter not in magazine: return False ``` - 如果当前字符不在 `magazine` 中,说明无法构造,返回 `False`。 ```python else: magazine = magazine.replace(letter, '', 1) ``` - 使用 `replace(letter, '', 1)` 删除 `magazine` 中第一个出现的 `letter`。 - 这相当于“消耗”一个可用字符。 ```python return True ``` - 所有字符都成功匹配并删除后,返回 `True`。 ✅ **功能正确性:通过了所有测试用例。** --- ## ⚠️ 为什么说这个方法“效率极低”? ### ❌ 时间复杂度分析 | 操作 | 复杂度 | |------|--------| | `'letter' in magazine` | O(n),字符串搜索 | | `magazine.replace(letter, '', 1)` | O(n),需要创建新字符串 | | 整体循环执行最多 len(ransomNote) 次 | 设为 m | 所以总时间复杂度是: > 💥 **O(m × n)**,其中 m = len(ransomNote), n = len(magazine) 而更优解(如 `Counter` 或字典计数)是: > ✅ **O(m + n)** —— 只需遍历一次两个字符串。 📌 在最坏情况下(比如 `ransomNote = "aaaa..."`, `magazine = "aaaaaaaa..."`),你的方法会超时! --- ### 🧵 示例说明性能问题 ```python ransomNote = "a" * 1000 magazine = "a" * 1000 ``` 你的代码会: 1. 第一次检查 `'a' in magazine` → O(1000) 2. 执行 `replace('a','',1)` → 创建一个长度为 999 的新字符串 3. 第二次再查 `'a' in new_magazine` → O(999) 4. 再 `replace` → 新字符串 998 长度 5. ……重复 1000 次 总共操作量 ≈ 1000 + 999 + 998 + ... + 1 ≈ **50万次操作** 而 `Counter` 方法只需要: - 统计 `magazine`: O(1000) - 遍历 `ransomNote`: O(1000) → 总共 **2000 次操作** 差距巨大! --- ## ✅ 正确高效的替代方案(推荐) ### ✔️ 推荐做法:使用 `Counter` ```python from collections import Counter class Solution: def canConstruct(self, ransomNote: str, magazine: str) -> bool: cnt_note = Counter(ransomNote) cnt_mag = Counter(magazine) return cnt_note <= cnt_mag # 判断是否多重集合包含 ``` 或手动模拟: ```python class Solution: def canConstruct(self, ransomNote: str, magazine: str) -> bool: count = {} # 统计 magazine 字符频次 for c in magazine: count[c] = count.get(c, 0) + 1 # 消耗字符构造 ransomNote for c in ransomNote: if count.get(c, 0) > 0: count[c] -= 1 else: return False return True ``` ✅ 时间复杂度:O(m + n) ✅ 空间复杂度:O(1)(最多26个小写字母)或 O(k) --- ## 🔁 为什么 `replace(letter, '', 1)` 不推荐? | 问题 | 说明 | |------|------| | **不可变字符串** | Python 字符串是不可变的,每次 `replace` 都生成新对象 | | **内存开销大** | 每次复制整个字符串(减一长度) | | **GC压力大** | 产生大量中间字符串,垃圾回收频繁 | | **易被误用** | `replace(x, '', 1)` 只删第一个,但如果不用限制次数会全删! | --- ## ✅ 改进建议(如果你坚持不用 Counter) 可以使用列表模拟可变容器: ```python class Solution: def canConstruct(self, ransomNote: str, magazine: str) -> bool: mag_list = list(magazine) # 转成列表便于删除 for letter in ransomNote: if letter in mag_list: mag_list.remove(letter) # 删除第一次出现的位置 else: return False return True ``` ⚠️ 仍然不推荐!因为 `list.remove()` 是 O(n),整体仍是 O(m×n) --- ## ✅ 总结对比 | 方法 | 时间复杂度 | 是否推荐 | 说明 | |------|------------|----------|------| | `in + replace`(你的方法) | O(m×n) | ❌ 不推荐 | 易超时,面试可能扣分 | | `Counter` 计数 | O(m+n) | ✅ 强烈推荐 | 简洁高效 | | 手动字典计数 | O(m+n) | ✅ 推荐 | 展示底层理解 | | `list.remove()` | O(m×n) | ⚠️ 不推荐 | 比 replace 好一点但依然慢 | --- ### 💡 面试建议: > “我先写一个直观解法帮助理解,然后优化到 O(n) 解法。” ```python # 直观但慢 def canConstruct_slow(...): ... # 最终提交版本 def canConstruct(...): cnt = Counter(magazine) for c in ransomNote: if cnt[c] <= 0: return False cnt[c] -= 1 return True ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值