Arrays and Strings

Arrays and Strings

Overview

Arrays and strings are the most fundamental data structures. This lesson covers key techniques frequently used in array/string problems: two pointers, sliding window, and prefix sum.


Table of Contents

  1. Array Basics
  2. Two Pointers Technique
  3. Sliding Window
  4. Prefix Sum
  5. String Processing
  6. Frequency Counting
  7. Practice Problems

1. Array Basics

Array Characteristics

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Operation      │ Time        │ Description          │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Index access   │ O(1)        │ arr[i]               │
│ Append         │ O(1)*       │ Dynamic array avg    │
│ Insert middle  │ O(n)        │ Element shift needed │
│ Delete         │ O(n)        │ Element shift needed │
│ Search         │ O(n)        │ Unsorted             │
│ Search (sorted)│ O(log n)    │ Binary search        │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Array Traversal Patterns

// C++ - Basic traversal
vector<int> arr = {1, 2, 3, 4, 5};

// Index-based
for (int i = 0; i < arr.size(); i++) {
    cout << arr[i] << " ";
}

// Range-based
for (int x : arr) {
    cout << x << " ";
}

// Reverse traversal
for (int i = arr.size() - 1; i >= 0; i--) {
    cout << arr[i] << " ";
}
# Python
arr = [1, 2, 3, 4, 5]

# Basic traversal
for x in arr:
    print(x, end=" ")

# With index
for i, x in enumerate(arr):
    print(f"arr[{i}] = {x}")

# Reverse traversal
for x in reversed(arr):
    print(x, end=" ")

2. Two Pointers Technique

Concept

Two Pointers: Use two pointers to traverse an array
→ Can reduce O(n²) to O(n)

Types:
1. Start from both ends (sorted array)
2. Move in same direction (slow/fast pointers)

2.1 Two Pointers from Both Ends

Problem: Find pair with sum equal to target in sorted array

Array: [1, 2, 4, 6, 8, 10]
Target: 10

left → [1]  [2]  [4]  [6]  [8]  [10] ← right
        ↓                          ↓
      1 + 10 = 11 > 10 → right--

left → [1]  [2]  [4]  [6]  [8]  [10]
        ↓                    ↓
      1 + 8 = 9 < 10 → left++

       [1]  [2]  [4]  [6]  [8]  [10]
             ↓              ↓
      2 + 8 = 10 āœ“ Found!
// C
void twoSum(int arr[], int n, int target) {
    int left = 0;
    int right = n - 1;

    while (left < right) {
        int sum = arr[left] + arr[right];

        if (sum == target) {
            printf("Found: %d + %d = %d\n",
                   arr[left], arr[right], target);
            return;
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
    printf("Not found\n");
}
// C++
pair<int, int> twoSum(const vector<int>& arr, int target) {
    int left = 0;
    int right = arr.size() - 1;

    while (left < right) {
        int sum = arr[left] + arr[right];

        if (sum == target) {
            return {left, right};
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
    return {-1, -1};
}
# Python
def two_sum(arr, target):
    left, right = 0, len(arr) - 1

    while left < right:
        current_sum = arr[left] + arr[right]

        if current_sum == target:
            return (left, right)
        elif current_sum < target:
            left += 1
        else:
            right -= 1

    return (-1, -1)

2.2 Palindrome Check

String: "racecar"

left → [r] [a] [c] [e] [c] [a] [r] ← right
        ↓                       ↓
       'r' == 'r' āœ“

       [r] [a] [c] [e] [c] [a] [r]
            ↓               ↓
       'a' == 'a' āœ“

       [r] [a] [c] [e] [c] [a] [r]
                ↓       ↓
       'c' == 'c' āœ“

       [r] [a] [c] [e] [c] [a] [r]
                    ↓
       left >= right → Palindrome!
// C
#include <string.h>
#include <stdbool.h>

bool isPalindrome(const char* s) {
    int left = 0;
    int right = strlen(s) - 1;

    while (left < right) {
        if (s[left] != s[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}
// C++
bool isPalindrome(const string& s) {
    int left = 0;
    int right = s.length() - 1;

    while (left < right) {
        if (s[left] != s[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}
# Python
def is_palindrome(s):
    left, right = 0, len(s) - 1

    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1

    return True

# Pythonic way
def is_palindrome_simple(s):
    return s == s[::-1]

2.3 Same Direction Two Pointers (Slow/Fast Pointers)

Problem: Remove duplicates from sorted array (In-place)

Array: [1, 1, 2, 2, 2, 3]

slow ↓
fast ↓
     [1] [1] [2] [2] [2] [3]
     slow is unique element position, fast explores

     [1] [1] [2] [2] [2] [3]
      ↓   ↓
     arr[slow] == arr[fast] → fast++

     [1] [1] [2] [2] [2] [3]
      ↓       ↓
     arr[slow] != arr[fast] → slow++, copy

     [1] [2] [2] [2] [2] [3]
          ↓   ↓
     ...

Result: [1, 2, 3, _, _, _], 3 unique elements
// C
int removeDuplicates(int arr[], int n) {
    if (n == 0) return 0;

    int slow = 0;

    for (int fast = 1; fast < n; fast++) {
        if (arr[fast] != arr[slow]) {
            slow++;
            arr[slow] = arr[fast];
        }
    }

    return slow + 1;  // Number of unique elements
}
// C++
int removeDuplicates(vector<int>& arr) {
    if (arr.empty()) return 0;

    int slow = 0;

    for (int fast = 1; fast < arr.size(); fast++) {
        if (arr[fast] != arr[slow]) {
            slow++;
            arr[slow] = arr[fast];
        }
    }

    return slow + 1;
}
# Python
def remove_duplicates(arr):
    if not arr:
        return 0

    slow = 0

    for fast in range(1, len(arr)):
        if arr[fast] != arr[slow]:
            slow += 1
            arr[slow] = arr[fast]

    return slow + 1

3. Sliding Window

Concept

Sliding Window: Move a fixed or variable-sized window through array
→ Effective for contiguous subarray/substring problems
→ Can reduce O(n²) to O(n)

Types:
1. Fixed-size window (size k)
2. Variable-size window (satisfies condition)

3.1 Fixed-Size Window

Problem: Maximum sum of contiguous subarray of size k

Array: [1, 4, 2, 10, 2, 3, 1, 0, 20]
k = 3

Window movement:
[1, 4, 2] → sum: 7
   [4, 2, 10] → sum: 16
      [2, 10, 2] → sum: 14
         [10, 2, 3] → sum: 15
            [2, 3, 1] → sum: 6
               [3, 1, 0] → sum: 4
                  [1, 0, 20] → sum: 21 ← Maximum!

Optimization: Don't sum k elements each time,
       add new element - remove old element
// C - Naive: O(n*k)
int maxSumNaive(int arr[], int n, int k) {
    int maxSum = 0;

    for (int i = 0; i <= n - k; i++) {
        int sum = 0;
        for (int j = 0; j < k; j++) {
            sum += arr[i + j];
        }
        if (sum > maxSum) {
            maxSum = sum;
        }
    }

    return maxSum;
}

// C - Sliding Window: O(n)
int maxSumSliding(int arr[], int n, int k) {
    // Calculate first window sum
    int windowSum = 0;
    for (int i = 0; i < k; i++) {
        windowSum += arr[i];
    }

    int maxSum = windowSum;

    // Slide window
    for (int i = k; i < n; i++) {
        windowSum += arr[i] - arr[i - k];  // Add new, remove old
        if (windowSum > maxSum) {
            maxSum = windowSum;
        }
    }

    return maxSum;
}
// C++
int maxSumSliding(const vector<int>& arr, int k) {
    int n = arr.size();
    if (n < k) return -1;

    // First window sum
    int windowSum = 0;
    for (int i = 0; i < k; i++) {
        windowSum += arr[i];
    }

    int maxSum = windowSum;

    // Sliding
    for (int i = k; i < n; i++) {
        windowSum += arr[i] - arr[i - k];
        maxSum = max(maxSum, windowSum);
    }

    return maxSum;
}
# Python
def max_sum_sliding(arr, k):
    n = len(arr)
    if n < k:
        return -1

    # First window sum
    window_sum = sum(arr[:k])
    max_sum = window_sum

    # Sliding
    for i in range(k, n):
        window_sum += arr[i] - arr[i - k]
        max_sum = max(max_sum, window_sum)

    return max_sum

3.2 Variable-Size Window

Problem: Minimum length subarray with sum >= target

Array: [2, 3, 1, 2, 4, 3], target = 7

left=0, right=0: [2] → sum=2 < 7, right++
left=0, right=1: [2,3] → sum=5 < 7, right++
left=0, right=2: [2,3,1] → sum=6 < 7, right++
left=0, right=3: [2,3,1,2] → sum=8 >= 7, len=4, left++
left=1, right=3: [3,1,2] → sum=6 < 7, right++
left=1, right=4: [3,1,2,4] → sum=10 >= 7, len=4, left++
left=2, right=4: [1,2,4] → sum=7 >= 7, len=3, left++
left=3, right=4: [2,4] → sum=6 < 7, right++
left=3, right=5: [2,4,3] → sum=9 >= 7, len=3, left++
left=4, right=5: [4,3] → sum=7 >= 7, len=2 ← Minimum!

Answer: 2
// C
int minSubArrayLen(int arr[], int n, int target) {
    int minLen = n + 1;  // Initialize to impossible value
    int left = 0;
    int sum = 0;

    for (int right = 0; right < n; right++) {
        sum += arr[right];

        while (sum >= target) {
            int len = right - left + 1;
            if (len < minLen) {
                minLen = len;
            }
            sum -= arr[left];
            left++;
        }
    }

    return (minLen == n + 1) ? 0 : minLen;
}
// C++
int minSubArrayLen(const vector<int>& arr, int target) {
    int n = arr.size();
    int minLen = INT_MAX;
    int left = 0;
    int sum = 0;

    for (int right = 0; right < n; right++) {
        sum += arr[right];

        while (sum >= target) {
            minLen = min(minLen, right - left + 1);
            sum -= arr[left];
            left++;
        }
    }

    return (minLen == INT_MAX) ? 0 : minLen;
}
# Python
def min_sub_array_len(arr, target):
    n = len(arr)
    min_len = float('inf')
    left = 0
    current_sum = 0

    for right in range(n):
        current_sum += arr[right]

        while current_sum >= target:
            min_len = min(min_len, right - left + 1)
            current_sum -= arr[left]
            left += 1

    return 0 if min_len == float('inf') else min_len

3.3 String Sliding Window

Problem: Longest substring without repeating characters

String: "abcabcbb"

[a] → set: {a}, length: 1
[a,b] → set: {a,b}, length: 2
[a,b,c] → set: {a,b,c}, length: 3 ← Maximum
[a,b,c,a] → 'a' duplicate! Move left to remove 'a'
[b,c,a] → set: {b,c,a}, length: 3
[b,c,a,b] → 'b' duplicate! Move left
...

Answer: 3 ("abc" or "bca" or "cab")
// C++
int lengthOfLongestSubstring(const string& s) {
    unordered_set<char> seen;
    int maxLen = 0;
    int left = 0;

    for (int right = 0; right < s.length(); right++) {
        // Move left if duplicate
        while (seen.count(s[right])) {
            seen.erase(s[left]);
            left++;
        }

        seen.insert(s[right]);
        maxLen = max(maxLen, right - left + 1);
    }

    return maxLen;
}
# Python
def length_of_longest_substring(s):
    seen = set()
    max_len = 0
    left = 0

    for right in range(len(s)):
        while s[right] in seen:
            seen.remove(s[left])
            left += 1

        seen.add(s[right])
        max_len = max(max_len, right - left + 1)

    return max_len

4. Prefix Sum

Concept

Prefix Sum (Cumulative Sum): Calculate range sum in O(1)

Original array:  [1, 2, 3, 4, 5]
Prefix sum:      [1, 3, 6, 10, 15]

prefix[i] = arr[0] + arr[1] + ... + arr[i]

Range sum [i, j]:
sum(i, j) = prefix[j] - prefix[i-1]
          = (arr[0]+...+arr[j]) - (arr[0]+...+arr[i-1])
          = arr[i] + ... + arr[j]

Prefix Sum Visualization

Index:       0    1    2    3    4
Original:   [1]  [2]  [3]  [4]  [5]
Prefix:     [1]  [3]  [6] [10] [15]

sum(1, 3) = arr[1] + arr[2] + arr[3]
          = 2 + 3 + 4 = 9

          = prefix[3] - prefix[0]
          = 10 - 1 = 9 āœ“

Implementation

// C
// Build prefix sum array
void buildPrefixSum(int arr[], int prefix[], int n) {
    prefix[0] = arr[0];
    for (int i = 1; i < n; i++) {
        prefix[i] = prefix[i - 1] + arr[i];
    }
}

// Range sum query [left, right]
int rangeSum(int prefix[], int left, int right) {
    if (left == 0) {
        return prefix[right];
    }
    return prefix[right] - prefix[left - 1];
}
// C++
class PrefixSum {
private:
    vector<int> prefix;

public:
    PrefixSum(const vector<int>& arr) {
        int n = arr.size();
        prefix.resize(n + 1, 0);

        for (int i = 0; i < n; i++) {
            prefix[i + 1] = prefix[i] + arr[i];
        }
    }

    // Sum of range [left, right]
    int query(int left, int right) {
        return prefix[right + 1] - prefix[left];
    }
};

// Usage example
// vector<int> arr = {1, 2, 3, 4, 5};
// PrefixSum ps(arr);
// cout << ps.query(1, 3);  // 9
# Python
class PrefixSum:
    def __init__(self, arr):
        n = len(arr)
        self.prefix = [0] * (n + 1)

        for i in range(n):
            self.prefix[i + 1] = self.prefix[i] + arr[i]

    def query(self, left, right):
        """Sum of range [left, right]"""
        return self.prefix[right + 1] - self.prefix[left]


# Usage example
arr = [1, 2, 3, 4, 5]
ps = PrefixSum(arr)
print(ps.query(1, 3))  # 9

2D Prefix Sum

Sum of submatrix in 2D array

Original matrix:            2D prefix:
[1, 2, 3]               [1,  3,  6]
[4, 5, 6]     →         [5, 12, 21]
[7, 8, 9]               [12, 27, 45]

Sum of submatrix (1,1)~(2,2):
= prefix[2][2] - prefix[0][2] - prefix[2][0] + prefix[0][0]
= 45 - 6 - 12 + 1 = 28

Verify: 5+6+8+9 = 28 āœ“
// C++ - 2D Prefix Sum
class PrefixSum2D {
private:
    vector<vector<int>> prefix;

public:
    PrefixSum2D(const vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        prefix.resize(m + 1, vector<int>(n + 1, 0));

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                prefix[i + 1][j + 1] = matrix[i][j]
                                     + prefix[i][j + 1]
                                     + prefix[i + 1][j]
                                     - prefix[i][j];
            }
        }
    }

    // Sum of submatrix (r1,c1)~(r2,c2)
    int query(int r1, int c1, int r2, int c2) {
        return prefix[r2 + 1][c2 + 1]
             - prefix[r1][c2 + 1]
             - prefix[r2 + 1][c1]
             + prefix[r1][c1];
    }
};
# Python - 2D Prefix Sum
class PrefixSum2D:
    def __init__(self, matrix):
        m, n = len(matrix), len(matrix[0])
        self.prefix = [[0] * (n + 1) for _ in range(m + 1)]

        for i in range(m):
            for j in range(n):
                self.prefix[i + 1][j + 1] = (matrix[i][j]
                                            + self.prefix[i][j + 1]
                                            + self.prefix[i + 1][j]
                                            - self.prefix[i][j])

    def query(self, r1, c1, r2, c2):
        """Sum of submatrix (r1,c1)~(r2,c2)"""
        return (self.prefix[r2 + 1][c2 + 1]
              - self.prefix[r1][c2 + 1]
              - self.prefix[r2 + 1][c1]
              + self.prefix[r1][c1])

5. String Processing

5.1 String Reversal

// C
void reverseString(char* s) {
    int left = 0;
    int right = strlen(s) - 1;

    while (left < right) {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}
// C++
void reverseString(string& s) {
    int left = 0, right = s.length() - 1;

    while (left < right) {
        swap(s[left], s[right]);
        left++;
        right--;
    }
}

// STL
// reverse(s.begin(), s.end());
# Python
def reverse_string(s):
    return s[::-1]

# In-place (list)
def reverse_string_list(chars):
    left, right = 0, len(chars) - 1
    while left < right:
        chars[left], chars[right] = chars[right], chars[left]
        left += 1
        right -= 1

5.2 Anagram Check

Anagram: Different words composed of same characters
Example: "listen" ↔ "silent"

Method 1: Sort and compare - O(n log n)
Method 2: Frequency comparison - O(n)
// C++ - Frequency method
bool isAnagram(const string& s1, const string& s2) {
    if (s1.length() != s2.length()) return false;

    int count[26] = {0};

    for (char c : s1) count[c - 'a']++;
    for (char c : s2) count[c - 'a']--;

    for (int i = 0; i < 26; i++) {
        if (count[i] != 0) return false;
    }

    return true;
}
# Python
from collections import Counter

def is_anagram(s1, s2):
    return Counter(s1) == Counter(s2)

# Or sorting method
def is_anagram_sort(s1, s2):
    return sorted(s1) == sorted(s2)

6. Frequency Counting

Frequency Using HashMap

// C++ - Character frequency
unordered_map<char, int> countFrequency(const string& s) {
    unordered_map<char, int> freq;
    for (char c : s) {
        freq[c]++;
    }
    return freq;
}

// Find most frequent character
char mostFrequent(const string& s) {
    unordered_map<char, int> freq;
    for (char c : s) {
        freq[c]++;
    }

    char result = '\0';
    int maxCount = 0;

    for (auto& [c, count] : freq) {
        if (count > maxCount) {
            maxCount = count;
            result = c;
        }
    }

    return result;
}
# Python
from collections import Counter

def count_frequency(s):
    return Counter(s)

# Most frequent character
def most_frequent(s):
    freq = Counter(s)
    return freq.most_common(1)[0][0]

Frequency Using Array (Alphabets)

// C - Lowercase only
void countFrequency(const char* s, int freq[]) {
    // freq array should be size 26 initialized to 0
    while (*s) {
        freq[*s - 'a']++;
        s++;
    }
}

// Usage
int freq[26] = {0};
countFrequency("hello", freq);
// freq['h'-'a'] = 1, freq['e'-'a'] = 1, freq['l'-'a'] = 2, freq['o'-'a'] = 1

7. Practice Problems

Problem 1: Array Rotation

Rotate array to the right by k positions.

Input: [1, 2, 3, 4, 5], k = 2
Output: [4, 5, 1, 2, 3]
Hint Can solve with O(1) space using three reversals: 1. Reverse entire array 2. Reverse first k elements 3. Reverse remaining elements
Solution Code
def rotate(arr, k):
    n = len(arr)
    k = k % n  # k might be larger than n

    def reverse(start, end):
        while start < end:
            arr[start], arr[end] = arr[end], arr[start]
            start += 1
            end -= 1

    reverse(0, n - 1)      # [5,4,3,2,1]
    reverse(0, k - 1)      # [4,5,3,2,1]
    reverse(k, n - 1)      # [4,5,1,2,3]

# Time: O(n), Space: O(1)

Problem 2: Count Subarrays with Sum k

Count the number of contiguous subarrays with sum equal to k.

Input: [1, 1, 1], k = 2
Output: 2 (two subarrays [1,1])
Hint Use prefix sum + hash map prefix[j] - prefix[i] = k → prefix[i] = prefix[j] - k
Solution Code
from collections import defaultdict

def subarray_sum(arr, k):
    count = 0
    prefix_sum = 0
    prefix_count = defaultdict(int)
    prefix_count[0] = 1  # Empty prefix

    for num in arr:
        prefix_sum += num

        # If prefix_sum - k appeared before
        # Sum from that point to current is k
        if prefix_sum - k in prefix_count:
            count += prefix_count[prefix_sum - k]

        prefix_count[prefix_sum] += 1

    return count

# Time: O(n), Space: O(n)
Difficulty Problem Platform Type
⭐ Two Sum LeetCode Hash map
⭐ Range Sum Query Baekjoon Prefix sum
⭐⭐ 3Sum LeetCode Two pointers
⭐⭐ Longest Substring Without Repeating LeetCode Sliding window
⭐⭐ Maximum Subarray LeetCode Kadane's algorithm
⭐⭐⭐ Minimum Window Substring LeetCode Sliding window

Next Steps


References

to navigate between lessons