DSA DAY ONE
DSA DAY ONE
PART I — FOUNDATIONS
Chapter 1: C++ Essentials for DSA
Chapter 2: C++ STL Deep Dive
Chapter 3: Complexity Analysis
PART II — LINEAR DATA STRUCTURES
Chapter 4: Arrays & Strings
Memory Layout & Cache Efficiency1D Array Operations — Rotation, Reversal, Partitioning2D Arrays & Matrix TraversalsPrefix Sums & Difference ArraysKadane's Algorithm — Maximum SubarrayString Manipulation Deep DiveCharacter Frequency & ASCII TricksSubarray vs. Subsequence vs. SubstringSample ProblemsLeetCode Problem Set
Chapter 5: Two Pointers & Sliding Window
Chapter 6: Hashing
Chapter 7: Linked Lists
Chapter 8: Stacks
Chapter 9: Queues & Deques
PART III — TREES
Chapter 10: Binary Trees
Chapter 11: Binary Search Trees (BST)
Chapter 12: Heaps & Priority Queues
Chapter 13: Tries (Prefix Trees)
Chapter 14: Segment Tree & Fenwick Tree
PART IV — ALGORITHMS
Chapter 15: Sorting Algorithms
Chapter 16: Binary Search
Chapter 17: Recursion & Backtracking
Chapter 18: Graphs — Fundamentals & Traversals
Chapter 19: Graph Algorithms — Shortest Paths, MST & Union-Find
Chapter 20: Dynamic Programming
Chapter 21: Greedy Algorithms
Chapter 22: Divide & Conquer
PART V — COMPETITIVE & MATH
Chapter 23: Bit Manipulation
Chapter 24: Math & Number Theory
PART VI — PATTERNS
Chapter 25: Pattern Recognition Mastery
PART VII — SYSTEM DESIGN
Chapter 26: System Design for FAANG Interviews
PART VIII — INTERVIEW STRATEGY
Chapter 27: Cracking FAANG — Strategy & Communication
04

Arrays & Strings


Chapter 4 ·  Part 2: LINEAR DATA STRUCTURES ·  Est. 31 min read

Chapter Goal: Achieve complete mastery over the most fundamental data structures in DSA. Arrays and strings appear in roughly 40% of all FAANG interview problems — often as the substrate for harder techniques. Every pattern introduced here (prefix sum, Kadane's, frequency counting, in-place tricks) reappears throughout graph, DP, and greedy chapters.


4.1 Memory Layout & Cache Efficiency

Arrays Are Contiguous in Memory

An array stores elements in a single contiguous block of memory. This has a profound impact on performance that interviewers sometimes probe.

int arr[5] = {10, 20, 30, 40, 50};

Memory layout:
Address:  1000  1004  1008  1012  1016
Value:    [ 10 ][ 20 ][ 30 ][ 40 ][ 50 ]
           arr[0] arr[1] arr[2] arr[3] arr[4]

Each int = 4 bytes. arr[i] lives at: base_address + i * sizeof(int)

Cache Lines and Spatial Locality

Modern CPUs load memory in cache lines (typically 64 bytes = 16 ints at once). When you access arr[0], the CPU pre-loads arr[0] through arr[15] into cache. Subsequent accesses to nearby elements are cache hits — extremely fast.

// ✅ Row-major access (cache-friendly) — accesses contiguous memory
for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        process(grid[i][j]);    // grid[i][0], grid[i][1], ... are adjacent
 
// ❌ Column-major access (cache-unfriendly) — causes cache misses
for (int j = 0; j < cols; j++)
    for (int i = 0; i < rows; i++)
        process(grid[i][j]);    // grid[0][j], grid[1][j], ... are far apart

Why does this matter in interviews?

  • 2D DP tables: always iterate row by row, not column by column.
  • Matrix problems: understand that grid[row][col] in C++ is row-major.
  • Performance questions: "why is your solution slow?" could be cache thrashing.

Arrays vs. Linked Lists — The Cache Argument

Array Linked List
Memory Contiguous Scattered
Cache performance Excellent (spatial locality) Poor (random pointers)
Access by index O(1) O(n)
Insert/delete (end) O(1) amortized O(1)
Insert/delete (middle) O(n) O(1) given a pointer

4.2 1D Array Operations — Rotation, Reversal, Partitioning

Traversal Patterns

vector<int> arr = {1, 2, 3, 4, 5};
int n = arr.size();
 
// --- Forward ---
for (int i = 0; i < n; i++) process(arr[i]);
 
// --- Backward ---
for (int i = n - 1; i >= 0; i--) process(arr[i]);
 
// --- Two-pointer (from both ends) ---
int l = 0, r = n - 1;
while (l < r) { process(arr[l], arr[r]); l++; r--; }
 
// --- Sliding window (fixed size k) ---
for (int i = k - 1; i < n; i++) {
    // Window: arr[i-k+1] ... arr[i]
}

In-Place Reversal

void reverse(vector<int>& arr, int l, int r) {
    while (l < r) {
        swap(arr[l], arr[r]);
        l++; r--;
    }
}
// Time: O(n), Space: O(1)

Array Rotation — The Triple Reversal Trick

Rotate an array of size n to the right by k steps.

Original:  [1, 2, 3, 4, 5, 6, 7],  k = 3
Expected:  [5, 6, 7, 1, 2, 3, 4]

Step 1: Reverse entire array  → [7, 6, 5, 4, 3, 2, 1]
Step 2: Reverse first k       → [5, 6, 7, 4, 3, 2, 1]
Step 3: Reverse rest (n-k)    → [5, 6, 7, 1, 2, 3, 4]  ✅
void rotate(vector<int>& nums, int k) {
    int n = nums.size();
    k %= n;                          // Handle k > n
    reverse(nums.begin(), nums.end());         // Reverse all
    reverse(nums.begin(), nums.begin() + k);   // Reverse first k
    reverse(nums.begin() + k, nums.end());     // Reverse rest
}
// Time: O(n), Space: O(1)

Dutch National Flag — Three-Way Partition

Partition an array into three sections (values < pivot, == pivot, > pivot) in O(n) and O(1) space. This is the algorithm behind std::sort for equal elements.

// Sort array of 0s, 1s, and 2s in-place (LC 75)
void sortColors(vector<int>& nums) {
    int lo = 0, mid = 0, hi = nums.size() - 1;
 
    while (mid <= hi) {
        if (nums[mid] == 0) {
            swap(nums[lo++], nums[mid++]);  // 0: put at front
        } else if (nums[mid] == 1) {
            mid++;                          // 1: already in position
        } else {
            swap(nums[mid], nums[hi--]);    // 2: put at back (don't advance mid!)
        }
    }
}
// Time: O(n), Space: O(1)

Cyclic Sort — O(n) for Permutation Arrays

When an array contains values in the range [1, n], you can sort it in O(n) time by placing each element at its correct index.

// Sort nums containing 1 to n (or 0 to n-1)
void cyclicSort(vector<int>& nums) {
    int i = 0;
    while (i < (int)nums.size()) {
        int correct = nums[i] - 1;           // 1-indexed: correct index = val - 1
        if (nums[i] != nums[correct]) {
            swap(nums[i], nums[correct]);    // Put nums[i] at its correct position
        } else {
            i++;
        }
    }
}
// Time: O(n), Space: O(1)

Use cyclic sort when: The problem involves an array of values in [1, n] and asks for missing/duplicate numbers.


4.3 2D Arrays & Matrix Traversals

Declaring and Accessing 2D Arrays

// Using vector of vectors (most common in interviews)
int m = 3, n = 4;
vector<vector<int>> grid(m, vector<int>(n, 0));
 
grid[row][col] = 42;             // Access element
int rows = grid.size();          // Number of rows
int cols = grid[0].size();       // Number of columns
 
// Direction arrays for 4-directional movement (BFS/DFS on grids)
int dx[] = {0, 0,  1, -1};      // Row changes
int dy[] = {1, -1, 0,  0};      // Col changes
 
for (int d = 0; d < 4; d++) {
    int nx = row + dx[d];
    int ny = col + dy[d];
    if (nx >= 0 && nx < rows && ny >= 0 && ny < cols) {
        // Valid neighbor at (nx, ny)
    }
}

Spiral Matrix Traversal

Input:                Output (clockwise spiral):
1  2  3               1  2  3  6  9  8  7  4  5
4  5  6
7  8  9
vector<int> spiralOrder(vector<vector<int>>& matrix) {
    vector<int> result;
    if (matrix.empty()) return result;
 
    int top = 0, bottom = matrix.size() - 1;
    int left = 0, right = matrix[0].size() - 1;
 
    while (top <= bottom && left <= right) {
        // → Traverse top row
        for (int col = left; col <= right; col++)
            result.push_back(matrix[top][col]);
        top++;
 
        // ↓ Traverse right column
        for (int row = top; row <= bottom; row++)
            result.push_back(matrix[row][right]);
        right--;
 
        // ← Traverse bottom row (if still valid)
        if (top <= bottom) {
            for (int col = right; col >= left; col--)
                result.push_back(matrix[bottom][col]);
            bottom--;
        }
 
        // ↑ Traverse left column (if still valid)
        if (left <= right) {
            for (int row = bottom; row >= top; row--)
                result.push_back(matrix[row][left]);
            left++;
        }
    }
    return result;
}
// Time: O(m×n), Space: O(1) extra

Transpose and Rotate Matrix

// Transpose: swap matrix[i][j] with matrix[j][i]
void transpose(vector<vector<int>>& mat) {
    int n = mat.size();
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            swap(mat[i][j], mat[j][i]);
}
 
// Rotate 90° clockwise (in-place): Transpose + Reverse each row
void rotate90(vector<vector<int>>& mat) {
    transpose(mat);
    for (auto& row : mat) reverse(row.begin(), row.end());
}
 
// Rotate 90° counter-clockwise: Reverse each row + Transpose
void rotate90CCW(vector<vector<int>>& mat) {
    for (auto& row : mat) reverse(row.begin(), row.end());
    transpose(mat);
}
// Time: O(n²), Space: O(1)

Diagonal Traversal

// Collect elements on each diagonal (top-left to bottom-right)
// Diagonal d: all cells where row - col == d (constant)
// Anti-diagonal d: all cells where row + col == d (constant)
 
// Anti-diagonal traversal
int m = matrix.size(), n = matrix[0].size();
for (int d = 0; d < m + n - 1; d++) {
    for (int row = 0; row < m; row++) {
        int col = d - row;
        if (col >= 0 && col < n) {
            process(matrix[row][col]);
        }
    }
}

4.4 Prefix Sums & Difference Arrays

This is one of the most powerful and frequently tested patterns. Master it completely.

1D Prefix Sum

Idea: Precompute cumulative sums so any range sum query is O(1).

// Build prefix sum array
vector<int> arr = {3, 1, 4, 1, 5, 9, 2, 6};
int n = arr.size();
vector<int> prefix(n + 1, 0);        // prefix[0] = 0 (sentinel)
 
for (int i = 0; i < n; i++) {
    prefix[i + 1] = prefix[i] + arr[i];
}
// prefix = [0, 3, 4, 8, 9, 14, 23, 25, 31]
 
// Range sum query [l, r] (0-indexed, inclusive) in O(1)
auto rangeSum = [&](int l, int r) {
    return prefix[r + 1] - prefix[l];
};
 
cout << rangeSum(1, 4);  // arr[1]+arr[2]+arr[3]+arr[4] = 1+4+1+5 = 11

Key insight: prefix[r+1] - prefix[l] = sum of elements from index l to r.


2D Prefix Sum

// Build 2D prefix sum (sum of rectangle [0,0] to [i,j])
int m = grid.size(), n = grid[0].size();
vector<vector<int>> ps(m + 1, vector<int>(n + 1, 0));
 
for (int i = 1; i <= m; i++)
    for (int j = 1; j <= n; j++)
        ps[i][j] = grid[i-1][j-1]
                 + ps[i-1][j]        // Above
                 + ps[i][j-1]        // Left
                 - ps[i-1][j-1];     // Remove double-counted top-left
 
// Rectangle sum: rows [r1,r2], cols [c1,c2] (0-indexed) in O(1)
auto rectSum = [&](int r1, int c1, int r2, int c2) {
    return ps[r2+1][c2+1]
         - ps[r1][c2+1]
         - ps[r2+1][c1]
         + ps[r1][c1];
};

Difference Array — O(1) Range Updates

Idea: Instead of updating every element in a range (O(n)), record the difference and restore with a prefix sum in O(n) at the end.

// Apply +val to all elements in range [l, r]
vector<int> arr = {0, 0, 0, 0, 0};  // Original array (all zeros)
int n = arr.size();
vector<int> diff(n + 1, 0);          // Difference array
 
auto rangeAdd = [&](int l, int r, int val) {
    diff[l]     += val;    // Start adding val from index l
    diff[r + 1] -= val;    // Stop adding val after index r
};
 
rangeAdd(1, 3, 5);         // Add 5 to indices 1, 2, 3
rangeAdd(2, 4, 3);         // Add 3 to indices 2, 3, 4
 
// Reconstruct array with prefix sum of diff
for (int i = 1; i < n; i++) diff[i] += diff[i - 1];
// diff now contains the actual updated values
 
// Example: arr = [0, 5, 8, 8, 3]  (diff applied)

Prefix Sum + Hash Map — Subarray Sum Pattern

Most important application: Find subarrays with a target sum.

// Count subarrays with sum == k
int subarraySum(vector<int>& nums, int k) {
    unordered_map<int, int> cnt;
    cnt[0] = 1;    // Empty prefix has sum 0
    int prefixSum = 0, result = 0;
 
    for (int x : nums) {
        prefixSum += x;
        // If (prefixSum - k) was seen before, there's a subarray summing to k
        result += cnt[prefixSum - k];
        cnt[prefixSum]++;
    }
    return result;
}
// Time: O(n), Space: O(n)
 
/*
  Visual: prefix[j] - prefix[i] = k
          → prefix[i] = prefix[j] - k
  For each j, count how many i's satisfy this.
*/

4.5 Kadane's Algorithm — Maximum Subarray

Kadane's algorithm finds the contiguous subarray with the largest sum in O(n) time and O(1) space. It's one of the most elegant DP transitions in existence.

Core Algorithm

Intuition: At each index, we ask:
  "Is it better to extend the current subarray, or start fresh here?"

  If currentSum + arr[i] < arr[i]:  start fresh (currentSum was negative drag)
  Otherwise:                          extend
int maxSubArray(vector<int>& nums) {
    int maxSum = nums[0];
    int currSum = nums[0];
 
    for (int i = 1; i < (int)nums.size(); i++) {
        currSum = max(nums[i], currSum + nums[i]);  // Extend or restart
        maxSum  = max(maxSum, currSum);             // Track global maximum
    }
    return maxSum;
}
// Time: O(n), Space: O(1)
 
// One-liner version:
int maxSubArray(vector<int>& nums) {
    int cur = nums[0], best = nums[0];
    for (int i = 1; i < (int)nums.size(); i++)
        cur = max(nums[i], cur + nums[i]), best = max(best, cur);
    return best;
}

With Subarray Indices (Tracking Start and End)

pair<int, pair<int,int>> maxSubArrayWithIndices(vector<int>& nums) {
    int maxSum = nums[0], currSum = nums[0];
    int start = 0, end = 0, tempStart = 0;
 
    for (int i = 1; i < (int)nums.size(); i++) {
        if (nums[i] > currSum + nums[i]) {
            currSum = nums[i];
            tempStart = i;          // Potential new start
        } else {
            currSum += nums[i];
        }
        if (currSum > maxSum) {
            maxSum = currSum;
            start = tempStart;
            end = i;
        }
    }
    return {maxSum, {start, end}};
}

Variant: Minimum Subarray Sum

// Just flip max → min and the comparison
int minSubArray(vector<int>& nums) {
    int minSum = nums[0], currSum = nums[0];
    for (int i = 1; i < (int)nums.size(); i++) {
        currSum = min(nums[i], currSum + nums[i]);
        minSum  = min(minSum, currSum);
    }
    return minSum;
}

Variant: Maximum Product Subarray (LC 152)

Products are tricky because a negative × negative = positive. Track both max AND min at each position.

int maxProduct(vector<int>& nums) {
    int maxProd = nums[0], minProd = nums[0], result = nums[0];
 
    for (int i = 1; i < (int)nums.size(); i++) {
        if (nums[i] < 0) swap(maxProd, minProd);  // Negative flips max and min
        maxProd = max(nums[i], maxProd * nums[i]);
        minProd = min(nums[i], minProd * nums[i]);
        result  = max(result, maxProd);
    }
    return result;
}
// Time: O(n), Space: O(1)

4.6 String Manipulation Deep Dive

Essential String Patterns

// --- Check if palindrome (ignoring non-alphanumeric) ---
bool isPalindrome(string s) {
    int l = 0, r = s.size() - 1;
    while (l < r) {
        while (l < r && !isalnum(s[l])) l++;  // Skip non-alphanumeric
        while (l < r && !isalnum(s[r])) r--;
        if (tolower(s[l]) != tolower(s[r])) return false;
        l++; r--;
    }
    return true;
}
 
// --- Reverse words in a string ---
// "  the sky is   blue  " → "blue is sky the"
string reverseWords(string s) {
    string result, word;
    stringstream ss(s);
    vector<string> words;
    while (ss >> word) words.push_back(word);
    for (int i = words.size() - 1; i >= 0; i--) {
        result += words[i];
        if (i > 0) result += " ";
    }
    return result;
}
 
// --- String compression (run-length encoding) ---
// "aaabbc" → "a3b2c1"
string compress(const string& s) {
    string result;
    int i = 0;
    while (i < (int)s.size()) {
        char c = s[i];
        int count = 0;
        while (i < (int)s.size() && s[i] == c) { count++; i++; }
        result += c;
        result += to_string(count);
    }
    return result;
}
 
// --- Longest Common Prefix ---
string longestCommonPrefix(vector<string>& strs) {
    if (strs.empty()) return "";
    string prefix = strs[0];
    for (int i = 1; i < (int)strs.size(); i++) {
        while (strs[i].find(prefix) != 0) {  // While prefix not at position 0
            prefix.pop_back();
            if (prefix.empty()) return "";
        }
    }
    return prefix;
}
// Time: O(S) where S = total characters, Space: O(1)

String Matching — Naive and KMP (Concept)

// --- Naive string search: O(n*m) ---
int naiveSearch(const string& text, const string& pattern) {
    int n = text.size(), m = pattern.size();
    for (int i = 0; i <= n - m; i++) {
        bool match = true;
        for (int j = 0; j < m; j++) {
            if (text[i + j] != pattern[j]) { match = false; break; }
        }
        if (match) return i;
    }
    return -1;
}
 
// --- STL string::find: uses optimized search internally ---
size_t pos = text.find(pattern);  // npos if not found
if (pos != string::npos) cout << "Found at " << pos;
 
// --- KMP Concept (for reference): O(n + m) ---
// KMP builds a "failure function" (lps array) that avoids re-comparing
// characters we already know match. Critical for pattern-matching problems.
// Full implementation in Chapter 6 (Hashing).

String Tokenization (Critical for Input Parsing)

// Split by single character delimiter
vector<string> split(const string& s, char delim) {
    vector<string> tokens;
    string token;
    istringstream stream(s);
    while (getline(stream, token, delim)) {
        if (!token.empty()) tokens.push_back(token);
    }
    return tokens;
}
 
// Split by whitespace (handles multiple spaces)
vector<string> splitBySpace(const string& s) {
    vector<string> tokens;
    string token;
    istringstream stream(s);
    while (stream >> token) tokens.push_back(token);
    return tokens;
}
 
// Join vector of strings
string join(const vector<string>& v, const string& sep) {
    string result;
    for (int i = 0; i < (int)v.size(); i++) {
        result += v[i];
        if (i < (int)v.size() - 1) result += sep;
    }
    return result;
}

4.7 Character Frequency & ASCII Tricks

Fixed-Size Frequency Array (26 letters) — O(1) Space

For problems involving only lowercase letters, a int freq[26] is much faster than unordered_map<char,int>.

// Count character frequency
string s = "interview";
int freq[26] = {};                  // Zero-initialized
for (char c : s) freq[c - 'a']++;  // 'a'=0, 'b'=1, ..., 'z'=25
 
// Check if all characters appear the same number of times
bool allSameFreq = true;
int expected = freq['a' - 'a'];
for (int i = 0; i < 26; i++) {
    if (freq[i] != 0 && freq[i] != expected) { allSameFreq = false; break; }
}
 
// --- Anagram check using frequency array ---
bool isAnagram(const string& s, const string& t) {
    if (s.size() != t.size()) return false;
    int freq[26] = {};
    for (char c : s) freq[c - 'a']++;
    for (char c : t) {
        if (--freq[c - 'a'] < 0) return false;
    }
    return true;
}
// Time: O(n), Space: O(1) — the freq array is always size 26
 
// --- For uppercase + lowercase ---
int freq128[128] = {};  // All ASCII characters
for (char c : s) freq128[(int)c]++;

Important ASCII Values to Memorize

'0' = 48,  '9' = 57   → digit range
'A' = 65,  'Z' = 90   → uppercase range
'a' = 97,  'z' = 122  → lowercase range
' ' = 32               → space

// Digit char to int:      c - '0'   (for '0'..'9')
// Lowercase to index:     c - 'a'   (for 'a'..'z')
// Uppercase to index:     c - 'A'   (for 'A'..'Z')
// Lowercase ↔ Uppercase:  c ^ 32    (XOR trick — toggles bit 5)

Sliding Window with Frequency Array (Preview)

// Count distinct characters in every window of size k
string s = "aababcabcd";
int k = 3;
int freq[26] = {};
int distinct = 0;
 
// Initialize first window
for (int i = 0; i < k; i++) {
    if (freq[s[i] - 'a']++ == 0) distinct++;
}
cout << distinct << " ";  // Window [0, k-1]
 
// Slide the window
for (int i = k; i < (int)s.size(); i++) {
    // Add new character
    if (freq[s[i] - 'a']++ == 0) distinct++;
    // Remove old character
    if (--freq[s[i - k] - 'a'] == 0) distinct--;
    cout << distinct << " ";
}

4.8 Subarray vs. Subsequence vs. Substring

This distinction is critical — getting it wrong means solving the wrong problem.

Term Definition Contiguous? Order preserved? Example from "abcde"
Subarray Contiguous elements of an array Yes Yes [b, c, d]
Substring Contiguous characters of a string Yes Yes "bcd"
Subsequence Non-contiguous, relative order preserved No Yes "ace", "bd"
Subset Any elements, no order requirement No No {a, c, e}

Why This Matters — Completely Different Approaches

// SUBARRAY problems: usually use sliding window, prefix sum, or Kadane's
// Example: "Find maximum subarray sum" → Kadane's O(n)
 
// SUBSEQUENCE problems: usually use DP (2D table for two sequences)
// Example: "Find length of longest common subsequence" → DP O(n*m)
 
// SUBSTRING problems: sliding window, two pointers, or hashing
// Example: "Find longest substring without repeating chars" → Sliding window O(n)
 
// Quick identification:
// "contiguous" or "subarray" → sliding window / prefix sum
// "not necessarily contiguous" or "subsequence" → DP

Counting Subarrays

// Total subarrays of an array of size n = n*(n+1)/2
// Subarrays starting at i: n-i (from i to i, i to i+1, ..., i to n-1)
// Total = sum from i=0 to n-1 of (n-i) = n + (n-1) + ... + 1 = n(n+1)/2
 
int n = 5;
long long totalSubarrays = (long long)n * (n + 1) / 2;  // 15 for n=5
 
// Enumerate all subarrays
for (int i = 0; i < n; i++) {
    for (int j = i; j < n; j++) {
        // Process subarray arr[i..j]
    }
}

4.9 Sample Problems


Problem 1 — Best Time to Buy and Sell Stock (Easy)

LeetCode 121

Given an array prices where prices[i] is the price on day i, find the maximum profit from buying on one day and selling on a later day.

Approach: Track the minimum price seen so far (best buy). At each day, compute profit if we sell today. The answer is the maximum such profit.

int maxProfit(vector<int>& prices) {
    int minPrice = INT_MAX;
    int maxProfit = 0;
 
    for (int price : prices) {
        minPrice  = min(minPrice, price);       // Best day to buy (so far)
        maxProfit = max(maxProfit, price - minPrice); // Best profit selling today
    }
    return maxProfit;
}
 
/*
  prices = [7, 1, 5, 3, 6, 4]
  minPrice: 7  1  1  1  1  1
  profit:   0  0  4  2  5  3
  Answer: 5 (buy at 1, sell at 6)
*/
// Time: O(n), Space: O(1)

Problem 2 — Contains Duplicate (Easy)

LeetCode 217

Return true if any value appears at least twice in the array.

Approach: Insert into an unordered_set. If insertion fails (element already exists), return true.

bool containsDuplicate(vector<int>& nums) {
    unordered_set<int> seen;
    for (int x : nums) {
        if (!seen.insert(x).second)  // insert returns {iterator, bool}
            return true;
    }
    return false;
}
// Time: O(n) average, Space: O(n)
 
// Alternative using sort: O(n log n) time, O(1) extra space
bool containsDuplicate(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    for (int i = 1; i < (int)nums.size(); i++)
        if (nums[i] == nums[i - 1]) return true;
    return false;
}

Problem 3 — Maximum Subarray (Medium)

LeetCode 53

Find the contiguous subarray with the largest sum and return its sum.

Approach: Kadane's algorithm — extend the current subarray or restart.

int maxSubArray(vector<int>& nums) {
    int currSum = nums[0];
    int maxSum  = nums[0];
 
    for (int i = 1; i < (int)nums.size(); i++) {
        // Should I extend the current subarray, or start fresh at nums[i]?
        currSum = max(nums[i], currSum + nums[i]);
        maxSum  = max(maxSum, currSum);
    }
    return maxSum;
}
 
/*
  nums    = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
  currSum = -2  1  -2  4   3  5  6   1  5
  maxSum  = -2  1   1  4   4  5  6   6  6
  Answer: 6 (subarray [4,-1,2,1])
*/
// Time: O(n), Space: O(1)

Problem 4 — Product of Array Except Self (Medium)

LeetCode 238

Return an array output where output[i] equals the product of all elements of nums except nums[i]. Solve WITHOUT division in O(n).

Approach: For each position, the answer is (product of all elements to the left) × (product of all elements to the right). Build a prefix product pass and a suffix product pass.

vector<int> productExceptSelf(vector<int>& nums) {
    int n = nums.size();
    vector<int> result(n, 1);
 
    // Pass 1: result[i] = product of all elements to the LEFT of i
    int left = 1;
    for (int i = 0; i < n; i++) {
        result[i] = left;
        left *= nums[i];
    }
 
    // Pass 2: multiply result[i] by product of all elements to the RIGHT of i
    int right = 1;
    for (int i = n - 1; i >= 0; i--) {
        result[i] *= right;
        right *= nums[i];
    }
    return result;
}
 
/*
  nums   =  [1,  2,  3,  4]
  left   =  [1,  1,  2,  6]  (prefix products, shifted by 1)
  right  =  [24, 12, 4,  1]  (suffix products, shifted by 1)
  result =  [24, 12, 8,  6]
*/
// Time: O(n), Space: O(1) extra (output array doesn't count)

Problem 5 — Subarray Sum Equals K (Medium)

LeetCode 560

Count the number of contiguous subarrays whose sum equals k.

Approach: Prefix sum + hash map. sum[j] - sum[i] = k ↔ sum[i] = sum[j] - k. For each position j, count how many previous prefix sums equal prefixSum - k.

int subarraySum(vector<int>& nums, int k) {
    unordered_map<int, int> prefixCount;
    prefixCount[0] = 1;   // Empty prefix (sum = 0) appears once
 
    int prefixSum = 0, count = 0;
 
    for (int x : nums) {
        prefixSum += x;
        // How many past prefix sums equal (prefixSum - k)?
        count += prefixCount[prefixSum - k];
        prefixCount[prefixSum]++;
    }
    return count;
}
 
/*
  nums = [1, 1, 1], k = 2
  Step 1: prefixSum=1, look for (1-2)=-1 → 0, count=0, map={0:1, 1:1}
  Step 2: prefixSum=2, look for (2-2)=0  → 1, count=1, map={0:1, 1:1, 2:1}
  Step 3: prefixSum=3, look for (3-2)=1  → 1, count=2, map={0:1, 1:1, 2:1, 3:1}
  Answer: 2  (subarrays [1,1] at positions 0-1 and 1-2)
*/
// Time: O(n), Space: O(n)

Problem 6 — Rotate Array (Medium)

LeetCode 189

Rotate the array to the right by k steps in-place.

Approach: Triple reversal trick — reverse the whole array, then reverse the first k elements, then reverse the rest.

void rotate(vector<int>& nums, int k) {
    int n = nums.size();
    k %= n;  // k could be larger than n
 
    // Helper: reverse in-place from l to r
    auto rev = [&](int l, int r) {
        while (l < r) swap(nums[l++], nums[r--]);
    };
 
    rev(0, n - 1);     // Reverse all:   [5,6,7,1,2,3,4] → [4,3,2,1,7,6,5]
    rev(0, k - 1);     // Reverse [0,k): [5,6,7,1,2,3,4]
    rev(k, n - 1);     // Reverse [k,n): [5,6,7,1,2,3,4]
}
 
/*
  nums = [1,2,3,4,5,6,7], k = 3
  After rev(0,6): [7,6,5,4,3,2,1]
  After rev(0,2): [5,6,7,4,3,2,1]
  After rev(3,6): [5,6,7,1,2,3,4]  ✅
*/
// Time: O(n), Space: O(1)

Problem 7 — Trapping Rain Water (Hard)

LeetCode 42

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

Approach: For each position i, water trapped = min(maxLeft[i], maxRight[i]) - height[i]. Pre-compute prefix max from the left and suffix max from the right.

int trap(vector<int>& height) {
    int n = height.size();
    vector<int> maxLeft(n), maxRight(n);
 
    // maxLeft[i]: tallest bar to the LEFT of and including i
    maxLeft[0] = height[0];
    for (int i = 1; i < n; i++)
        maxLeft[i] = max(maxLeft[i-1], height[i]);
 
    // maxRight[i]: tallest bar to the RIGHT of and including i
    maxRight[n-1] = height[n-1];
    for (int i = n - 2; i >= 0; i--)
        maxRight[i] = max(maxRight[i+1], height[i]);
 
    // Water at position i = min(maxLeft, maxRight) - height[i]
    int water = 0;
    for (int i = 0; i < n; i++)
        water += min(maxLeft[i], maxRight[i]) - height[i];
 
    return water;
}
 
/*
  height =   [0,1,0,2,1,0,1,3,2,1,2,1]
  maxLeft =  [0,1,1,2,2,2,2,3,3,3,3,3]
  maxRight = [3,3,3,3,3,3,3,3,2,2,2,1]
  water at each: 0,0,1,0,1,2,1,0,0,1,0,0 → total = 6
*/
// Time: O(n), Space: O(n)
 
// ─── Two-Pointer Optimization: O(1) Space ───────────────────────────────────
int trap_twoPointer(vector<int>& height) {
    int l = 0, r = height.size() - 1;
    int maxL = 0, maxR = 0, water = 0;
 
    while (l < r) {
        if (height[l] < height[r]) {
            maxL = max(maxL, height[l]);
            water += maxL - height[l];
            l++;
        } else {
            maxR = max(maxR, height[r]);
            water += maxR - height[r];
            r--;
        }
    }
    return water;
}
// Time: O(n), Space: O(1)

Problem 8 — Find All Duplicates in Array (Medium)

LeetCode 442

Given an integer array of length n where all integers are in [1, n], find all elements that appear twice. Use O(1) extra space.

Approach: Use the index as a hash. For each number x, negate the value at index |x| - 1. If the value there is already negative, |x| is a duplicate.

vector<int> findDuplicates(vector<int>& nums) {
    vector<int> result;
 
    for (int i = 0; i < (int)nums.size(); i++) {
        int idx = abs(nums[i]) - 1;    // Map value to index (1-indexed → 0-indexed)
 
        if (nums[idx] < 0) {
            result.push_back(abs(nums[i]));  // Already visited → duplicate!
        } else {
            nums[idx] = -nums[idx];          // Mark as visited by negating
        }
    }
    return result;
}
 
/*
  nums = [4,3,2,7,8,2,3,1]
  i=0: idx=3, nums[3]=7>0, negate → nums=[4,3,2,-7,8,2,3,1]
  i=1: idx=2, nums[2]=2>0, negate → nums=[4,3,-2,-7,8,2,3,1]
  i=2: idx=1, nums[1]=3>0, negate → nums=[4,-3,-2,-7,8,2,3,1]
  i=3: idx=6, nums[6]=3>0, negate → nums=[4,-3,-2,-7,8,2,-3,1]
  i=4: idx=7, nums[7]=1>0, negate → nums=[4,-3,-2,-7,8,2,-3,-1]
  i=5: idx=1, nums[1]=-3<0, DUPLICATE: 2 ✅
  i=6: idx=2, nums[2]=-2<0, DUPLICATE: 3 ✅
*/
// Time: O(n), Space: O(1) extra

Problem 9 — Valid Anagram (Easy)

LeetCode 242

Given two strings s and t, return true if t is an anagram of s.

Approach: Count character frequencies using a 26-element array.

bool isAnagram(string s, string t) {
    if (s.size() != t.size()) return false;
 
    int freq[26] = {};
    for (char c : s) freq[c - 'a']++;
    for (char c : t) {
        if (--freq[c - 'a'] < 0) return false;  // t has more of this char
    }
    return true;
}
// Time: O(n), Space: O(1) — fixed 26-element array
 
// For Unicode / any characters: use unordered_map instead

Problem 10 — Maximum Product Subarray (Medium)

LeetCode 152

Find the contiguous subarray with the largest product.

Approach: Track both max and min products. Negative × negative = positive, so a previous minimum can become the next maximum.

int maxProduct(vector<int>& nums) {
    int maxProd = nums[0];
    int minProd = nums[0];
    int result  = nums[0];
 
    for (int i = 1; i < (int)nums.size(); i++) {
        // If nums[i] < 0, max and min swap roles
        if (nums[i] < 0) swap(maxProd, minProd);
 
        maxProd = max(nums[i], maxProd * nums[i]);
        minProd = min(nums[i], minProd * nums[i]);
        result  = max(result, maxProd);
    }
    return result;
}
 
/*
  nums     = [2, 3, -2, 4]
  maxProd  = 2  6  -2  4
  minProd  = 2  3 -12  -8  (tracks minimum for future negatives)
  result   = 2  6   6   6
  Answer: 6 (subarray [2, 3])
*/
// Time: O(n), Space: O(1)

Problem 11 — Spiral Matrix (Medium)

LeetCode 54

Given an m×n matrix, return all elements in spiral order.

Approach: Maintain four boundaries (top, bottom, left, right) and peel off one layer at a time.

vector<int> spiralOrder(vector<vector<int>>& matrix) {
    vector<int> result;
    int top = 0, bottom = matrix.size() - 1;
    int left = 0, right = matrix[0].size() - 1;
 
    while (top <= bottom && left <= right) {
        for (int col = left;   col <= right;  col++) result.push_back(matrix[top][col]);
        top++;
        for (int row = top;    row <= bottom; row++) result.push_back(matrix[row][right]);
        right--;
        if (top <= bottom) {
            for (int col = right; col >= left;  col--) result.push_back(matrix[bottom][col]);
            bottom--;
        }
        if (left <= right) {
            for (int row = bottom; row >= top;  row--) result.push_back(matrix[row][left]);
            left++;
        }
    }
    return result;
}
// Time: O(m×n), Space: O(1) extra

Problem 12 — Range Sum Query 2D (Medium)

LeetCode 304

Given a matrix, handle multiple sum queries for any sub-rectangle in O(1).

Approach: Precompute a 2D prefix sum table. Each query is then O(1).

class NumMatrix {
    vector<vector<int>> ps;  // ps[i][j] = sum of rectangle [0,0] to [i-1,j-1]
public:
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        ps.assign(m + 1, vector<int>(n + 1, 0));
 
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                ps[i][j] = matrix[i-1][j-1]
                         + ps[i-1][j]
                         + ps[i][j-1]
                         - ps[i-1][j-1];
    }
 
    // Sum of rectangle: rows [r1..r2], cols [c1..c2] (0-indexed)
    int sumRegion(int r1, int c1, int r2, int c2) {
        return ps[r2+1][c2+1]
             - ps[r1][c2+1]
             - ps[r2+1][c1]
             + ps[r1][c1];
    }
};
// Build: O(m×n), Query: O(1), Space: O(m×n)

4.10 LeetCode Problem Set

Practice these in order — difficulty escalates gradually.

# Problem Difficulty Core Technique Link
1 Two Sum Easy Hash map lookup LC 1
2 Best Time to Buy and Sell Stock Easy Running min scan LC 121
3 Valid Anagram Easy Frequency array LC 242
4 Maximum Subarray Medium Kadane's algorithm LC 53
5 Product of Array Except Self Medium Prefix & suffix product LC 238
6 Spiral Matrix Medium Boundary simulation LC 54
7 Subarray Sum Equals K Medium Prefix sum + hash map LC 560
8 Trapping Rain Water Hard Prefix/suffix max LC 42

Suggested order: Solve #1 and #2 on Day 1 to get warmed up. #4 (Kadane's) and #5 (Prefix product) are interview staples — master both solutions. #7 is the hardest conceptually among mediums. #8 is a classic hard problem that every serious candidate must know cold.


← PreviousChapter 3: Complexity AnalysisNext →Chapter 5: Two Pointers & Sliding Window
Chapter 4 — Progress
0 / 33 COMPLETE
Memory & Layout
  • Explain why row-major access is faster than column-major for 2D arrays
  • Know that `vector<vector<int>>` is heap-allocated; large static 2D arrays go global
  • Understand spatial locality and why arrays outperform linked lists in practice
1D Array Techniques
  • Implement in-place reversal using two pointers
  • Implement right rotation by k using the triple reversal trick (O(n) time, O(1) space)
  • Implement Dutch National Flag three-way partition
  • Use cyclic sort to find missing/duplicate numbers in [1..n] arrays
2D Array & Matrix
  • Implement spiral matrix traversal with four shrinking boundaries
  • Implement in-place 90° clockwise rotation: transpose then reverse rows
  • Know how to access diagonals: `row - col = constant` for main diagonals
Prefix Sums
  • Build a 1D prefix sum array and answer range queries in O(1)
  • Build a 2D prefix sum and answer rectangle queries in O(1)
  • Apply difference array for O(1) range updates + O(n) reconstruction
  • Use prefix sum + hash map to count subarrays with sum equal to k
Kadane's Algorithm
  • Implement Kadane's algorithm from memory (no DP table needed)
  • Extend Kadane's to track subarray start and end indices
  • Implement minimum subarray sum variant
  • Implement maximum product subarray (tracking both max and min)
Strings
  • Implement palindrome check ignoring non-alphanumeric characters
  • Reverse words in a sentence using stringstream
  • Split a string by delimiter using getline + stringstream
  • Implement string compression (run-length encoding)
Character Frequency
  • Use `int freq[26] = {}` for O(1) space frequency counting (lowercase letters)
  • Know ASCII values: '0'=48, 'A'=65, 'a'=97
  • Convert digit char to int: `c - '0'`, letter to index: `c - 'a'`
Core Distinctions
  • Explain the difference between subarray, subsequence, substring, and subset
  • Know which technique applies to each: sliding window → subarray; DP → subsequence
Problems
  • Solve Best Time to Buy and Sell Stock without looking at notes
  • Solve Maximum Subarray (Kadane's) without looking at notes
  • Solve Product of Array Except Self without using division
  • Solve Trapping Rain Water using both the prefix array and two-pointer methods
  • Solve Subarray Sum Equals K using the prefix sum + hash map pattern
  • Solve Find All Duplicates using the index-negation trick
Notes▸