Loading content...
In the previous page, we established that strings and arrays are structurally identical—both are ordered, indexed sequences. But understanding the relationship in principle is different from seeing it in practice.
This page takes your concrete string knowledge and shows exactly how each technique generalizes. We'll walk through the patterns you've internalized—frequency counting, prefix analysis, two-pointer approaches, sliding windows—and demonstrate that replacing 'character' with 'element' is often the only change required.
By the end of this page, you won't just believe that string knowledge transfers to arrays—you'll see the transfer happening, pattern by pattern, algorithm by algorithm.
By the end of this page, you will understand: (1) How character frequency counting becomes element frequency counting, (2) How substring patterns become subarray patterns, (3) How prefix/suffix analysis extends to prefix sums and cumulative arrays, (4) How string-specific optimizations apply to arrays, and (5) The few places where string and array patterns diverge.
One of the most powerful techniques you learned with strings is frequency counting—counting how often each character appears. This enables anagram detection, character validation, and efficient comparison algorithms.
String Application:
Problem: Determine if two strings are anagrams of each other.
Approach: Count character frequencies in both strings; compare the frequency maps.
Insight: Two strings are anagrams if and only if they have identical character frequency distributions.
Array Generalization:
Problem: Determine if two arrays contain the same elements with the same frequencies (i.e., one is a permutation of the other).
Approach: Count element frequencies in both arrays; compare the frequency maps.
Insight: The exact same algorithm works—just replace 'character' with 'element.'
| String Problem | Array Equivalent | Algorithm Change |
|---|---|---|
| Check if anagram | Check if permutation | None — same frequency comparison |
| Find first non-repeating character | Find first unique element | None — same frequency + order approach |
| Count characters appearing k times | Count elements appearing k times | None — frequency threshold |
| Find most frequent character | Find mode (most frequent element) | None — max frequency extraction |
| Valid window with all characters | Valid window with all required elements | None — same window + frequency logic |
Why the generalization is seamless:
Frequency counting depends only on three capabilities:
Strings and arrays both support iteration. Characters and elements can both be hashed (or, for small ranges, indexed into an array). Comparison is identical. There's no structural reason the algorithm differs.
One nuance worth noting:
With strings, you often know the element space is bounded (e.g., 26 lowercase letters, 128 ASCII characters). This allows using a fixed-size array for counting: int counts[26].
With arrays of arbitrary elements (especially large integers or objects), you typically need a hash map for counting. This doesn't change the algorithm—just the data structure used to store counts.
The core insight transfers perfectly: frequency distributions characterize multisets, whether the elements are characters or any other type.
When element values are bounded to a small range (e.g., numbers 0–100), you can use array-based counting like you did with characters. When the range is large or unbounded, switch to a hash map. The algorithm remains identical—only the counting data structure changes.
With strings, you've worked extensively with prefixes and suffixes—the beginning and ending portions of a string. You've checked whether one string is a prefix of another, found longest common prefixes, and reasoned about how prefixes relate to the full string.
String Application:
Problem: Find the longest common prefix among a list of strings.
Approach: Compare characters at each position across all strings until a mismatch is found.
Key insight: Prefixes form a hierarchy—if "ab" is a common prefix, then "a" is also a common prefix.
Array Generalization — Prefix Sums:
With numeric arrays, the prefix concept evolves into something even more powerful: prefix sums. A prefix sum array stores the cumulative sum up to each index.
Given array: [3, 1, 4, 1, 5, 9]
Prefix sum: [3, 4, 8, 9, 14, 23]
prefix[0] = 3 (sum of elements 0..0)prefix[1] = 3 + 1 = 4 (sum of elements 0..1)prefix[2] = 3 + 1 + 4 = 8 (sum of elements 0..2)Why this matters:
Prefix sums allow you to compute the sum of any subarray in O(1) time:
sum(i, j) = prefix[j] - prefix[i-1]
This is analogous to how substring extraction works—you're retrieving a contiguous portion of the sequence, but now the 'retrieval' computes a numeric property rather than returning elements.
| String Pattern | Array Equivalent | Key Insight |
|---|---|---|
| Check if s1 is prefix of s2 | Check if arr1 elements are prefix of arr2 | Element-by-element comparison |
| Find longest common prefix | Find longest common prefix array | Same algorithm structure |
| Prefix-suffix matching (KMP) | Pattern preprocessing extends | Algorithm generalizes directly |
| Check prefix equality efficiently | Compute prefix sums for range queries | Precomputation enables O(1) queries |
| Rolling hash for substrings | Rolling sum for subarrays | Sliding window with aggregate update |
The generalization principle:
String prefixes are about "what characters appear from the start up to position i." Array prefix sums are about "what aggregate property holds from the start up to position i."
The structural idea is identical: precompute something about all prefixes so that you can answer questions about arbitrary ranges efficiently.
This pattern—precompute prefix information, answer range queries in constant time—appears constantly in algorithm design. You learned the pattern with strings; now you can apply it with numbers, sums, products, XOR values, or any associative operation.
The prefix sum technique exemplifies a broader principle: spend O(n) time preprocessing to enable O(1) range queries later. This time-for-speed tradeoff appears in string algorithms (suffix arrays, Z-arrays) and generalizes to trees (LCA preprocessing), graphs (all-pairs shortest paths), and beyond.
You've extensively used two-pointer techniques on strings—maintaining two indices that move through the sequence according to some logic. Classic string applications include palindrome checking, finding pairs, and partitioning.
String Application — Palindrome Check:
Approach: Place one pointer at the start, one at the end. Compare characters at both pointers. If equal, move pointers inward. If unequal at any point, it's not a palindrome.
Time complexity: O(n/2) = O(n)
Array Generalization — Symmetric Check:
Approach: Identical. Place pointers at start and end. Compare elements. Move inward.
The algorithm requires no modification whatsoever. Palindrome checking on strings is literally symmetric element checking on arrays where elements happen to be characters.
String Application — Two Sum in Sorted Context:
Problem: In a sorted string (sequence of character codes), find two positions where characters' codes sum to a target.
Approach: Start and end pointers. If sum < target, move left pointer right. If sum > target, move right pointer left.
Array Generalization — Two Sum II:
Problem: In a sorted array, find two indices whose elements sum to a target.
Approach: Identical. This is one of the most famous array problems, and the technique is directly borrowed from sequence processing.
String Application — Container with Most Water (Conceptual):
Imagine characters as bar heights. Find two characters forming a container that holds the most 'water.'
Array Generalization — Container with Most Water:
This is the exact LeetCode problem. The algoritm is the same: start and end pointers, move the pointer with the smaller 'height.'
| String Use Case | Array Use Case | Pointer Movement Logic |
|---|---|---|
| Palindrome check | Symmetric array check | Both pointers move inward |
| Valid parentheses balancing | Balanced partition check | Single pass with counter |
| Reverse in place | Reverse array in place | Swap and move inward |
| Merge sorted strings | Merge sorted arrays | Compare heads, advance smaller |
| Remove duplicates | Remove duplicates from sorted array | Fast pointer finds uniques, slow places them |
| Partition characters | Partition array (Dutch flag) | Pointers define regions |
Two-pointer techniques don't care what elements are in the sequence. They care about relative ordering, comparison results, and position management. Whether you're processing characters, numbers, or complex objects, the movement logic remains unchanged.
The sliding window is perhaps the most versatile technique you've learned. With strings, you used it to find substrings with certain properties—longest substring without repeating characters, minimum window containing all characters, and more.
String Application — Longest Substring Without Repeating Characters:
Approach: Expand window by adding characters. When a repeat is detected, shrink from the left until the window is valid again. Track maximum window size.
Array Generalization — Longest Subarray with All Unique Elements:
Approach: Identical. Expand by adding elements. Shrink when a duplicate appears. Track maximum.
The word 'character' becomes 'element.' Everything else stays the same.
String Application — Minimum Window Substring:
Problem: Find the smallest window in string S containing all characters of string T.
Approach: Expand to include required characters. Shrink to minimize while maintaining coverage. Use frequency maps.
Array Generalization — Minimum Window with Required Elements:
Problem: Find the smallest window in array A containing all elements of array B.
Approach: Identical. The algorithm structure—expand, update coverage, shrink, update coverage—doesn't change.
String Application — Fixed-Size Window Analysis:
Problem: Check if any window of size k in a string contains an anagram of pattern P.
Approach: Slide fixed-size window. Maintain character frequency of window. Compare to pattern frequency.
Array Generalization — Fixed-Size Window Maximum:
Problem: Find the maximum (or sum, or any aggregate) in every window of size k.
Approach: Slide fixed-size window. Maintain aggregate as window moves. The maintenance technique varies (deque for max, simple update for sum), but the window structure is identical.
The power of sliding window lies in maintaining an invariant—a property that stays true as the window moves. With strings, invariants are about character presence or frequency. With arrays, invariants are about element presence, sum bounds, or other aggregates. The invariant concept is what transfers, not the specific property.
String pattern matching is a classic algorithmic domain—finding occurrences of a pattern string within a text string. Algorithms like KMP, Rabin-Karp, and Boyer-Moore are optimized for this task.
String Application — Find Pattern in Text:
Problem: Find all occurrences of pattern P in text T.
Naive approach: O(n × m) where you slide the pattern across the text and compare character by character.
Optimized approach: KMP preprocesses the pattern to skip unnecessary comparisons; Rabin-Karp uses rolling hashes.
Array Generalization — Find Subarray Pattern:
Problem: Find all occurrences of a pattern array P within a larger array A.
Approach: The same algorithms apply. KMP works because it depends on element equality, not character-specific properties. Rabin-Karp uses a rolling hash that can generalize to any element type with a suitable hash function.
The generalization is deep:
The KMP failure function computes, for each position, the length of the longest proper prefix that is also a suffix. This computation depends only on element equality—not on elements being characters.
The Rabin-Karp rolling hash uses polynomial hashing: treating the sequence as digits in some base. The formula hash(window) = sum(element[i] * base^(len-1-i)) works for any element type that can be converted to numeric values.
Where string-specific optimizations exist:
Some string algorithms exploit properties unique to text:
But the core algorithmic techniques transfer. When alphabet size is large (many possible element values), you use hash-based rather than table-based approaches—the algorithm structure remains.
Most pattern matching algorithms generalize to arrays with no structural change. Optimizations may need adjustment—a 256-element shift table for ASCII strings becomes a hash map for unbounded element types—but the algorithmic insight is preserved. The generalization is in method, even when implementation details differ.
While most string techniques generalize to arrays, a few aspects of strings are genuinely unique. Understanding these differences prevents over-generalization and keeps your mental model accurate.
1. Lexicographic Ordering
Strings have a natural, well-defined lexicographic order based on character codes. This enables:
Arrays of arbitrary types don't have inherent ordering unless the element type itself is comparable. You can sort arrays of integers naturally, but arrays of objects require explicit comparators.
2. Small, Known Alphabet
Characters typically come from a known, small set (26 letters, 128 ASCII values, or ~65,000 Unicode BMP characters). This enables:
Arrays of integers or other types may have effectively infinite possible values, requiring hash maps instead of arrays for counting and lookup.
3. Textual Semantics
Strings carry semantic meaning in ways arrays don't:
These are properties of text as human communication, not properties of sequences as data structures.
4. Immutability Conventions
Many languages make strings immutable by default (Java, Python, JavaScript). This is partly for safety (strings are often used as keys), partly for optimization (string interning, compile-time literals).
Arrays are typically mutable by default. The same safety considerations that motivate string immutability often don't apply to arrays.
Understanding these differences ensures you don't blindly apply string idioms where they don't fit—like expecting small-alphabet optimizations when processing arrays of large integers.
When moving from strings to arrays, the algorithmic structure transfers, but size assumptions may not. If a string algorithm uses int count[26], the array version might need HashMap<Integer, Integer>. The algorithm is the same; the implementation adapts to the element space.
Let's solidify your understanding with a recognition exercise. For each string problem below, identify the generalized array problem and the key technique that transfers.
Problem 1: Valid Anagram
String version: Given strings s and t, determine if t is an anagram of s.
Array generalization: Given arrays a and b, determine if b is a permutation of a (same elements, same frequencies).
Technique that transfers: Frequency counting with hash map comparison.
Problem 2: Longest Palindromic Substring
String version: Find the longest palindromic substring in a string.
Array generalization: Find the longest symmetric contiguous subarray (mirrors around its center).
Technique that transfers: Expand-around-center approach or dynamic programming on subsequences.
Problem 3: Group Anagrams
String version: Group strings that are anagrams of each other.
Array generalization: Group arrays that are permutations of each other.
Technique that transfers: Use a canonical form (sorted version) as hash key, group by this key.
| String Problem | Array Generalization | Core Technique |
|---|---|---|
| First unique character | First unique element | Frequency count + first-pass scan |
| Reverse words in string | Reverse groups in array | Two-step reversal pattern |
| Longest common prefix | Longest common prefix of arrays | Character-by-character → element-by-element |
| String to integer (parsing) | Array to value (interpretation) | Sequential digit processing |
| Implement strStr() | Find subarray in array | Pattern matching (KMP, naive) |
| Rotate string | Rotate array | Three-reversal trick or cyclic replace |
The recognition skill:
Master algorithmic thinkers don't memorize solutions to individual problems. They recognize problem shapes and apply known techniques. The table above should feel less like six separate mappings and more like six instances of one principle: sequence algorithms apply to all sequences, regardless of element type.
When you see a new array problem, ask: "Have I solved the character version of this?" Often the answer is yes, and the solution transfers immediately.
Each time you solve a string problem, consciously ask how it would apply to arrays. Each time you solve an array problem, consider how it relates to string techniques. This bidirectional connection-making builds a rich mental pattern library that accelerates problem-solving.
Let's consolidate the key insights from this page:
What's next:
Now that we've seen how string ideas generalize, we'll examine what arrays provide that strings cannot. The final page in this module explores the limitations of strings—fixed type, text-only, often immutable—and how arrays address each of these limitations, completing our bridge to Chapter 5.
You now understand how specific string techniques generalize to array problems. Frequency counting, prefix analysis, two-pointer, and sliding window techniques all transfer with minimal modification. This knowledge dramatically reduces the learning curve for arrays—you're not starting fresh; you're extending mastered techniques.