Unit 1: Basic Data Structures
1. Architectural Foundations of Arrays and Memory Layouts
Contiguous Memory Allocation and Addressing
An array is a linear collection of elements of homogeneous data type placed in contiguous memory locations. Because memory addresses are contiguous, random access to any element by its index executes in strictly time.
For a one-dimensional array starting at base memory address where each element occupies bytes, the physical memory address of the element at 0-indexed position is calculated as:
If the array is 1-indexed (with lower bound ):
Multidimensional Arrays: Row-Major and Column-Major Mapping
Higher-dimensional arrays must be flattened into linear physical RAM addresses:
- Row-Major Order (Standard in C/C++, Python, Java): Consecutive elements of a row reside in consecutive memory addresses. For a 2D array with row dimension and column dimension :
- Column-Major Order (Standard in Fortran, MATLAB, R): Consecutive elements of a column reside in consecutive memory addresses:
Cache locality (spatial locality) heavily impacts execution runtime in competitive programming. Iterating through a row-major 2D array row-by-row (for i: for j: sum += M[i][j]) leverages CPU cache lines (typically 64 bytes), whereas column-by-column iteration induces frequent cache misses, slowing runtime by factors of to on large grids ().
Static vs. Dynamic Arrays and Amortized Analysis
- Static Arrays: Allocated on the call stack (or static/global memory segment) with fixed size determined at compile time (e.g.,
int arr[100005];). Global allocation avoids stack overflow when handling competitive problem constraints up to elements. - Dynamic Arrays (e.g.,
std::vectorin C++,ArrayListin Java): Reside in heap memory. When capacity is exhausted upon an insertion, the dynamic array allocates a new block of double capacity ( geometric progression), copies existing elements, and deallocates the old buffer. - Amortized Analysis: While a single reallocation takes time, inserting items requires total copies. The amortized time per insertion remains . If the capacity increased by a constant additive term instead of geometrically, insertion would degrade to amortized.
2. Array Manipulation and In-Place Algorithmic Patterns
In-Place Array Reversal and Cyclic Rotation
Shifting or rotating an array of size by positions can be executed without auxiliary memory ( extra space):
- The Reversal Algorithm:
To left-rotate by steps ():- Reverse the prefix sub-array:
- Reverse the suffix sub-array:
- Reverse the entire array:
Total time complexity: , space complexity: .
- The Juggling Algorithm (Cyclic Permutation via GCD):
The array is partitioned into independent disjoint cycles. In each cycle, elements are shifted along modular indices until the cycle closes. Each element is read and moved exactly once, executing in time and auxiliary space.
Block-Swap Algorithm
Divides the array into two sub-blocks and . It recursively swaps blocks of equal size until the remaining segments align. Time complexity: , Auxiliary space: .
Prefix Sums and Difference Arrays
- Prefix Sum Array: Constructed as with . Enables constant-time range sum queries over via:
- Difference Array: Defined as and . To increment all elements in range by a value in time:
The modified array is reconstructed by taking prefix sums of in time after processing batch updates.
3. Multidimensional Arrays and Matrix Mechanics
In-Place Matrix Rotation
Rotating an square matrix clockwise by in-place ( auxiliary space):
- Compute the Transpose of the matrix: swap with for all .
- Reverse each row horizontally: swap with for .
To rotate counter-clockwise by , either transpose then reverse each column, or reverse each row then transpose.
Spiral Traversal of a 2D Matrix
Traversing an matrix in spiral order requires maintaining four boundary boundaries: top, bottom, left, and right:
- Traverse from
lefttorightalongtoprow; incrementtop. - Traverse from
toptobottomalongrightcolumn; decrementright. - If
top <= bottom, traverse fromrighttoleftalongbottomrow; decrementbottom. - If
left <= right, traverse frombottomtotopalongleftcolumn; incrementleft.
This executes in time touching each element exactly once.
4. Strings in Competitive Programming
Memory Representation and Encodings
Strings in C/C++ are represented either as null-terminated character arrays (char str[], where \0 designates end-of-string) or dynamically sized objects (std::string).
In Java and Python, string objects are immutable; repeated concatenation using + inside a loop takes time due to reallocation and copying. StringBuilder (in Java) or list joins (in Python) should be utilized to achieve linear aggregation.
Frequency Arrays and Hashing Primitives
For lowercase English alphabet strings (), a frequency vector int freq[26] = {0} tracks character occurrences in time and space. This primitive underpins anagram detection, permutation matching, and palindromic rearrangements (a permutation of a string can form a palindrome if and only if at most one character has an odd frequency count).
5. Stack Data Structure: Principles and Applications
LIFO Principle and Implementation Mechanics
A stack enforces Last-In, First-Out (LIFO) semantics. Key primitives include:
push(x): Inserts element onto the top of the stack ().pop(): Removes the topmost element ().top()/peek(): Inspects the topmost element without removal ().empty()/isEmpty(): Checks if the stack contains zero elements ().
When implemented using fixed arrays, inserting beyond the stack capacity triggers a Stack Overflow. Attempting to pop from an empty stack causes a Stack Underflow.
Monotonic Stacks
A monotonic stack maintains its elements in strictly increasing or strictly decreasing order.
- Next Greater Element (NGE): To find the first element to the right greater than for all :
Iterate from right to left; while the stack is non-empty andtop() <= A[i], pop. If stack is empty, ; otherwise . Push . - Each element is pushed and popped at most once, achieving total time across the array compared to brute-force searches.
- Similar patterns solve the Largest Rectangle in Histogram and Stock Span Problem in optimal time.
Expression Parsing and Evaluation
- Infix to Postfix (Reverse Polish Notation) via Shunting Yard Algorithm:
- Operands are directly appended to the output token list.
- Operators are pushed to an operator stack after popping operators of greater or equal precedence (or strictly greater for right-associative operators like exponentiation
^). - Left parenthesis
(is pushed onto the operator stack; right parenthesis)causes popping until(is encountered.
- Postfix Evaluation:
- Scan tokens left to right.
- Operands are pushed onto an evaluation stack.
- Upon encountering an operator , pop the right operand , pop the left operand , compute , and push back onto the stack.
6. Queues and Deque Structures
FIFO Principle and Ring Buffer (Circular Queue) Implementation
A queue follows First-In, First-Out (FIFO) semantics with enqueue at the rear and dequeue at the front.
In a linear array, successive dequeues waste memory at the front. A Circular Queue wraps pointers modulo capacity :
- Initial state:
front = -1,rear = -1. enqueue(x):rear = (rear + 1) % N,arr[rear] = x.dequeue():front = (front + 1) % N.- Full condition:
(rear + 1) % N == front. - Empty condition:
front == -1.
Double-Ended Queue (Deque) and Sliding Window Extrema
A Deque allows insertion and deletion at both ends (push_front, push_back, pop_front, pop_back).
- Sliding Window Maximum (in an array of size with window size ):
Maintain a monotonic deque of indices storing values in monotonically decreasing order.
For each index :- Remove indices from the front if they fall outside the current window ().
- Pop indices from the back while .
- Push current index to the back.
- The front index represents the maximum element in the window .
Overall complexity: time and space.