Circular Array
Overview
Circular Array problems involve arrays where the end wraps around to the beginning, creating a circular structure. Elements can be accessed using modular arithmetic: index % array.length to handle wraparound.
The key insight: use modulo operator to handle circular indexing. When index exceeds array bounds, wrap around using (index % n + n) % n to handle negative indices correctly.
Circular array is particularly useful for:
- Rotating arrays
- Circular buffers
- Ring data structures
- Wraparound indexing
- Circular queue/stack
- Next/previous element queries
Pattern 1: Circular Index Access
Access elements with wraparound.
class CircularArray<T> {
private data: T[];
constructor(data: T[]) {
this.data = [...data];
}
get(index: number): T {
const n = this.data.length;
const normalizedIndex = ((index % n) + n) % n;
return this.data[normalizedIndex];
}
set(index: number, value: T): void {
const n = this.data.length;
const normalizedIndex = ((index % n) + n) % n;
this.data[normalizedIndex] = value;
}
next(index: number): number {
return (index + 1) % this.data.length;
}
previous(index: number): number {
return (index - 1 + this.data.length) % this.data.length;
}
}Time: O(1), Space: O(n)
Pattern 2: Search in Circular Sorted Array
Search target in rotated sorted array.
function searchCircular(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) return mid;
// Left half is sorted
if (nums[left] <= nums[mid]) {
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
// Right half is sorted
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}Time: O(log n), Space: O(1)
Pattern 3: Next Greater Element (Circular)
Find next greater element in circular array.
function nextGreaterElementsCircular(nums: number[]): number[] {
const n = nums.length;
const result = new Array(n).fill(-1);
const stack: number[] = [];
// Process array twice for circular
for (let i = 0; i < n * 2; i++) {
const index = i % n;
while (stack.length > 0 && nums[index] > nums[stack[stack.length - 1]]) {
const top = stack.pop()!;
result[top] = nums[index];
}
if (i < n) {
stack.push(index);
}
}
return result;
}Time: O(n), Space: O(n)
Pattern 4: Circular Array Loop
Detect cycle in circular array.
function circularArrayLoop(nums: number[]): boolean {
const n = nums.length;
for (let i = 0; i < n; i++) {
if (nums[i] === 0) continue;
let slow = i;
let fast = i;
// Check if cycle exists
do {
slow = getNext(nums, slow);
fast = getNext(nums, getNext(nums, fast));
} while (slow !== fast);
// Check if cycle is valid (same direction, length > 1)
if (slow === getNext(nums, slow)) {
continue; // Length 1 cycle
}
let current = slow;
let direction = nums[current] > 0;
let valid = true;
do {
if ((nums[current] > 0) !== direction) {
valid = false;
break;
}
current = getNext(nums, current);
} while (current !== slow);
if (valid) return true;
}
return false;
}
function getNext(nums: number[], index: number): number {
const n = nums.length;
return ((index + nums[index]) % n + n) % n;
}Time: O(n²), Space: O(1)
Pattern 5: Circular Queue
Implement circular queue.
class CircularQueue<T> {
private data: (T | undefined)[];
private head: number;
private tail: number;
private size: number;
private capacity: number;
constructor(k: number) {
this.capacity = k;
this.data = new Array(k);
this.head = 0;
this.tail = 0;
this.size = 0;
}
enQueue(value: T): boolean {
if (this.isFull()) return false;
this.data[this.tail] = value;
this.tail = (this.tail + 1) % this.capacity;
this.size++;
return true;
}
deQueue(): boolean {
if (this.isEmpty()) return false;
this.data[this.head] = undefined;
this.head = (this.head + 1) % this.capacity;
this.size--;
return true;
}
Front(): T | -1 {
if (this.isEmpty()) return -1;
return this.data[this.head]!;
}
Rear(): T | -1 {
if (this.isEmpty()) return -1;
const rearIndex = (this.tail - 1 + this.capacity) % this.capacity;
return this.data[rearIndex]!;
}
isEmpty(): boolean {
return this.size === 0;
}
isFull(): boolean {
return this.size === this.capacity;
}
}Time: O(1) for all operations, Space: O(k)
Pattern 6: Rotate Circular Array
Rotate elements in circular array.
function rotateCircular(nums: number[], k: number): void {
const n = nums.length;
k = k % n;
// Reverse entire array
reverse(nums, 0, n - 1);
// Reverse first k elements
reverse(nums, 0, k - 1);
// Reverse remaining elements
reverse(nums, k, n - 1);
}
function reverse(nums: number[], start: number, end: number): void {
while (start < end) {
[nums[start], nums[end]] = [nums[end], nums[start]];
start++;
end--;
}
}Time: O(n), Space: O(1)
Pattern 7: Maximum Sum Circular Subarray
Find maximum sum subarray in circular array.
function maxSubarraySumCircular(nums: number[]): number {
// Case 1: Maximum subarray in linear array (normal Kadane)
let maxSum = nums[0];
let currentMax = nums[0];
// Case 2: Maximum sum wraps around (total - minimum subarray)
let minSum = nums[0];
let currentMin = nums[0];
let totalSum = nums[0];
for (let i = 1; i < nums.length; i++) {
totalSum += nums[i];
// Maximum subarray
currentMax = Math.max(nums[i], currentMax + nums[i]);
maxSum = Math.max(maxSum, currentMax);
// Minimum subarray
currentMin = Math.min(nums[i], currentMin + nums[i]);
minSum = Math.min(minSum, currentMin);
}
// If all negative, return maxSum
if (maxSum < 0) return maxSum;
// Maximum is either normal max or (total - min)
return Math.max(maxSum, totalSum - minSum);
}Time: O(n), Space: O(1)
Pattern 8: Circular Permutation
Generate circular permutations.
function circularPermutations(nums: number[]): number[][] {
const result: number[][] = [];
const n = nums.length;
// Generate all rotations
for (let i = 0; i < n; i++) {
const rotated: number[] = [];
for (let j = 0; j < n; j++) {
rotated.push(nums[(i + j) % n]);
}
result.push(rotated);
}
return result;
}Time: O(n²), Space: O(n²)
Pattern 9: Circular Buffer
Implement circular buffer.
class CircularBuffer<T> {
private buffer: (T | undefined)[];
private writeIndex: number;
private readIndex: number;
private count: number;
private capacity: number;
constructor(capacity: number) {
this.capacity = capacity;
this.buffer = new Array(capacity);
this.writeIndex = 0;
this.readIndex = 0;
this.count = 0;
}
write(value: T): boolean {
if (this.isFull()) return false;
this.buffer[this.writeIndex] = value;
this.writeIndex = (this.writeIndex + 1) % this.capacity;
this.count++;
return true;
}
read(): T | undefined {
if (this.isEmpty()) return undefined;
const value = this.buffer[this.readIndex];
this.buffer[this.readIndex] = undefined;
this.readIndex = (this.readIndex + 1) % this.capacity;
this.count--;
return value;
}
peek(): T | undefined {
if (this.isEmpty()) return undefined;
return this.buffer[this.readIndex];
}
isEmpty(): boolean {
return this.count === 0;
}
isFull(): boolean {
return this.count === this.capacity;
}
}Time: O(1) for all operations, Space: O(capacity)
Pattern 10: Circular Array Traversal
Traverse circular array from any starting point.
function traverseCircular<T>(arr: T[], startIndex: number, direction: 'forward' | 'backward' = 'forward'): T[] {
const result: T[] = [];
const n = arr.length;
if (direction === 'forward') {
for (let i = 0; i < n; i++) {
const index = (startIndex + i) % n;
result.push(arr[index]);
}
} else {
for (let i = 0; i < n; i++) {
const index = (startIndex - i + n) % n;
result.push(arr[index]);
}
}
return result;
}Time: O(n), Space: O(n)
Pattern 11: Circular Array Range Query
Query range in circular array.
function circularRangeQuery(
arr: number[],
start: number,
end: number
): number[] {
const n = arr.length;
const result: number[] = [];
if (start <= end) {
// Normal range
for (let i = start; i <= end; i++) {
result.push(arr[i % n]);
}
} else {
// Wraps around
for (let i = start; i < n; i++) {
result.push(arr[i]);
}
for (let i = 0; i <= end; i++) {
result.push(arr[i]);
}
}
return result;
}Time: O(end - start), Space: O(end - start)
Pattern 12: Circular Array Maximum Distance
Find maximum distance between two elements in circular array.
function maxCircularDistance(arr: number[]): number {
const n = arr.length;
let maxDist = 0;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
// Distance going forward
const forwardDist = j - i;
// Distance going backward (circular)
const backwardDist = i + (n - j);
const minDist = Math.min(forwardDist, backwardDist);
maxDist = Math.max(maxDist, minDist);
}
}
return maxDist;
}Time: O(n²), Space: O(1)
When to Use Circular Array Techniques
Use circular array techniques when:
- Array wraps around (end connects to beginning)
- Need circular buffer or queue
- Rotating array elements
- Circular permutations or rotations
- Ring data structures
- Wraparound indexing required
Key insight: Use modulo operator (index % n + n) % n for safe circular indexing.
Template (Circular Index Access)
function getCircularIndex<T>(arr: T[], index: number): T {
const n = arr.length;
const normalizedIndex = ((index % n) + n) % n;
return arr[normalizedIndex];
}
function setCircularIndex<T>(arr: T[], index: number, value: T): void {
const n = arr.length;
const normalizedIndex = ((index % n) + n) % n;
arr[normalizedIndex] = value;
}Template (Circular Traversal)
function traverseCircularTemplate<T>(
arr: T[],
start: number,
steps: number,
direction: 'forward' | 'backward'
): T[] {
const result: T[] = [];
const n = arr.length;
for (let i = 0; i < steps; i++) {
const offset = direction === 'forward' ? i : -i;
const index = ((start + offset) % n + n) % n;
result.push(arr[index]);
}
return result;
}Time and Space Complexity Summary
- Circular index access: O(1) time, O(1) space
- Search circular sorted: O(log n) time, O(1) space
- Next greater circular: O(n) time, O(n) space
- Circular queue: O(1) time per operation, O(k) space
- Rotate circular: O(n) time, O(1) space
- Max sum circular: O(n) time, O(1) space
Practice Tips
- Modulo arithmetic - Use
(index % n + n) % nfor safe indexing - Handle wraparound - Consider both forward and backward paths
- Circular vs linear - Distinguish when wraparound matters
- Edge cases - Empty array, single element, full rotation
- Visualization - Draw circular structure to understand
Common Mistakes
- Index errors - Not handling negative indices correctly
- Off-by-one - Errors in circular calculations
- Not normalizing - Forgetting to normalize indices
- Direction confusion - Mixing forward and backward traversal
- Boundary handling - Not handling wraparound correctly
Related Concepts
- Modular Arithmetic - Core technique
- Array Rotation - Related operation
- Circular Queue - Data structure application
- Ring Buffer - Real-world application
Circular array is essential for ring data structures and wraparound problems. Master modulo arithmetic and circular indexing, and you'll efficiently solve circular array problems in coding interviews.