Files
cpp-flashcards/org/study_deck_02/toolkit/notes.org
T
2026-06-08 11:38:09 +08:00

5.0 KiB

Notes

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 to int* when passed to functions — size info lost
  • std::array knows its size: freq.size() always returns 26

Value semantics

  • std::array can 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::array never decays; pass by reference explicitly: void f(const std::array<int,26>& a)

Bounds checking (optional)

  • .at(i) throws std::out_of_range on 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::find etc. work directly on std::array
  • C arrays need explicit begin/end: std::all_of(std::begin(arr), std::end(arr), ...)
  • std::array has .begin(), .end(), .size()

Cons of std::array

  • Slightly more verbose syntax: std::array<int, 26> vs int[26]
  • Template parameter required — can't use runtime size (use std::vector then)
  • 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 N in std::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 memset or just zero the stack frame
  • C-style int freq[26]; is uninitialized — garbage values
  • C-style int freq[26] = {}; or int freq[26]{}; also zero-initializes

Size is part of the type

  • std::array<int, 26> and std::array<int, 27> are different types
  • Can't accidentally mix them — type error at compile time
  • C arrays: void f(int a[26]) actually becomes void 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 constexpr arrays, 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-based
  • std::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::array be constexpr?
  • What about std::array vs std::vector for dynamic sizes?
  • How does the optimizer decide to unroll the all_of loop?