Ruby两段Scramble算法实现代码的性能差异原因咨询
Let’s break down why your two Ruby solutions for the scramble problem (checking if s2 can be a substring of a rearranged s1) might have noticeable performance gaps. First, let’s recap what your Version 1 code does, then compare it to common alternative approaches to highlight the key differences.
What Version 1 Is Doing (And Why It’s Fast)
Your first solution uses a classic "frequency count with fixed-size arrays" approach, which is optimized for this exact problem:
- Quick failure check: It immediately returns
falseif s1 is shorter than s2—this avoids wasting time on impossible cases right away. - Fixed-size array counting: It initializes two arrays of length 26 (one for each lowercase letter) to track how many times each character appears in s1 and s2.
- Linear traversal: It loops through s1 once to populate its frequency array, then loops through s2 once to populate its own. Finally, it would check that every character’s count in s2 is less than or equal to its count in s1 (your code cuts off here, but that’s the logical final step).
Performance Pros of Version 1
- Time complexity: O(n + m), where n is the length of s1 and m is the length of s2. This is linear time—you only traverse each string once, no repeats.
- Space complexity: O(1). The arrays are always 26 elements long, regardless of how big your input strings get. No dynamic memory overhead here.
- Fast low-level operations: Array index access in Ruby is extremely efficient. Calculating
s1[x].ord - 97gives a direct index (0 for 'a', 1 for 'b', etc.), so modifying the count is a single, cheap memory operation.
Common Alternative Solutions (And Why They’re Slower)
Most other Ruby solutions for this problem fall into two categories, both of which introduce extra overhead compared to your Version 1:
1. Using String#count for Each Character
A common "concise but slow" approach looks like this:
def scramble(s1, s2) return false if s1.length < s2.length s2.chars.uniq.all? { |char| s1.count(char) >= s2.count(char) } end
Performance Issues Here:
- Repeated traversals: Every call to
countloops through the entire string. If s2 has 26 unique characters, you’re looping through s1 and s2 26 times each—this turns your time complexity into O(k*(n + m)) where k is the number of unique characters in s2. For large strings, this is drastically slower. - No early termination: Even if one character fails the check, you might still end up counting all other characters first.
2. Using Hash Tables for Frequency Counting
Another approach uses Ruby’s tally method (or manual hash counting):
def scramble(s1, s2) return false if s1.length < s2.length char_counts = s1.chars.tally s2.chars.each do |char| return false unless char_counts[char]&.> 0 char_counts[char] -= 1 end true end
Performance Issues Compared to Version 1:
- Hash overhead: While this is still O(n + m) time, hash tables have extra costs that arrays don’t. Ruby has to compute hash values for each character, handle potential hash collisions, and perform dynamic lookups—all of which are slower than direct array index access.
- Dynamic memory: The hash’s size grows with the number of unique characters in s1, which is negligible for small strings but adds tiny overhead compared to the fixed-size array.
Key Takeaways for Performance Differences
The main factors driving performance gaps between your Version 1 and other solutions are:
- Data structure efficiency: Fixed-size arrays beat hash tables for this problem because of direct, low-cost index access.
- Traversal count: Version 1 only touches each character once, while
count-based solutions repeat traversals unnecessarily. - Low-level operation speed: Array modifications are simpler and faster than hash lookups/updates in Ruby.
内容的提问来源于stack exchange,提问作者Donald

