5.0 KiB
5.0 KiB
Notes
- 0242. Valid Anagram
DONE 0242. Valid Anagram easy arrays hashing counting
Approach
Frequency counter with fixed-size array (Alpha Frequency Array trick). Early exit on size mismatch. Single pass over both strings.
C++
class Solution {
public:
bool isAnagram(std::string s, std::string t) {
if (s.size() != t.size()) return false;
std::array<int, 26> freq{};
for (int i = 0; i < s.size(); i++) {
freq[s[i] - 'a']++;
freq[t[i] - 'a']--;
}
return std::all_of(freq.begin(), freq.end(), [](int x){ return x == 0; });
}
};
Why std::array over C-style arrays?
Type safety
std::array<int, 26>carries its size in the type system- C-style
int freq[26]decays toint*when passed to functions — size info lost std::arrayknows its size:freq.size()always returns 26
Value semantics
std::arraycan be copied, assigned, returned from functions like any value- C arrays decay to pointers, can't be assigned:
int a[5] = {1,2,3,4,5};
int b[5];
b = a; // ERROR: array type 'int[5]' is not assignable
std::array<int,5> sa = {1,2,3,4,5};
std::array<int,5> sb;
sb = sa; // OK: copies all elements
No pointer decay
- C arrays silently decay to
T*in many contexts — source of bugs std::arraynever decays; pass by reference explicitly:void f(const std::array<int,26>& a)
Bounds checking (optional)
.at(i)throwsstd::out_of_rangeon bad index[i]is unchecked (same as C array) — zero overhead if you want it- C arrays have no checked access option
Works with STL algorithms
std::all_of,std::sort,std::findetc. work directly onstd::array- C arrays need explicit begin/end:
std::all_of(std::begin(arr), std::end(arr), ...) std::arrayhas.begin(),.end(),.size()
Cons of std::array
- Slightly more verbose syntax:
std::array<int, 26>vsint[26] - Template parameter required — can't use runtime size (use
std::vectorthen) - Compile-time dependency: size must be constexpr
Why std::all_of?
Declarative intent
- "All elements satisfy predicate" — reads like English
- vs manual loop:
for (int i=0; i<26; i++) if (freq[i]!=0) return false;— imperative, more mental parsing
Zero overhead
- Compiles to same machine code as hand-written loop
- Optimizer inlines the lambda, unrolls if beneficial
- No performance penalty vs manual loop
Composability
- Can chain with other STL:
std::any_of,std::none_of,std::count_if - Lambda can be arbitrarily complex without changing the loop structure
- Easy to swap predicate without restructuring code
Cons
- Slightly harder to debug (breakpoint inside lambda vs explicit loop)
- For trivial checks, manual loop may be more readable to some
- Requires
<algorithm>include
How does the compiler know 26 is okay?
Compile-time constant
- 26 is a literal — known at compile time
- Template parameter
Ninstd::array<int, N>requires constexpr - Compiler sees: "allocate space for 26 ints right here, right now"
Where does the memory live?
std::array<int, 26> is an aggregate containing int data[26].
- If local variable → stack allocation
- If global/static → data/bss segment
- If member of class → wherever the object lives
Stack allocation is just moving the stack pointer:
sub rsp, 104 ; 26 * 4 bytes = 104 bytes
; freq is now at [rsp], zero-initialized by {}
Zero-initialization
std::array<int, 26> freq{};— value-initialization, all zeros- Compiler may emit
memsetor just zero the stack frame - C-style
int freq[26];is uninitialized — garbage values - C-style
int freq[26] = {};orint freq[26]{};also zero-initializes
Size is part of the type
std::array<int, 26>andstd::array<int, 27>are different types- Can't accidentally mix them — type error at compile time
- C arrays:
void f(int a[26])actually becomesvoid f(int* a)— size lost
Compile-time evaluation
freq.size()is constexpr 26 — no runtime overhead- Loop
for (int i = 0; i < 26; i++)— compiler may unroll entirely - With
constexprarrays, entire computation can happen at compile time
What about std::unordered_map?
When alphabet is not bounded (Unicode, arbitrary keys):
std::map— O(log n) per op, ordered, tree-basedstd::unordered_map— O(1) average, hash table, unordered
For lowercase a-z, neither beats array:
- Array:
freq[c - 'a']— direct index, one memory access - Map/unordered_map: hash + probe/compare — multiple accesses, branches
Questions for later
- How does the stack pointer move for arrays of different sizes?
- What's the alignment requirement for
std::array<int, 26>? - Can
std::arraybeconstexpr? - What about
std::arrayvsstd::vectorfor dynamic sizes? - How does the optimizer decide to unroll the
all_ofloop?