Arrays and strings are the most fundamental data structures in programming. They form the basis for understanding more complex data structures and are heavily tested in technical interviews.
In this guide, we’ll explore arrays and strings, their properties, operations, and common algorithmic patterns.
Understanding Arrays
Array Fundamentals
# Array: Contiguous memory allocation
# Fixed size (in most languages)
# Memory layout
array_memory = {
"index": [0, 1, 2, 3, 4, 5],
"value": [10, 25, 33, 47, 62, 99],
"memory": ["@100", "@104", "@108", "@112", "@116", "@120"]
}
# Each int takes 4 bytes (typical)
Array Operations
class Array:
"""Simple array implementation"""
def __init__(self, capacity):
self.capacity = capacity
self.size = 0
self.data = [None] * capacity
def get(self, index):
"""O(1) - Direct access by index"""
if 0 <= index < self.size:
return self.data[index]
raise IndexError("Index out of bounds")
def set(self, index, value):
"""O(1) - Set value at index"""
if 0 <= index < self.size:
self.data[index] = value
else:
raise IndexError("Index out of bounds")
def append(self, value):
"""O(1) amortized - Add to end"""
if self.size == self.capacity:
self._resize()
self.data[self.size] = value
self.size += 1
def insert(self, index, value):
"""O(n) - Insert at index"""
if self.size == self.capacity:
self._resize()
# Shift elements right
for i in range(self.size, index, -1):
self.data[i] = self.data[i - 1]
self.data[index] = value
self.size += 1
def delete(self, index):
"""O(n) - Delete at index"""
value = self.data[index]
# Shift elements left
for i in range(index, self.size - 1):
self.data[i] = self.data[i + 1]
self.size -= 1
return value
def _resize(self):
"""O(n) - Double capacity"""
new_data = [None] * (self.capacity * 2)
for i in range(self.capacity):
new_data[i] = self.data[i]
self.data = new_data
self.capacity *= 2
Time Complexity
# Array operations complexity
array_complexity = {
"access": "O(1)",
"search": "O(n)",
"insertion": "O(n)",
"deletion": "O(n)",
"append": "O(1) amortized"
}
Two-Dimensional Arrays
# 2D Array / Matrix
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
# Access element
def get_element(matrix, row, col):
return matrix[row][col] # O(1)
# Traverse matrix
def traverse_matrix(matrix):
rows = len(matrix)
cols = len(matrix[0])
for i in range(rows):
for j in range(cols):
print(matrix[i][j], end=" ")
print()
# Diagonal traversal
def diagonal_traverse(matrix):
if not matrix or not matrix[0]:
return []
result = []
for d in range(len(matrix) + len(matrix[0]) - 1):
row = min(d, len(matrix) - 1)
col = d - row
if d % 2 == 0:
# Even diagonals: bottom-left to top-right
while row >= 0 and col < len(matrix[0]):
result.append(matrix[row][col])
row -= 1
col += 1
else:
# Odd diagonals: top-right to bottom-left
col = min(d, len(matrix[0]) - 1)
row = d - col
while col >= 0 and row < len(matrix):
result.append(matrix[row][col])
row += 1
col -= 1
return result
Understanding Strings
String Fundamentals
# Strings are arrays of characters
# Most languages: immutable (Python, Java)
# Some: mutable (C++, C#)
# String creation
s = "hello world"
chars = ['h', 'e', 'l', 'l', 'o']
# String operations
string_operations = {
"length": len(s), # O(1) - stored
"access": s[0], # O(1)
"concatenation": s + "!", # O(n)
"substring": s[0:5], # O(k)
"search": s.find("world"), # O(n)
}
Common String Operations
# String manipulation functions
def reverse_string(s):
"""Reverse a string"""
return s[::-1]
def reverse_words(s):
"""Reverse words in a string"""
return ' '.join(s.split()[::-1])
def is_palindrome(s):
"""Check if string is palindrome"""
cleaned = ''.join(c.lower() for c in s if c.isalnum())
return cleaned == cleaned[::-1]
def count_characters(s):
"""Count character frequency"""
from collections import Counter
return Counter(s)
def is_anagram(s1, s2):
"""Check if two strings are anagrams"""
return sorted(s1) == sorted(s2)
def longest_unique_substring(s):
"""Find longest substring without repeating"""
seen = {}
start = 0
max_length = 0
result = ""
for i, char in enumerate(s):
if char in seen and seen[char] >= start:
start = seen[char] + 1
if i - start + 1 > max_length:
max_length = i - start + 1
result = s[start:i+1]
seen[char] = i
return result
String Matching Algorithms
# Brute force search
def naive_search(text, pattern):
"""O(n*m) time complexity"""
matches = []
for i in range(len(text) - len(pattern) + 1):
match = True
for j in range(len(pattern)):
if text[i + j] != pattern[j]:
match = False
break
if match:
matches.append(i)
return matches
# KMP Algorithm (Knuth-Morris-Pratt)
def kmp_search(text, pattern):
"""O(n + m) time complexity"""
if not pattern:
return [0]
# Build LPS (Longest Prefix Suffix) array
lps = [0] * len(pattern)
length = 0
i = 1
while i < len(pattern):
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
# Search
matches = []
i = j = 0
while i < len(text):
if pattern[j] == text[i]:
i += 1
j += 1
if j == len(pattern):
matches.append(i - j)
j = lps[j - 1]
elif i < len(text) and pattern[j] != text[i]:
if j != 0:
j = lps[j - 1]
else:
i += 1
return matches
# Rabin-Karp Algorithm
def rabin_karp(text, pattern, d=256, q=101):
"""O(n + m) average time"""
n, m = len(text), len(pattern)
h = pow(d, m - 1, q)
p = t = 0
# Calculate hash
for i in range(m):
p = (d * p + ord(pattern[i])) % q
t = (d * t + ord(text[i])) % q
matches = []
for i in range(n - m + 1):
if p == t:
if text[i:i+m] == pattern:
matches.append(i)
if i < n - m:
t = (d * (t - ord(text[i]) * h) + ord(text[i + m])) % q
if t < 0:
t += q
return matches
Common Patterns
Sliding Window
# Fixed sliding window
def find_max_sum(arr, k):
"""Find maximum sum of k consecutive elements"""
window_sum = sum(arr[:k])
max_sum = window_sum
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
# Variable sliding window
def min_subarray_len(target, nums):
"""Find minimum length of subarray with sum >= target"""
min_length = float('inf')
window_sum = 0
left = 0
for right in range(len(nums)):
window_sum += nums[right]
while window_sum >= target:
min_length = min(min_length, right - left + 1)
window_sum -= nums[left]
left += 1
return min_length if min_length != float('inf') else 0
# Longest substring with k distinct characters
def longest_k_distinct(s, k):
if k == 0:
return 0
char_count = {}
left = 0
max_length = 0
for right in range(len(s)):
char_count[s[right]] = char_count.get(s[right], 0) + 1
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
max_length = max(max_length, right - left + 1)
return max_length
Two Pointers
# Remove duplicates from sorted array
def remove_duplicates(nums):
"""In-place, returns new length"""
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
# Container with most water
def max_area(height):
"""Two pointer approach"""
left = 0
right = len(height) - 1
max_water = 0
while left < right:
width = right - left
h = min(height[left], height[right])
max_water = max(max_water, width * h)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
# 3Sum problem
def three_sum(nums):
"""Find all unique triplets that sum to 0"""
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return result
Prefix Sum
# Prefix sum for range queries
class PrefixSum:
def __init__(self, nums):
self.prefix = [0]
for num in nums:
self.prefix.append(self.prefix[-1] + num)
def range_sum(self, left, right):
"""O(1) range sum query"""
return self.prefix[right + 1] - self.prefix[left]
# Subarray sum equals k
def subarray_sum(nums, k):
"""Count subarrays with sum k"""
count = 0
prefix_sum = 0
prefix_counts = {0: 1}
for num in nums:
prefix_sum += num
if prefix_sum - k in prefix_counts:
count += prefix_counts[prefix_sum - k]
prefix_counts[prefix_sum] = prefix_counts.get(prefix_sum, 0) + 1
return count
Sorting-Based
# Find missing positive
def first_missing_positive(nums):
"""Find smallest missing positive integer"""
n = len(nums)
# Place each number in its correct position
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
correct_idx = nums[i] - 1
nums[i], nums[correct_idx] = nums[correct_idx], nums[i]
# Find first position with wrong value
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1
# Find duplicates
def find_duplicates(nums):
"""Find all duplicates using negative marking"""
duplicates = []
for num in nums:
idx = abs(num)
if nums[idx] < 0:
duplicates.append(idx)
else:
nums[idx] = -nums[idx]
return duplicates
Interview Problems
Two Sum
# Problem: Find two numbers that add to target
# Return their indices
# Solution 1: Brute force - O(nยฒ)
def two_sum_brute(nums, target):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
# Solution 2: Hash map - O(n)
def two_sum_hashmap(nums, target):
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
# Solution 3: Two pointers (sorted array) - O(n log n)
def two_sum_sorted(nums, target):
left, right = 0, len(nums) - 1
while left < right:
current = nums[left] + nums[right]
if current == target:
return [left, right]
elif current < target:
left += 1
else:
right -= 1
return []
Maximum Subarray (Kadane’s Algorithm)
# Problem: Find contiguous subarray with largest sum
def max_subarray(nums):
"""Kadane's Algorithm - O(n)"""
max_sum = nums[0]
current_sum = nums[0]
for i in range(1, len(nums)):
current_sum = max(nums[i], current_sum + nums[i])
max_sum = max(max_sum, current_sum)
return max_sum
def max_subarray_with_indices(nums):
"""Also return start and end indices"""
max_sum = nums[0]
current_sum = nums[0]
start = end = 0
temp_start = 0
for i in range(1, len(nums)):
if nums[i] > current_sum + nums[i]:
current_sum = nums[i]
temp_start = i
else:
current_sum += nums[i]
if current_sum > max_sum:
max_sum = current_sum
start = temp_start
end = i
return max_sum, start, end
String Compression
def compress_string(s):
"""Compress string like 'aabbbc' -> 'a2b3c1'"""
if not s:
return s
result = []
count = 1
for i in range(1, len(s)):
if s[i] == s[i-1]:
count += 1
else:
result.append(s[i-1] + str(count))
count = 1
result.append(s[-1] + str(count))
compressed = ''.join(result)
return compressed if len(compressed) < len(s) else s
Conclusion
Arrays and strings are foundational data structures:
- Arrays: O(1) access, O(n) search/insert/delete
- Strings: Immutable in most languages, many O(n) operations
- Common patterns: Sliding window, two pointers, prefix sum
Master these fundamentals to tackle more complex data structures and algorithms.
Related Articles
- Linked Lists: Implementation and Practical Use Cases
- Sorting Algorithms: Comparison-based and Linear
- Dynamic Programming Fundamentals
Comments