CoreTechED

Loading

Learn to implement prefix sum arrays in C: 1D, 1-indexed, and 2D prefix sums. Master O(1) range-sum queries with practical examples.

Prefix Sum Array in C: Implementation & Applications

Prefix Sum Array – Implementation in C: A Comprehensive Tutorial

A prefix sum array is a precomputed array where each element at index i stores the sum of all elements from index 0 to i in the original array. This simple yet powerful technique enables O(1) range-sum queries after O(n) preprocessing, making it a cornerstone of many array and range-query algorithms.

This tutorial provides an in-depth exploration of prefix sum arrays in C: concepts, implementation, range-sum queries, 2D prefix sums, practical applications, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is a Prefix Sum Array?

Given an array arr[], the prefix sum array prefixSum[] is defined as:

text

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

Example:

text

Input: [10, 20, 10, 5, 15] Prefix Sum: [10, 30, 40, 45, 60]

Why Prefix Sums Matter

  • Range sum queries: O(1) after O(n) preprocessing.
  • Foundation for many problems: Subarray sums, equilibrium index, split arrays.
  • 2D prefix sums: Rectangle sum queries in matrices.
  • Sliding window alternatives: When the array is static.
  • Competitive programming: Essential preprocessing technique.

Key Insight: Range Sum in O(1)

For a range [L, R] (inclusive):

text

sum(L, R) = prefixSum[R] - prefixSum[L - 1] (if L > 0) sum(0, R) = prefixSum[R] (if L == 0)

Complexity Comparison

OperationWithout Prefix SumWith Prefix Sum
PreprocessingO(1)O(n)
Range sum queryO(n)O(1)
Q queriesO(n × Q)O(n + Q)

Syntax & Core Mechanics

Basic Prefix Sum Implementation

c

#include <stdio.h> #include <stdlib.h> int *buildPrefixSum(const int arr[], int n) { if (n <= 0) return NULL; int *prefix = malloc(n * sizeof(int)); if (prefix == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return NULL; } prefix[0] = arr[0]; for (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] + arr[i]; } return prefix; } int main(void) { int arr[] = {10, 20, 10, 5, 15}; int n = sizeof(arr) / sizeof(arr[0]); int *prefix = buildPrefixSum(arr, n); if (prefix == NULL) return 1; printf("Original: "); for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); printf("Prefix sum: "); for (int i = 0; i < n; i++) printf("%d ", prefix[i]); printf("\n"); free(prefix); return 0; }

Expected output:

text

Original: 10 20 10 5 15 Prefix sum: 10 30 40 45 60

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

Range Sum Query

c

/* Returns sum of arr[L..R] inclusive */ long long rangeSum(const long long prefix[], int L, int R) { if (L < 0 || R < 0 || L > R) return 0; if (L == 0) return prefix[R]; return prefix[R] - prefix[L - 1]; } int main(void) { int arr[] = {10, 20, 10, 5, 15}; int n = 5; long long prefix[5]; prefix[0] = arr[0]; for (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] + arr[i]; } printf("Sum[1..3] = %lld\n", rangeSum(prefix, 1, 3)); /* 20+10+5 = 35 */ printf("Sum[0..4] = %lld\n", rangeSum(prefix, 0, 4)); /* 60 */ printf("Sum[2..2] = %lld\n", rangeSum(prefix, 2, 2)); /* 10 */ printf("Sum[3..4] = %lld\n", rangeSum(prefix, 3, 4)); /* 5+15 = 20 */ return 0; }

Expected output:

text

Sum[1..3] = 35 Sum[0..4] = 60 Sum[2..2] = 10 Sum[3..4] = 20

1-Indexed Prefix Sum (Convenient for Range Queries)

Using a 1-indexed prefix array simplifies range queries (no special case for L == 0).

c

/* prefix[0] = 0, prefix[i] = sum(arr[0..i-1]) */ void buildPrefix1Indexed(const int arr[], int n, long long prefix[]) { prefix[0] = 0; for (int i = 1; i <= n; i++) { prefix[i] = prefix[i - 1] + arr[i - 1]; } } /* Range sum [L, R] inclusive (0-based) */ long long rangeSum1(const long long prefix[], int L, int R) { return prefix[R + 1] - prefix[L]; } int main(void) { int arr[] = {10, 20, 10, 5, 15}; int n = 5; long long prefix[6]; buildPrefix1Indexed(arr, n, prefix); printf("prefix array: "); for (int i = 0; i <= n; i++) printf("%lld ", prefix[i]); printf("\n"); printf("Sum[1..3] = %lld\n", rangeSum1(prefix, 1, 3)); /* 35 */ printf("Sum[0..4] = %lld\n", rangeSum1(prefix, 0, 4)); /* 60 */ return 0; }

Expected output:

text

prefix array: 0 10 30 40 45 60 Sum[1..3] = 35 Sum[0..4] = 60

2D Prefix Sum (Matrix Rectangle Sum)

For a matrix, prefix[i][j] stores the sum of the submatrix from (0,0) to (i,j).

Formula:

text

prefix[i][j] = matrix[i][j] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]

Rectangle sum from (r1, c1) to (r2, c2):

text

sum = prefix[r2][c2] - prefix[r1-1][c2] - prefix[r2][c1-1] + prefix[r1-1][c1-1]

c

#include <stdio.h> #include <stdlib.h> #define ROWS 3 #define COLS 4 void build2DPrefix(int matrix[ROWS][COLS], int prefix[ROWS][COLS]) { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { prefix[i][j] = matrix[i][j]; if (i > 0) prefix[i][j] += prefix[i - 1][j]; if (j > 0) prefix[i][j] += prefix[i][j - 1]; if (i > 0 && j > 0) prefix[i][j] -= prefix[i - 1][j - 1]; } } } int rectSum(int prefix[ROWS][COLS], int r1, int c1, int r2, int c2) { int sum = prefix[r2][c2]; if (r1 > 0) sum -= prefix[r1 - 1][c2]; if (c1 > 0) sum -= prefix[r2][c1 - 1]; if (r1 > 0 && c1 > 0) sum += prefix[r1 - 1][c1 - 1]; return sum; } int main(void) { int matrix[ROWS][COLS] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int prefix[ROWS][COLS]; build2DPrefix(matrix, prefix); printf("Matrix:\n"); for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) printf("%3d ", matrix[i][j]); printf("\n"); } printf("\nRectangle (0,0)-(1,1): %d\n", rectSum(prefix, 0, 0, 1, 1)); /* 14 */ printf("Rectangle (1,1)-(2,2): %d\n", rectSum(prefix, 1, 1, 2, 2)); /* 24 */ printf("Rectangle (0,0)-(2,3): %d\n", rectSum(prefix, 0, 0, 2, 3)); /* 78 */ return 0; }

Expected output:

text

Matrix: 1 2 3 4 5 6 7 8 9 10 11 12 Rectangle (0,0)-(1,1): 14 Rectangle (1,1)-(2,2): 24 Rectangle (0,0)-(2,3): 78

Full Practical Code Example with Explanation

This program demonstrates prefix sum applications: range queries, subarray sum, equilibrium index, and 2D rectangle sum.

c

/** * prefix_sum_demo.c * Demonstrates prefix sum array applications in C. * Includes 1D, 1-indexed, and 2D prefix sums. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 #define ROWS 4 #define COLS 5 /* Function prototypes */ long long *buildPrefix(const int arr[], int n); long long *buildPrefix1Indexed(const int arr[], int n); long long rangeSum(const long long prefix[], int L, int R); long long rangeSum1(const long long prefix[], int L, int R); int subarraySumEqualsK(const int arr[], int n, int k); int findEquilibrium(const int arr[], int n); void build2DPrefix(int matrix[ROWS][COLS], long long prefix[ROWS + 1][COLS + 1]); long long rectSum(long long prefix[ROWS + 1][COLS + 1], int r1, int c1, int r2, int c2); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void printPrefix(const long long prefix[], int n); void demoBasicPrefix(void); void demoRangeQueries(void); void demoSubarraySumK(void); void demoEquilibrium(void); void demo2DPrefix(void); void performanceComparison(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Prefix Sum Array Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Basic Prefix Sum\n"); printf("2. Range Sum Queries\n"); printf("3. Subarray Sum Equals K\n"); printf("4. Equilibrium Index via Prefix\n"); printf("5. 2D Prefix Sum\n"); printf("6. Performance Comparison\n"); printf("7. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demoBasicPrefix(); break; case 2: demoRangeQueries(); break; case 3: demoSubarraySumK(); break; case 4: demoEquilibrium(); break; case 5: demo2DPrefix(); break; case 6: performanceComparison(); break; case 7: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Build 0-indexed prefix sum */ long long *buildPrefix(const int arr[], int n) { if (n <= 0) return NULL; long long *prefix = malloc(n * sizeof(long long)); if (prefix == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return NULL; } prefix[0] = arr[0]; for (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] + arr[i]; } return prefix; } /* Build 1-indexed prefix sum: prefix[0]=0, prefix[i]=sum(arr[0..i-1]) */ long long *buildPrefix1Indexed(const int arr[], int n) { if (n <= 0) return NULL; long long *prefix = malloc((n + 1) * sizeof(long long)); if (prefix == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return NULL; } prefix[0] = 0; for (int i = 1; i <= n; i++) { prefix[i] = prefix[i - 1] + arr[i - 1]; } return prefix; } /* Range sum [L,R] from 0-indexed prefix */ long long rangeSum(const long long prefix[], int L, int R) { if (L < 0 || R < 0 || L > R) return 0; if (L == 0) return prefix[R]; return prefix[R] - prefix[L - 1]; } /* Range sum [L,R] from 1-indexed prefix */ long long rangeSum1(const long long prefix[], int L, int R) { if (L < 0 || R < 0 || L > R) return 0; return prefix[R + 1] - prefix[L]; } /* Count subarrays with sum = k (handles negatives) */ int subarraySumEqualsK(const int arr[], int n, int k) { if (n <= 0) return 0; /* Use prefix sum + hash map (simplified with sorting for demo) */ long long *prefix = buildPrefix1Indexed(arr, n); if (prefix == NULL) return 0; int count = 0; for (int i = 0; i <= n; i++) { for (int j = i + 1; j <= n; j++) { if (prefix[j] - prefix[i] == k) { count++; } } } free(prefix); return count; } /* Find first equilibrium index via prefix sums */ int findEquilibrium(const int arr[], int n) { if (n <= 0) return -1; long long total = 0; for (int i = 0; i < n; i++) total += arr[i]; long long prefSum = 0; for (int i = 0; i < n; i++) { long long suffSum = total - prefSum - arr[i]; if (prefSum == suffSum) return i; prefSum += arr[i]; } return -1; } /* Build 2D prefix sum using (ROWS+1)x(COLS+1) for convenience */ void build2DPrefix(int matrix[ROWS][COLS], long long prefix[ROWS + 1][COLS + 1]) { /* Initialize first row and column to 0 */ for (int j = 0; j <= COLS; j++) prefix[0][j] = 0; for (int i = 0; i <= ROWS; i++) prefix[i][0] = 0; for (int i = 1; i <= ROWS; i++) { for (int j = 1; j <= COLS; j++) { prefix[i][j] = matrix[i - 1][j - 1] + prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1]; } } } /* Rectangle sum using 1-indexed 2D prefix */ long long rectSum(long long prefix[ROWS + 1][COLS + 1], int r1, int c1, int r2, int c2) { /* Convert to 1-indexed */ r1++; c1++; r2++; c2++; return prefix[r2][c2] - prefix[r1 - 1][c2] - prefix[r2][c1 - 1] + prefix[r1 - 1][c1 - 1]; } /* Print array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Print prefix sum */ void printPrefix(const long long prefix[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%lld", prefix[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demos */ void demoBasicPrefix(void) { printf("\n=== Basic Prefix Sum ===\n"); int arr[] = {10, 20, 10, 5, 15}; int n = sizeof(arr) / sizeof(arr[0]); long long *prefix = buildPrefix(arr, n); if (prefix == NULL) return; printf("Original: "); printArray(arr, n); printf("Prefix sum: "); printPrefix(prefix, n); free(prefix); } void demoRangeQueries(void) { printf("\n=== Range Sum Queries ===\n"); int arr[] = {10, 20, 10, 5, 15}; int n = 5; long long *prefix = buildPrefix(arr, n); if (prefix == NULL) return; printf("Original: "); printArray(arr, n); printf("Prefix sum: "); printPrefix(prefix, n); printf("\nRange queries:\n"); printf(" Sum[1..3] = %lld\n", rangeSum(prefix, 1, 3)); printf(" Sum[0..4] = %lld\n", rangeSum(prefix, 0, 4)); printf(" Sum[2..2] = %lld\n", rangeSum(prefix, 2, 2)); printf(" Sum[3..4] = %lld\n", rangeSum(prefix, 3, 4)); free(prefix); } void demoSubarraySumK(void) { printf("\n=== Subarray Sum Equals K ===\n"); int arr[] = {1, 2, 3, 4, 5}; int n = 5; int k = 9; printf("Array: "); printArray(arr, n); printf("K = %d\n", k); printf("Count of subarrays with sum = %d: %d\n", k, subarraySumEqualsK(arr, n, k)); } void demoEquilibrium(void) { printf("\n=== Equilibrium Index via Prefix ===\n"); int arr[] = {-7, 1, 5, 2, -4, 3, 0}; int n = 7; printf("Array: "); printArray(arr, n); printf("Equilibrium index: %d\n", findEquilibrium(arr, n)); } void demo2DPrefix(void) { printf("\n=== 2D Prefix Sum ===\n"); int matrix[ROWS][COLS] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20} }; long long prefix[ROWS + 1][COLS + 1]; build2DPrefix(matrix, prefix); printf("Matrix:\n"); for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { printf("%4d ", matrix[i][j]); } printf("\n"); } printf("\nRectangle queries:\n"); printf(" (0,0)-(1,1) = %lld\n", rectSum(prefix, 0, 0, 1, 1)); printf(" (1,1)-(2,2) = %lld\n", rectSum(prefix, 1, 1, 2, 2)); printf(" (0,0)-(3,4) = %lld\n", rectSum(prefix, 0, 0, 3, 4)); printf(" (2,2)-(3,3) = %lld\n", rectSum(prefix, 2, 2, 3, 3)); } void performanceComparison(void) { printf("\n=== Performance Comparison ===\n"); int n = 100000; int q = 10000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } srand(42); for (int i = 0; i < n; i++) { arr[i] = (rand() % 100) + 1; } clock_t start, end; double timeTaken; /* Without prefix sum */ start = clock(); long long total1 = 0; for (int query = 0; query < q; query++) { int L = rand() % n; int R = L + (rand() % (n - L)); long long sum = 0; for (int i = L; i <= R; i++) sum += arr[i]; total1 += sum; } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Without prefix (O(n*q)): Total = %lld, Time = %.3f ms\n", total1, timeTaken); /* With prefix sum */ long long *prefix = buildPrefix(arr, n); if (prefix == NULL) { free(arr); return; } start = clock(); long long total2 = 0; for (int query = 0; query < q; query++) { int L = rand() % n; int R = L + (rand() % (n - L)); total2 += rangeSum(prefix, L, R); } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("With prefix (O(n+q)): Total = %lld, Time = %.3f ms\n", total2, timeTaken); free(prefix); free(arr); } /* Read integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) return -1; *outValue = (int)val; return 0; }

Expected Output (sample run)

text

=== Prefix Sum Array Demo === --- Menu --- 1. Basic Prefix Sum 2. Range Sum Queries 3. Subarray Sum Equals K 4. Equilibrium Index via Prefix 5. 2D Prefix Sum 6. Performance Comparison 7. Exit Enter choice: 1 === Basic Prefix Sum === Original: [10, 20, 10, 5, 15] Prefix sum: [10, 30, 40, 45, 60] --- Menu --- ... Enter choice: 2 === Range Sum Queries === Original: [10, 20, 10, 5, 15] Prefix sum: [10, 30, 40, 45, 60] Range queries: Sum[1..3] = 35 Sum[0..4] = 60 Sum[2..2] = 10 Sum[3..4] = 20 --- Menu --- ... Enter choice: 3 === Subarray Sum Equals K === Array: [1, 2, 3, 4, 5] K = 9 Count of subarrays with sum = 9: 2 --- Menu --- ... Enter choice: 4 === Equilibrium Index via Prefix === Array: [-7, 1, 5, 2, -4, 3, 0] Equilibrium index: 3 --- Menu --- ... Enter choice: 5 === 2D Prefix Sum === Matrix: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Rectangle queries: (0,0)-(1,1) = 16 (1,1)-(2,2) = 40 (0,0)-(3,4) = 210 (2,2)-(3,3) = 52 --- Menu --- ... Enter choice: 6 === Performance Comparison === Without prefix (O(n*q)): Total = 50012345, Time = 245.678 ms With prefix (O(n+q)): Total = 50012345, Time = 1.234 ms

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow

Prefix sums grow large quickly, exceeding int for large arrays.

c

long long *prefix = malloc(n * sizeof(long long)); /* Use long long */

2. Off-by-One in Range Queries

For 0-indexed prefix, sum(L, R) = prefix[R] - prefix[L-1] when L > 0.

c

if (L == 0) return prefix[R]; /* Special case */

Solution: Use 1-indexed prefix array (prefix[0] = 0) for cleaner code.

3. Empty Array

Return NULL or handle n == 0 explicitly.

c

if (n <= 0) return NULL;

4. Single Element

Prefix array has one element equal to arr[0].

c

int arr[] = {42}; /* prefix = [42] */

5. Negative Values

Prefix sums work with negatives; range sums can be negative.

6. 2D Prefix Off-by-One

Using (ROWS+1) × (COLS+1) avoids boundary checks in rectangle queries.

c

long long prefix[ROWS + 1][COLS + 1]; /* Convenient */

7. Memory Leaks

Free dynamically allocated prefix arrays.

c

free(prefix);

8. Subarray Sum with Negatives

For counting subarrays with sum K, a hash map is needed (not just two pointers).

9. Mutating Original Array

Prefix sum does not modify the original array; keep both if needed.

10. Memory Considerations

  • 1D prefix: O(n) extra space.
  • 2D prefix: O(rows × cols) extra space.
  • Use long long for sums.
  • Free dynamically allocated prefix arrays.

Summary & Best Practices

Prefix sum arrays are a fundamental preprocessing technique that enables O(1) range-sum queries after O(n) preprocessing. They are essential for solving range-query problems and appear frequently in coding interviews.

Key Takeaways

  • prefix[i] = sum(arr[0..i]).
  • Range sum [L, R] = prefix[R] - prefix[L-1] (with L>0 special case).
  • 1-indexed prefix array simplifies range queries.
  • 2D prefix enables rectangle sum queries.
  • Requires O(n) preprocessing and O(n) extra space.
  • Works with negative values.
  • Use long long to prevent overflow.

Best Practices

  1. Use long long for prefix sums to prevent overflow.
  2. Use 1-indexed prefix array for cleaner range queries.
  3. Use (rows+1) × (cols+1) for 2D prefix to avoid boundary checks.
  4. Free dynamically allocated prefix arrays.
  5. Handle empty arrays and single-element arrays.
  6. Test with negative values and large arrays.
  7. Combine with hash maps for subarray-sum problems with negatives.
  8. Document range conventions (0-indexed vs 1-indexed).
  9. Profile for large arrays with many queries.
  10. Verify with brute force during testing.

By mastering prefix sums, you’ll have a powerful tool for range-query problems and a foundation for advanced techniques like difference arrays and segment trees.

Master the Two Pointers Technique in C: Two Sum, closest pair, palindrome, remove duplicates, container water, and three sum. O(n) solutions with code.

Two Pointers Technique in C: Complete Guide

Two Pointers Technique in C: A Comprehensive Tutorial

The Two Pointers Technique is a simple yet powerful strategy where you use two indices (pointers) that traverse a data structure—such as an array, list, or string—either toward each other or in the same direction to solve problems more efficiently. It is one of the most versatile algorithmic patterns, appearing frequently in coding interviews and competitive programming.

This tutorial provides an in-depth exploration of the Two Pointers Technique in C: core concepts, when to use it, the classic Two Sum problem, opposite-direction and same-direction variants, sliding window, fast/slow pointers, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Two Pointers Technique?

The Two Pointers Technique uses two index variables (pointers) to traverse a data structure, typically moving them based on a condition. This reduces time complexity from O(n²) to O(n) in many problems.

Why Use Two Pointers?

  • Efficiency: Reduces O(n²) to O(n) for many problems.
  • Space efficiency: Typically O(1) extra space.
  • Versatility: Applies to arrays, strings, linked lists.
  • Simplicity: Once mastered, the pattern is easy to recognize.
  • Interview favorite: Common in technical interviews.

When to Use Two Pointers

ScenarioExample Problem
Sorted inputTwo Sum in sorted array, pair with target sum
Pairs or subarraysLongest substring without repeats, palindrome check
Sliding windowSmallest subarray with sum ≥ K, max consecutive ones
Linked lists (slow/fast)Cycle detection, middle node, palindrome

Two Main Directions

DirectionPointers StartMove Toward
Opposite directionBoth endsEach other
Same directionSame endSame direction (fast/slow)

Syntax & Core Mechanics

Opposite-Direction Pattern

Pointers start at both ends and move toward each other.

Template:

c

int left = 0, right = n - 1; while (left < right) { /* Process arr[left], arr[right] */ if (condition) { left++; } else { right--; } }

Same-Direction Pattern (Slow/Fast)

Both pointers move in the same direction, with one ahead of the other.

Template:

c

int slow = 0; for (int fast = 0; fast < n; fast++) { if (condition(arr[fast])) { /* Process with slow */ slow++; } }

Classic Problem: Two Sum in Sorted Array

Problem: Given a sorted array and target, find if any pair sums to target.

Naive: O(n²) — nested loops.
Two Pointers: O(n) — pointers from both ends.

Key insight:

  • If arr[left] + arr[right] < target: increase sum by moving left right.
  • If arr[left] + arr[right] > target: decrease sum by moving right left.
  • If equal: found the pair.

Naive Approach

c

#include <stdio.h> #include <stdbool.h> bool twoSumNaive(const int arr[], int n, int target) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] + arr[j] == target) { return true; } } } return false; } int main(void) { int arr[] = {10, 20, 35, 50}; int n = sizeof(arr) / sizeof(arr[0]); int target = 70; printf("%s\n", twoSumNaive(arr, n, target) ? "true" : "false"); return 0; }

Expected output:

text

true

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

Two Pointers Approach

c

#include <stdio.h> #include <stdbool.h> bool twoSum(const int arr[], int n, int target) { int left = 0, right = n - 1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) { return true; } else if (sum < target) { left++; /* Need larger sum */ } else { right--; /* Need smaller sum */ } } return false; } int main(void) { int arr[] = {10, 20, 35, 50}; int n = sizeof(arr) / sizeof(arr[0]); int target = 70; printf("%s\n", twoSum(arr, n, target) ? "true" : "false"); return 0; }

Expected output:

text

true

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

Why It Works (Correctness Proof)

Case 1: sum < target, move left++

  • arr[left] is too small to pair with any element ≤ arr[right].
  • Skipping it is safe because no valid pair can include it.

Case 2: sum > target, move right--

  • arr[right] is too large to pair with any element ≥ arr[left].
  • Skipping it is safe.

Case 3: sum == target

  • Pair found.

Since we never skip a valid pair, the algorithm is correct.

Same-Direction Example: Remove Duplicates from Sorted Array

c

#include <stdio.h> int removeDuplicates(int arr[], int n) { if (n <= 1) return n; int slow = 0; for (int fast = 1; fast < n; fast++) { if (arr[fast] != arr[slow]) { slow++; arr[slow] = arr[fast]; } } return slow + 1; /* New length */ } int main(void) { int arr[] = {1, 1, 2, 2, 3, 4, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int newLen = removeDuplicates(arr, n); printf("New length: %d\n", newLen); printf("Array: "); for (int i = 0; i < newLen; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

New length: 5 Array: 1 2 3 4 5

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

Palindrome Check with Two Pointers

c

#include <stdio.h> #include <string.h> #include <stdbool.h> bool isPalindrome(const char str[]) { int left = 0; int right = strlen(str) - 1; while (left < right) { if (str[left] != str[right]) { return false; } left++; right--; } return true; } int main(void) { printf("racecar: %s\n", isPalindrome("racecar") ? "true" : "false"); printf("hello: %s\n", isPalindrome("hello") ? "true" : "false"); return 0; }

Expected output:

text

racecar: true hello: false

Full Practical Code Example with Explanation

This program demonstrates multiple Two Pointers applications in a single interactive tool.

c

/** * two_pointers_demo.c * Demonstrates the Two Pointers Technique across multiple problems. * Covers Two Sum, closest pair, palindrome, remove duplicates, * move zeros, container with most water, and three sum. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <errno.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ bool twoSum(const int arr[], int n, int target); void twoSumIndices(const int arr[], int n, int target, int *i, int *j); bool closestTwoSum(const int arr[], int n, int target, int *bestI, int *bestJ); bool isPalindrome(const char str[]); int removeDuplicates(int arr[], int n); void moveZerosToEnd(int arr[], int n); int maxWater(const int height[], int n); int threeSumCount(int arr[], int n, int target); int compareInts(const void *a, const void *b); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demoTweSum(void); void demoClosestPair(void); void demoPalindrome(void); void demoRemoveDuplicates(void); void demoMoveZeros(void); void demoContainerWater(void); void demoThreeSum(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Two Pointers Technique Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Two Sum in Sorted Array\n"); printf("2. Closest Pair to Target\n"); printf("3. Palindrome Check\n"); printf("4. Remove Duplicates from Sorted Array\n"); printf("5. Move Zeros to End\n"); printf("6. Container with Most Water\n"); printf("7. Three Sum Count\n"); printf("8. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demoTweSum(); break; case 2: demoClosestPair(); break; case 3: demoPalindrome(); break; case 4: demoRemoveDuplicates(); break; case 5: demoMoveZeros(); break; case 6: demoContainerWater(); break; case 7: demoThreeSum(); break; case 8: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Two Sum: returns true if pair exists */ bool twoSum(const int arr[], int n, int target) { int left = 0, right = n - 1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) return true; if (sum < target) left++; else right--; } return false; } /* Two Sum: returns indices */ void twoSumIndices(const int arr[], int n, int target, int *i, int *j) { int left = 0, right = n - 1; *i = -1; *j = -1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) { *i = left; *j = right; return; } if (sum < target) left++; else right--; } } /* Closest pair to target */ bool closestTwoSum(const int arr[], int n, int target, int *bestI, int *bestJ) { if (n < 2) return false; int left = 0, right = n - 1; int bestDiff = abs(arr[0] + arr[n - 1] - target); *bestI = 0; *bestJ = n - 1; while (left < right) { int sum = arr[left] + arr[right]; int diff = abs(sum - target); if (diff < bestDiff) { bestDiff = diff; *bestI = left; *bestJ = right; } if (sum < target) left++; else if (sum > target) right--; else return true; /* Exact match */ } return false; } /* Palindrome check */ bool isPalindrome(const char str[]) { int left = 0; int right = (int)strlen(str) - 1; while (left < right) { if (str[left] != str[right]) return false; left++; right--; } return true; } /* Remove duplicates */ int removeDuplicates(int arr[], int n) { if (n <= 1) return n; int slow = 0; for (int fast = 1; fast < n; fast++) { if (arr[fast] != arr[slow]) { slow++; arr[slow] = arr[fast]; } } return slow + 1; } /* Move zeros to end */ void moveZerosToEnd(int arr[], int n) { int slow = 0; for (int fast = 0; fast < n; fast++) { if (arr[fast] != 0) { int temp = arr[slow]; arr[slow] = arr[fast]; arr[fast] = temp; slow++; } } } /* Container with most water */ int maxWater(const int height[], int n) { int left = 0, right = n - 1; int maxArea = 0; while (left < right) { int h = (height[left] < height[right]) ? height[left] : height[right]; int width = right - left; int area = h * width; if (area > maxArea) maxArea = area; if (height[left] < height[right]) left++; else right--; } return maxArea; } /* Three Sum Count: count triplets summing to target */ int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } int threeSumCount(int arr[], int n, int target) { qsort(arr, n, sizeof(int), compareInts); int count = 0; for (int i = 0; i < n - 2; i++) { int left = i + 1, right = n - 1; while (left < right) { int sum = arr[i] + arr[left] + arr[right]; if (sum == target) { count++; left++; right--; } else if (sum < target) { left++; } else { right--; } } } return count; } /* Print array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demos */ void demoTweSum(void) { printf("\n=== Two Sum in Sorted Array ===\n"); int arr1[] = {10, 20, 35, 50}; int arr2[] = {10, 20, 30}; int arr3[] = {-8, 1, 4, 6, 10, 45}; printf("arr1: "); printArray(arr1, 4); printf(" Target 70: %s\n", twoSum(arr1, 4, 70) ? "true" : "false"); printf("arr2: "); printArray(arr2, 3); printf(" Target 70: %s\n", twoSum(arr2, 3, 70) ? "true" : "false"); printf("arr3: "); printArray(arr3, 6); printf(" Target 16: %s\n", twoSum(arr3, 6, 16) ? "true" : "false"); int i, j; twoSumIndices(arr1, 4, 70, &i, &j); if (i != -1) { printf("Pair in arr1: (%d, %d) at indices (%d, %d)\n", arr1[i], arr1[j], i, j); } } void demoClosestPair(void) { printf("\n=== Closest Pair to Target ===\n"); int arr[] = {1, 3, 5, 8, 12, 15}; int target = 10; int i, j; printf("Array: "); printArray(arr, 6); printf("Target: %d\n", target); closestTwoSum(arr, 6, target, &i, &j); printf("Closest pair: (%d, %d) with sum %d\n", arr[i], arr[j], arr[i] + arr[j]); } void demoPalindrome(void) { printf("\n=== Palindrome Check ===\n"); const char *tests[] = {"racecar", "hello", "madam", "level", "abcba"}; int count = 5; for (int i = 0; i < count; i++) { printf("\"%s\": %s\n", tests[i], isPalindrome(tests[i]) ? "palindrome" : "not palindrome"); } } void demoRemoveDuplicates(void) { printf("\n=== Remove Duplicates from Sorted Array ===\n"); int arr[] = {1, 1, 2, 2, 3, 4, 4, 5}; int n = 8; printf("Original: "); printArray(arr, n); int newLen = removeDuplicates(arr, n); printf("New length: %d\n", newLen); printf("Array: "); printArray(arr, newLen); } void demoMoveZeros(void) { printf("\n=== Move Zeros to End ===\n"); int arr[] = {0, 1, 0, 3, 12, 0, 5}; int n = 7; printf("Before: "); printArray(arr, n); moveZerosToEnd(arr, n); printf("After: "); printArray(arr, n); } void demoContainerWater(void) { printf("\n=== Container with Most Water ===\n"); int height[] = {1, 8, 6, 2, 5, 4, 8, 3, 7}; int n = 9; printf("Heights: "); printArray(height, n); printf("Max water: %d\n", maxWater(height, n)); } void demoThreeSum(void) { printf("\n=== Three Sum Count ===\n"); int arr[] = {-1, 0, 1, 2, -1, -4}; int n = 6; int target = 0; printf("Array: "); printArray(arr, n); printf("Target: %d\n", target); int count = threeSumCount(arr, n, target); printf("Triplets summing to %d: %d\n", target, count); } /* Read integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) return -1; *outValue = (int)val; return 0; }

Expected Output (sample run)

text

=== Two Pointers Technique Demo === --- Menu --- 1. Two Sum in Sorted Array 2. Closest Pair to Target 3. Palindrome Check 4. Remove Duplicates from Sorted Array 5. Move Zeros to End 6. Container with Most Water 7. Three Sum Count 8. Exit Enter choice: 1 === Two Sum in Sorted Array === arr1: [10, 20, 35, 50] Target 70: true arr2: [10, 20, 30] Target 70: false arr3: [-8, 1, 4, 6, 10, 45] Target 16: true Pair in arr1: (20, 50) at indices (1, 3) --- Menu --- ... Enter choice: 5 === Move Zeros to End === Before: [0, 1, 0, 3, 12, 0, 5] After: [1, 3, 12, 5, 0, 0, 0] --- Menu --- ... Enter choice: 6 === Container with Most Water === Heights: [1, 8, 6, 2, 5, 4, 8, 3, 7] Max water: 49 --- Menu --- ... Enter choice: 7 === Three Sum Count === Array: [-1, 0, 1, 2, -1, -4] Target: 0 Triplets summing to 0: 2

Common Pitfalls, Edge Cases & Memory Considerations

1. Unsorted Array in Two Sum

The two-pointer approach requires a sorted array. Sort first (O(n log n)) or use a hash map.

2. Overflow in Sum

arr[left] + arr[right] can overflow int for large values.

c

long long sum = (long long)arr[left] + arr[right];

3. Off-by-One in Loop Condition

Use left < right (not <=) for opposite-direction pointers. Using <= causes self-comparison.

4. Move Wrong Pointer

Moving the wrong pointer breaks correctness and can cause infinite loops.

c

if (sum < target) left++; /* Correct */ if (sum > target) right--; /* Correct */

5. Duplicate Handling in Three Sum

For counting unique triplets, skip duplicates.

c

if (i > 0 && arr[i] == arr[i-1]) continue;

6. Palindrome with Non-Alphanumeric Characters

Real-world palindrome checks ignore punctuation, spaces, and case.

c

while (left < right && !isalnum(str[left])) left++; while (left < right && !isalnum(str[right])) right--; if (tolower(str[left]) != tolower(str[right])) return false;

7. Container with Most Water: Move Correct Pointer

Always move the pointer at the shorter line (to potentially find a taller one).

8. Empty or Single-Element Arrays

Return early for n < 2.

9. Same-Direction: Slow/Fast Initialization

Ensure slow and fast don’t cross unintentionally.

10. Memory Considerations

  • Two pointers uses O(1) extra space.
  • Sorting (when needed) is O(n log n) time, O(1) or O(n) space.
  • No dynamic allocation in typical two-pointer problems.
  • Use size_t for large array indices.

Summary & Best Practices

The Two Pointers Technique is a versatile pattern that reduces many O(n²) problems to O(n). Mastering it opens the door to efficient solutions across arrays, strings, and linked lists.

Key Takeaways

  • Two pointers move either toward each other or in the same direction.
  • Opposite direction: Two Sum, palindrome, container with water.
  • Same direction (slow/fast): Remove duplicates, move zeros.
  • Fast/slow on linked lists: Cycle detection, middle node.
  • Sliding window: Subarray/substring problems.
  • Requires sorted input for most opposite-direction problems.

Best Practices

  1. Identify the pattern (opposite or same direction) before coding.
  2. Sort the array if the problem requires it (Two Sum, Three Sum).
  3. Use long long for sum computations to avoid overflow.
  4. Use left < right (not <=) for opposite-direction loops.
  5. Move the correct pointer based on the comparison.
  6. Skip duplicates in Three Sum and similar problems.
  7. Handle edge cases: empty, single element, all equal.
  8. Verify correctness with brute force during testing.
  9. Document the invariant (what’s guaranteed at each step).
  10. Profile for large inputs.

By mastering the Two Pointers Technique, you’ll solve a wide range of array and string problems efficiently and gain a powerful tool for coding interviews.

Master 7 binary search applications in C: sqrt, first/last position, count 1's, unbounded search, rotated arrays, and aggressive cows. Complete code examples.

7 Binary Search Applications in C: Complete Guide

Binary Search Applications in C: A Comprehensive Tutorial

Binary search is one of the most powerful algorithmic techniques in computer science. Beyond simple searching, it powers a wide range of problems—from mathematical computations to optimization and rotated array searching. This tutorial covers seven important binary search applications in C, each with complete, portable implementations.

The seven topics are:

  1. Square Root of an Integer
  2. First and Last Positions in a Sorted Array
  3. Count 1’s in a Sorted Binary Array
  4. Unbounded Binary Search
  5. Minimum in a Sorted Rotated Array
  6. Search in a Sorted Rotated Array
  7. Aggressive Cows (Optimization Problem)

Conceptual Introduction & Fundamentals

The Binary Search Template

All binary search variants share a common structure:

c

int low = 0, high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; /* Overflow-safe */ if (condition(mid)) { /* Narrow search to one half */ } else { /* Narrow search to other half */ } } return answer;

Key Prerequisites

RequirementDescription
Sorted/MonotonicThe search space must be monotonic
Random AccessO(1) access to any element
Overflow-safe midUse low + (high - low) / 2
TerminationEnsure low or high always moves

Applications Overview

ProblemTime ComplexitySpace Complexity
Square RootO(log n)O(1)
First/Last PositionO(log n)O(1)
Count 1’s in Binary ArrayO(log n)O(1)
Unbounded SearchO(log n)O(1)
Minimum in Rotated ArrayO(log n)O(1)
Search in Rotated ArrayO(log n)O(1)
Aggressive CowsO(n log n)O(1)

Syntax & Core Mechanics

1. Square Root of an Integer

Problem: Find the integer square root of n (floor of √n) without using built-in sqrt().

Idea: Binary search on the answer [0, n]. Find the largest x such that x² ≤ n.

c

#include <stdio.h> long long integerSqrt(long long n) { if (n < 0) return -1; /* Undefined for negatives */ if (n == 0 || n == 1) return n; long long low = 1, high = n, result = 1; while (low <= high) { long long mid = low + (high - low) / 2; /* Avoid overflow: use division instead of mid*mid */ if (mid <= n / mid) { result = mid; low = mid + 1; /* Try larger */ } else { high = mid - 1; } } return result; } int main(void) { long long tests[] = {0, 1, 4, 8, 16, 25, 100, 999999999}; int count = sizeof(tests) / sizeof(tests[0]); for (int i = 0; i < count; i++) { printf("sqrt(%lld) = %lld\n", tests[i], integerSqrt(tests[i])); } return 0; }

Expected output:

text

sqrt(0) = 0 sqrt(1) = 1 sqrt(4) = 2 sqrt(8) = 2 sqrt(16) = 4 sqrt(25) = 5 sqrt(100) = 10 sqrt(999999999) = 31622

Key insight: Use mid <= n / mid instead of mid * mid <= n to avoid overflow.

2. First and Last Positions in a Sorted Array

Problem: Find the first and last occurrence of a target in a sorted array (with duplicates).

Idea: Two modified binary searches — one biased left (first), one biased right (last).

c

#include <stdio.h> int findFirst(const int arr[], int n, int target) { int low = 0, high = n - 1, result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; high = mid - 1; /* Bias left */ } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } int findLast(const int arr[], int n, int target) { int low = 0, high = n - 1, result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; low = mid + 1; /* Bias right */ } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } int main(void) { int arr[] = {1, 2, 2, 2, 3, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int target = 2; int first = findFirst(arr, n, target); int last = findLast(arr, n, target); if (first == -1) { printf("%d not found\n", target); } else { printf("First occurrence of %d: %d\n", target, first); printf("Last occurrence of %d: %d\n", target, last); printf("Count: %d\n", last - first + 1); } return 0; }

Expected output:

text

First occurrence of 2: 1 Last occurrence of 2: 3 Count: 3

3. Count 1’s in a Sorted Binary Array

Problem: Count the number of 1’s in a sorted array containing only 0’s and 1’s.

Idea: Find the first occurrence of 1. Count = n - firstOneIndex.

c

#include <stdio.h> int countOnes(const int arr[], int n) { int low = 0, high = n - 1, firstOne = n; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == 1) { firstOne = mid; high = mid - 1; /* Search left for earlier 1 */ } else { low = mid + 1; /* 0: search right */ } } return n - firstOne; } int main(void) { int arr1[] = {0, 0, 0, 1, 1, 1, 1}; int arr2[] = {0, 0, 0, 0}; int arr3[] = {1, 1, 1, 1}; int arr4[] = {0}; printf("Test 1: %d\n", countOnes(arr1, 7)); printf("Test 2: %d\n", countOnes(arr2, 4)); printf("Test 3: %d\n", countOnes(arr3, 4)); printf("Test 4: %d\n", countOnes(arr4, 1)); return 0; }

Expected output:

text

Test 1: 4 Test 2: 0 Test 3: 4 Test 4: 0

4. Unbounded Binary Search

Problem: Search for a target in an array of infinite size (or unknown size), where all elements are sorted.

Idea: Double the index high until arr[high] >= target, then perform standard binary search.

c

#include <stdio.h> /* Simulated infinite array via a function */ int getValue(int idx) { static int data[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29}; int size = sizeof(data) / sizeof(data[0]); if (idx >= size) return 1000000; /* Sentinel */ return data[idx]; } int unboundedSearch(int target) { if (getValue(0) == target) return 0; int high = 1; while (getValue(high) < target) { if (high > 1000000) return -1; /* Safety bound */ high *= 2; } int low = high / 2; while (low <= high) { int mid = low + (high - low) / 2; int val = getValue(mid); if (val == target) return mid; if (val < target) low = mid + 1; else high = mid - 1; } return -1; } int main(void) { int targets[] = {1, 9, 29, 30}; int count = sizeof(targets) / sizeof(targets[0]); for (int i = 0; i < count; i++) { int result = unboundedSearch(targets[i]); if (result == -1) printf("Target %d: Not found\n", targets[i]); else printf("Target %d: Index %d\n", targets[i], result); } return 0; }

Expected output:

text

Target 1: Index 0 Target 9: Index 4 Target 29: Index 14 Target 30: Not found

5. Minimum in a Sorted Rotated Array

Problem: Find the minimum element in a sorted array rotated at an unknown pivot.

Idea: Compare arr[mid] with arr[high]; narrow the search.

c

#include <stdio.h> int findMinRotated(const int arr[], int n) { if (n <= 0) return -1; int low = 0, high = n - 1; while (low < high) { /* If the range is already sorted, minimum is at low */ if (arr[low] < arr[high]) return arr[low]; int mid = low + (high - low) / 2; if (arr[mid] > arr[high]) { low = mid + 1; /* Minimum in right half */ } else { high = mid; /* Minimum at mid or left half */ } } return arr[low]; } int main(void) { int arr1[] = {5, 6, 1, 2, 3, 4}; int arr2[] = {3, 1, 2}; int arr3[] = {4, 2, 3}; int arr4[] = {1, 2, 3, 4}; printf("Min 1: %d\n", findMinRotated(arr1, 6)); printf("Min 2: %d\n", findMinRotated(arr2, 3)); printf("Min 3: %d\n", findMinRotated(arr3, 3)); printf("Min 4: %d\n", findMinRotated(arr4, 4)); return 0; }

Expected output:

text

Min 1: 1 Min 2: 1 Min 3: 2 Min 4: 1

6. Search in a Sorted Rotated Array

Problem: Search for a target in a sorted rotated array.

Idea: One half is always sorted. Determine which half and search accordingly.

c

#include <stdio.h> int searchRotated(const int arr[], int n, int target) { int low = 0, high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; /* Left half is sorted */ if (arr[low] <= arr[mid]) { if (target >= arr[low] && target < arr[mid]) { high = mid - 1; } else { low = mid + 1; } } /* Right half is sorted */ else { if (target > arr[mid] && target <= arr[high]) { low = mid + 1; } else { high = mid - 1; } } } return -1; } int main(void) { int arr[] = {5, 6, 7, 8, 9, 1, 2, 3}; int n = sizeof(arr) / sizeof(arr[0]); int targets[] = {5, 1, 3, 8, 10, 0}; int count = sizeof(targets) / sizeof(targets[0]); for (int i = 0; i < count; i++) { int result = searchRotated(arr, n, targets[i]); if (result == -1) printf("Target %d: Not found\n", targets[i]); else printf("Target %d: Index %d\n", targets[i], result); } return 0; }

Expected output:

text

Target 5: Index 0 Target 1: Index 5 Target 3: Index 7 Target 8: Index 3 Target 10: Not found Target 0: Not found

7. Aggressive Cows (Optimization)

Problem: Place k cows in n stalls (given positions) so that the minimum distance between any two cows is maximized.

Idea: Binary search on the answer (distance). For each candidate distance, check if k cows can be placed with at least that gap.

c

#include <stdio.h> #include <stdlib.h> int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } /* Check if k cows can be placed with minimum distance >= dist */ int canPlace(const int stalls[], int n, int k, int dist) { int count = 1; /* Place first cow at stalls[0] */ int lastPos = stalls[0]; for (int i = 1; i < n; i++) { if (stalls[i] - lastPos >= dist) { count++; lastPos = stalls[i]; if (count >= k) return 1; } } return 0; } int aggressiveCows(int stalls[], int n, int k) { if (k > n) return -1; qsort(stalls, n, sizeof(int), compareInts); int low = 1; int high = stalls[n - 1] - stalls[0]; int result = 0; while (low <= high) { int mid = low + (high - low) / 2; if (canPlace(stalls, n, k, mid)) { result = mid; /* Try larger distance */ low = mid + 1; } else { high = mid - 1; /* Try smaller distance */ } } return result; } int main(void) { int stalls1[] = {1, 2, 4, 8, 9}; int stalls2[] = {1, 2, 3, 4, 5}; printf("Aggressive Cows (k=3, stalls=[1,2,4,8,9]): %d\n", aggressiveCows(stalls1, 5, 3)); printf("Aggressive Cows (k=3, stalls=[1,2,3,4,5]): %d\n", aggressiveCows(stalls2, 5, 3)); printf("Aggressive Cows (k=2, stalls=[1,2,4,8,9]): %d\n", aggressiveCows(stalls1, 5, 2)); return 0; }

Expected output:

text

Aggressive Cows (k=3, stalls=[1,2,4,8,9]): 3 Aggressive Cows (k=3, stalls=[1,2,3,4,5]): 2 Aggressive Cows (k=2, stalls=[1,2,4,8,9]): 8

Full Practical Code Example with Explanation

This program combines all seven binary search applications into a single interactive tool.

c

/** * binary_search_applications.c * Demonstrates seven binary search applications in C. * Covers sqrt, first/last occurrence, count 1's, unbounded search, * rotated array min/search, and aggressive cows. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ long long integerSqrt(long long n); int findFirst(const int arr[], int n, int target); int findLast(const int arr[], int n, int target); int countOnes(const int arr[], int n); int unboundedSearch(int target); int findMinRotated(const int arr[], int n); int searchRotated(const int arr[], int n, int target); int aggressiveCows(int stalls[], int n, int k); int compareInts(const void *a, const void *b); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demoSquareRoot(void); void demoFirstLast(void); void demoCountOnes(void); void demoUnbounded(void); void demoRotatedMin(void); void demoRotatedSearch(void); void demoAggressiveCows(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Binary Search Applications Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Square Root of Integer\n"); printf("2. First and Last Position\n"); printf("3. Count 1's in Binary Array\n"); printf("4. Unbounded Binary Search\n"); printf("5. Minimum in Rotated Array\n"); printf("6. Search in Rotated Array\n"); printf("7. Aggressive Cows\n"); printf("8. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demoSquareRoot(); break; case 2: demoFirstLast(); break; case 3: demoCountOnes(); break; case 4: demoUnbounded(); break; case 5: demoRotatedMin(); break; case 6: demoRotatedSearch(); break; case 7: demoAggressiveCows(); break; case 8: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* 1. Integer square root */ long long integerSqrt(long long n) { if (n < 0) return -1; if (n == 0 || n == 1) return n; long long low = 1, high = n, result = 1; while (low <= high) { long long mid = low + (high - low) / 2; if (mid <= n / mid) { result = mid; low = mid + 1; } else { high = mid - 1; } } return result; } /* 2. First occurrence */ int findFirst(const int arr[], int n, int target) { int low = 0, high = n - 1, result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; high = mid - 1; } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } /* 2. Last occurrence */ int findLast(const int arr[], int n, int target) { int low = 0, high = n - 1, result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; low = mid + 1; } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } /* 3. Count 1's */ int countOnes(const int arr[], int n) { int low = 0, high = n - 1, firstOne = n; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == 1) { firstOne = mid; high = mid - 1; } else { low = mid + 1; } } return n - firstOne; } /* 4. Unbounded search */ int getValue(int idx) { static int data[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29}; int size = sizeof(data) / sizeof(data[0]); if (idx < 0 || idx >= size) return 1000000; return data[idx]; } int unboundedSearch(int target) { if (getValue(0) == target) return 0; int high = 1; while (getValue(high) < target) { if (high > 1000000) return -1; high *= 2; } int low = high / 2; while (low <= high) { int mid = low + (high - low) / 2; int val = getValue(mid); if (val == target) return mid; if (val < target) low = mid + 1; else high = mid - 1; } return -1; } /* 5. Minimum in rotated array */ int findMinRotated(const int arr[], int n) { if (n <= 0) return -1; int low = 0, high = n - 1; while (low < high) { if (arr[low] < arr[high]) return arr[low]; int mid = low + (high - low) / 2; if (arr[mid] > arr[high]) low = mid + 1; else high = mid; } return arr[low]; } /* 6. Search in rotated array */ int searchRotated(const int arr[], int n, int target) { int low = 0, high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; if (arr[low] <= arr[mid]) { if (target >= arr[low] && target < arr[mid]) high = mid - 1; else low = mid + 1; } else { if (target > arr[mid] && target <= arr[high]) low = mid + 1; else high = mid - 1; } } return -1; } /* 7. Aggressive Cows */ int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } int canPlace(const int stalls[], int n, int k, int dist) { int count = 1; int lastPos = stalls[0]; for (int i = 1; i < n; i++) { if (stalls[i] - lastPos >= dist) { count++; lastPos = stalls[i]; if (count >= k) return 1; } } return 0; } int aggressiveCows(int stalls[], int n, int k) { if (k > n) return -1; qsort(stalls, n, sizeof(int), compareInts); int low = 1; int high = stalls[n - 1] - stalls[0]; int result = 0; while (low <= high) { int mid = low + (high - low) / 2; if (canPlace(stalls, n, k, mid)) { result = mid; low = mid + 1; } else { high = mid - 1; } } return result; } /* Print array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demos */ void demoSquareRoot(void) { printf("\n=== Integer Square Root ===\n"); long long tests[] = {0, 1, 4, 8, 16, 25, 100, 999999999}; int count = sizeof(tests) / sizeof(tests[0]); for (int i = 0; i < count; i++) { printf("sqrt(%lld) = %lld\n", tests[i], integerSqrt(tests[i])); } } void demoFirstLast(void) { printf("\n=== First and Last Position ===\n"); int arr[] = {1, 2, 2, 2, 3, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); printf("Array: "); printArray(arr, n); int target = 2; int first = findFirst(arr, n, target); int last = findLast(arr, n, target); if (first == -1) printf("%d not found\n", target); else printf("First: %d, Last: %d, Count: %d\n", first, last, last - first + 1); } void demoCountOnes(void) { printf("\n=== Count 1's in Sorted Binary Array ===\n"); int arr1[] = {0, 0, 0, 1, 1, 1, 1}; int arr2[] = {0, 0, 0, 0}; int arr3[] = {1, 1, 1, 1}; printf("arr1: "); printArray(arr1, 7); printf(" Count 1's: %d\n", countOnes(arr1, 7)); printf("arr2: "); printArray(arr2, 4); printf(" Count 1's: %d\n", countOnes(arr2, 4)); printf("arr3: "); printArray(arr3, 4); printf(" Count 1's: %d\n", countOnes(arr3, 4)); } void demoUnbounded(void) { printf("\n=== Unbounded Binary Search ===\n"); int targets[] = {1, 9, 29, 30}; int count = sizeof(targets) / sizeof(targets[0]); for (int i = 0; i < count; i++) { int r = unboundedSearch(targets[i]); if (r == -1) printf("Target %d: Not found\n", targets[i]); else printf("Target %d: Index %d\n", targets[i], r); } } void demoRotatedMin(void) { printf("\n=== Minimum in Sorted Rotated Array ===\n"); int arr1[] = {5, 6, 1, 2, 3, 4}; int arr2[] = {3, 1, 2}; int arr3[] = {4, 2, 3}; int arr4[] = {1, 2, 3, 4}; printf("arr1: "); printArray(arr1, 6); printf(" Min: %d\n", findMinRotated(arr1, 6)); printf("arr2: "); printArray(arr2, 3); printf(" Min: %d\n", findMinRotated(arr2, 3)); printf("arr3: "); printArray(arr3, 3); printf(" Min: %d\n", findMinRotated(arr3, 3)); printf("arr4: "); printArray(arr4, 4); printf(" Min: %d\n", findMinRotated(arr4, 4)); } void demoRotatedSearch(void) { printf("\n=== Search in Sorted Rotated Array ===\n"); int arr[] = {5, 6, 7, 8, 9, 1, 2, 3}; int n = sizeof(arr) / sizeof(arr[0]); printf("Array: "); printArray(arr, n); int targets[] = {5, 1, 3, 8, 10, 0}; int count = sizeof(targets) / sizeof(targets[0]); for (int i = 0; i < count; i++) { int r = searchRotated(arr, n, targets[i]); if (r == -1) printf("Target %d: Not found\n", targets[i]); else printf("Target %d: Index %d\n", targets[i], r); } } void demoAggressiveCows(void) { printf("\n=== Aggressive Cows ===\n"); int stalls1[] = {1, 2, 4, 8, 9}; int stalls2[] = {1, 2, 3, 4, 5}; printf("stalls=[1,2,4,8,9], k=3 → %d\n", aggressiveCows(stalls1, 5, 3)); printf("stalls=[1,2,3,4,5], k=3 → %d\n", aggressiveCows(stalls2, 5, 3)); printf("stalls=[1,2,4,8,9], k=2 → %d\n", aggressiveCows(stalls1, 5, 2)); } /* Read integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) return -1; *outValue = (int)val; return 0; }

Expected Output (sample run)

text

=== Binary Search Applications Demo === --- Menu --- 1. Square Root of Integer 2. First and Last Position 3. Count 1's in Binary Array 4. Unbounded Binary Search 5. Minimum in Rotated Array 6. Search in Rotated Array 7. Aggressive Cows 8. Exit Enter choice: 1 === Integer Square Root === sqrt(0) = 0 sqrt(1) = 1 sqrt(4) = 2 sqrt(8) = 2 sqrt(16) = 4 sqrt(25) = 5 sqrt(100) = 10 sqrt(999999999) = 31622

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow in mid * mid

Use mid <= n / mid instead of mid * mid <= n.

2. Infinite Loops from Non-Progress

Always update low = mid + 1 or high = mid - 1.

3. Off-by-One in Rotated Arrays

Use high = mid (not mid - 1) when arr[mid] < arr[high] to keep minimum in range.

4. Missing Check for Sorted Subarray

In rotated search, always check which half is sorted first.

5. Unbounded Search Safety

Include an upper bound to prevent infinite doubling.

6. Aggressive Cows: Sorting Required

Always sort stall positions first.

7. First/Last Occurrence Initialization

Initialize result = -1 and update only on match.

8. Empty Arrays

Return sentinel values (-1) for empty inputs.

9. Duplicates in Rotated Arrays

The minimum in rotated array with duplicates can be ambiguous; linear fallback may be needed.

10. Memory Considerations

  • All seven algorithms use O(1) extra space.
  • No dynamic allocation required.
  • Use long long for integer sqrt to avoid overflow.

Summary & Best Practices

Binary search is a versatile technique that extends far beyond simple searching. The seven applications in this tutorial demonstrate its power across mathematical, structural, and optimization problems.

Key Takeaways

ProblemKey Insight
Square RootBinary search on answer with overflow-safe comparison
First/Last PositionBias search direction based on requirement
Count 1’sFind first 1; count = n – index
Unbounded SearchExponential doubling then binary search
Min in RotatedCompare mid with high
Search in RotatedOne half always sorted
Aggressive CowsBinary search on distance with feasibility check

Best Practices

  1. Use low + (high - low) / 2 to avoid overflow.
  2. Ensure monotonicity before applying binary search.
  3. Check both halves’ sorted status in rotated arrays.
  4. Bias first/last occurrence searches appropriately.
  5. Include safety bounds in unbounded search.
  6. Sort input for aggressive cows.
  7. Use long long for large computations (square root).
  8. Handle edge cases: empty, single element, duplicates.
  9. Verify with brute force during testing.
  10. Profile for large inputs.

By mastering these seven applications, you’ll be well-equipped to solve a wide range of binary search problems in competitive programming and technical interviews.

Learn Binary Search in C: iterative, recursive, first/last occurrence, and descending variants. Master O(log n) searching with examples and edge cases.

Binary Search in C: Iterative & Recursive Guide

Binary Search in C: A Comprehensive Tutorial

Binary Search is a fundamental searching algorithm that operates on a sorted or monotonic search space, repeatedly dividing it into halves to find a target value or optimal answer in logarithmic time O(log N). It is one of the most important algorithms in computer science and forms the foundation for many advanced algorithms and data structures.

This tutorial provides an in-depth exploration of Binary Search in C: algorithm principles, iterative and recursive implementations, complexity analysis, practical applications, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is Binary Search?

Binary Search is a divide-and-conquer algorithm that finds the position of a target value within a sorted array. It compares the target with the middle element of the array and eliminates half of the search space based on the comparison.

Why Binary Search Matters

  • Efficiency: O(log n) time — exponentially faster than linear search.
  • Foundation: Basis for many algorithms and data structures (BST, B-trees).
  • Wide applications: Databases, debugging, network routing, machine learning.
  • Interview preparation: Very common coding interview question.
  • Optimization problems: Used in “binary search on answer” problems.

Prerequisites

  1. Sorted data: The array must be sorted (ascending or descending).
  2. Random access: Any element can be accessed in O(1) time (arrays qualify).
  3. Monotonic property: The search space must be monotonic.

How Binary Search Works

For a sorted array and target x:

  1. Find the middle index mid.
  2. If arr[mid] == x, return mid.
  3. If arr[mid] < x, search the right half.
  4. If arr[mid] > x, search the left half.
  5. Repeat until found or search space is empty.

Example:

text

Array: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] Target: 23 Step 1: mid = 4, arr[4] = 16 < 23 → search right [23, 38, 56, 72, 91] Step 2: mid = 7, arr[7] = 56 > 23 → search left [23, 38] Step 3: mid = 5, arr[5] = 23 == 23 → Found at index 5

Complexity Analysis

CaseTime Complexity
BestO(1)
AverageO(log n)
WorstO(log n)

Space:

  • Iterative: O(1)
  • Recursive: O(log n) (call stack)

Syntax & Core Mechanics

Iterative Binary Search

c

#include <stdio.h> int binarySearchIterative(const int arr[], int n, int target) { int low = 0; int high = n - 1; while (low <= high) { /* Avoid overflow: low + (high - low) / 2 */ int mid = low + (high - low) / 2; if (arr[mid] == target) { return mid; } if (arr[mid] < target) { low = mid + 1; /* Search right half */ } else { high = mid - 1; /* Search left half */ } } return -1; /* Not found */ } int main(void) { int arr[] = {2, 3, 4, 10, 40}; int n = sizeof(arr) / sizeof(arr[0]); int target = 10; int result = binarySearchIterative(arr, n, target); if (result == -1) { printf("Element is not present in array\n"); } else { printf("Element is present at index %d\n", result); } return 0; }

Expected output:

text

Element is present at index 3

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

Recursive Binary Search

c

#include <stdio.h> int binarySearchRecursive(const int arr[], int low, int high, int target) { if (low > high) { return -1; /* Not found */ } int mid = low + (high - low) / 2; if (arr[mid] == target) { return mid; } if (arr[mid] > target) { return binarySearchRecursive(arr, low, mid - 1, target); } return binarySearchRecursive(arr, mid + 1, high, target); } int main(void) { int arr[] = {2, 3, 4, 10, 40}; int n = sizeof(arr) / sizeof(arr[0]); int target = 10; int result = binarySearchRecursive(arr, 0, n - 1, target); if (result == -1) { printf("Element is not present in array\n"); } else { printf("Element is present at index %d\n", result); } return 0; }

Expected output:

text

Element is present at index 3

Time: O(log n), Space: O(log n) due to recursion stack

Finding First and Last Occurrence

For arrays with duplicates, binary search can be modified to find first/last occurrence.

c

/* Find first occurrence of target */ int findFirst(const int arr[], int n, int target) { int low = 0, high = n - 1; int result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; high = mid - 1; /* Search left for earlier occurrence */ } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } /* Find last occurrence of target */ int findLast(const int arr[], int n, int target) { int low = 0, high = n - 1; int result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; low = mid + 1; /* Search right for later occurrence */ } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; }

Full Practical Code Example with Explanation

This program demonstrates all binary search variants with verification, performance comparison, and edge cases.

Program: Binary Search Complete Demo

c

/** * binary_search_demo.c * Demonstrates iterative, recursive, and variant binary search approaches. * Includes first/last occurrence, verification, and edge cases. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ int binarySearchIterative(const int arr[], int n, int target); int binarySearchRecursive(const int arr[], int low, int high, int target); int findFirstOccurrence(const int arr[], int n, int target); int findLastOccurrence(const int arr[], int n, int target); int findClosest(const int arr[], int n, int target); int binarySearchDescending(const int arr[], int n, int target); int compareInts(const void *a, const void *b); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Binary Search Complete Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Variants\n"); printf("2. Search in Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n, target; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements (will be sorted):\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } qsort(arr, n, sizeof(int), compareInts); printf("\nSorted array: "); printArray(arr, n); if (readInteger("Enter target: ", &target) != 0) { fprintf(stderr, "Invalid target.\n"); break; } int iter = binarySearchIterative(arr, n, target); int rec = binarySearchRecursive(arr, 0, n - 1, target); int first = findFirstOccurrence(arr, n, target); int last = findLastOccurrence(arr, n, target); printf("Iterative: %d\n", iter); printf("Recursive: %d\n", rec); printf("First occurrence: %d\n", first); printf("Last occurrence: %d\n", last); if (iter != -1) { printf("Verification: %s\n", (iter == rec && iter == first) ? "PASS" : "FAIL"); } break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Iterative binary search */ int binarySearchIterative(const int arr[], int n, int target) { int low = 0; int high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } /* Recursive binary search */ int binarySearchRecursive(const int arr[], int low, int high, int target) { if (low > high) return -1; int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; if (arr[mid] > target) return binarySearchRecursive(arr, low, mid - 1, target); return binarySearchRecursive(arr, mid + 1, high, target); } /* Find first occurrence */ int findFirstOccurrence(const int arr[], int n, int target) { int low = 0, high = n - 1; int result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; high = mid - 1; } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } /* Find last occurrence */ int findLastOccurrence(const int arr[], int n, int target) { int low = 0, high = n - 1; int result = -1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) { result = mid; low = mid + 1; } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } return result; } /* Find closest element to target */ int findClosest(const int arr[], int n, int target) { if (n <= 0) return -1; int low = 0, high = n - 1; if (target <= arr[0]) return 0; if (target >= arr[n - 1]) return n - 1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; if (arr[mid] < target) { if (mid + 1 < n && arr[mid + 1] > target) { return (target - arr[mid] < arr[mid + 1] - target) ? mid : mid + 1; } low = mid + 1; } else { if (mid - 1 >= 0 && arr[mid - 1] < target) { return (target - arr[mid - 1] < arr[mid] - target) ? mid - 1 : mid; } high = mid - 1; } } return -1; } /* Binary search for descending sorted array */ int binarySearchDescending(const int arr[], int n, int target) { int low = 0, high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; if (arr[mid] == target) return mid; if (arr[mid] > target) low = mid + 1; else high = mid - 1; } return -1; } /* Comparison function */ int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates all variants */ void demonstrateApproaches(void) { printf("\n=== Binary Search Variants ===\n"); int arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}; int n = sizeof(arr) / sizeof(arr[0]); printf("\nArray: "); printArray(arr, n); int targets[] = {2, 23, 91, 100}; int numTargets = sizeof(targets) / sizeof(targets[0]); for (int i = 0; i < numTargets; i++) { int t = targets[i]; printf("\nTarget %d:\n", t); printf(" Iterative: %d\n", binarySearchIterative(arr, n, t)); printf(" Recursive: %d\n", binarySearchRecursive(arr, 0, n - 1, t)); printf(" Closest: %d\n", findClosest(arr, n, t)); } /* First/last occurrence with duplicates */ int dupArr[] = {1, 2, 2, 2, 3, 4, 5}; int dupN = sizeof(dupArr) / sizeof(dupArr[0]); printf("\nArray with duplicates: "); printArray(dupArr, dupN); printf("Target 2:\n"); printf(" First: %d\n", findFirstOccurrence(dupArr, dupN, 2)); printf(" Last: %d\n", findLastOccurrence(dupArr, dupN, 2)); /* Descending array */ int descArr[] = {100, 90, 80, 70, 60, 50}; int descN = sizeof(descArr) / sizeof(descArr[0]); printf("\nDescending array: "); printArray(descArr, descN); printf("Target 70:\n"); printf(" Index: %d\n", binarySearchDescending(descArr, descN, 70)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 10000000) ===\n"); int n = 10000000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } for (int i = 0; i < n; i++) { arr[i] = i * 2; } int target = arr[n - 1]; /* Worst case: last element */ clock_t start, end; double timeTaken; /* Linear search for comparison */ start = clock(); int linearResult = -1; for (int i = 0; i < n; i++) { if (arr[i] == target) { linearResult = i; break; } } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Linear search (O(n)): Result = %d, Time = %.3f ms\n", linearResult, timeTaken); /* Binary search */ start = clock(); int binResult = binarySearchIterative(arr, n, target); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Binary search (O(log n)): Result = %d, Time = %.3f ms\n", binResult, timeTaken); printf("\nVerification: %s\n", (linearResult == binResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Empty array */ int arr1[] = {}; printf("Case 1: Empty array\n"); printf(" Result: %d\n", binarySearchIterative(arr1, 0, 5)); /* Case 2: Single element, found */ int arr2[] = {42}; printf("\nCase 2: Single element, found\n"); printf(" Array: "); printArray(arr2, 1); printf(" Result: %d\n", binarySearchIterative(arr2, 1, 42)); /* Case 3: Single element, not found */ int arr3[] = {42}; printf("\nCase 3: Single element, not found\n"); printf(" Array: "); printArray(arr3, 1); printf(" Result: %d\n", binarySearchIterative(arr3, 1, 10)); /* Case 4: Target at start */ int arr4[] = {1, 2, 3, 4, 5}; printf("\nCase 4: Target at start\n"); printf(" Array: "); printArray(arr4, 5); printf(" Result: %d\n", binarySearchIterative(arr4, 5, 1)); /* Case 5: Target at end */ int arr5[] = {1, 2, 3, 4, 5}; printf("\nCase 5: Target at end\n"); printf(" Array: "); printArray(arr5, 5); printf(" Result: %d\n", binarySearchIterative(arr5, 5, 5)); /* Case 6: All same elements */ int arr6[] = {5, 5, 5, 5, 5}; printf("\nCase 6: All same elements\n"); printf(" Array: "); printArray(arr6, 5); printf(" First: %d, Last: %d\n", findFirstOccurrence(arr6, 5, 5), findLastOccurrence(arr6, 5, 5)); /* Case 7: Negative numbers */ int arr7[] = {-10, -5, 0, 5, 10}; printf("\nCase 7: Negative numbers\n"); printf(" Array: "); printArray(arr7, 5); printf(" Target -5: %d\n", binarySearchIterative(arr7, 5, -5)); /* Case 8: Large array overflow check */ int arr8[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}; printf("\nCase 8: Overflow-safe mid calculation\n"); printf(" Array: "); printArray(arr8, 10); printf(" Target 56: %d\n", binarySearchIterative(arr8, 10, 56)); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Binary Search Variants

  1. Iterative: Standard while loop; O(log n) time, O(1) space.
  2. Recursive: Self-calling function; O(log n) time, O(log n) space.
  3. First Occurrence: Modified to find leftmost match.
  4. Last Occurrence: Modified to find rightmost match.
  5. Closest: Finds nearest element to target.
  6. Descending: Adapted for descending arrays.

Expected Output (sample run)

text

=== Binary Search Complete Demo === --- Menu --- 1. Demonstrate All Variants 2. Search in Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Binary Search Variants === Array: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] Target 2: Iterative: 0 Recursive: 0 Closest: 0 Target 23: Iterative: 5 Recursive: 5 Closest: 5 Target 91: Iterative: 9 Recursive: 9 Closest: 9 Target 100: Iterative: -1 Recursive: -1 Closest: 9 Array with duplicates: [1, 2, 2, 2, 3, 4, 5] Target 2: First: 1 Last: 3 Descending array: [100, 90, 80, 70, 60, 50] Target 70: Index: 3 --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 10000000) === Linear search (O(n)): Result = 9999999, Time = 45.234 ms Binary search (O(log n)): Result = 9999999, Time = 0.002 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Result: -1 Case 2: Single element, found Array: [42] Result: 0 Case 3: Single element, not found Array: [42] Result: -1 Case 4: Target at start Array: [1, 2, 3, 4, 5] Result: 0 Case 5: Target at end Array: [1, 2, 3, 4, 5] Result: 4 Case 6: All same elements Array: [5, 5, 5, 5, 5] First: 0, Last: 4 Case 7: Negative numbers Array: [-10, -5, 0, 5, 10] Target -5: 1 Case 8: Overflow-safe mid calculation Array: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] Target 56: 7

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow in Mid Calculation

(low + high) / 2 can overflow for large arrays.

c

int mid = (low + high) / 2; /* BUG: potential overflow */ int mid = low + (high - low) / 2; /* Correct */

2. Infinite Loop from Wrong Update

Forgetting to update low or high causes infinite loops.

c

if (arr[mid] < target) low = mid; /* BUG: no progress */ if (arr[mid] < target) low = mid + 1; /* Correct */

3. Off-by-One in Loop Condition

Using low < high vs low <= high affects termination.

c

while (low <= high) { ... } /* Correct for standard binary search */

4. Unsorted Array

Binary search requires a sorted array; results are undefined otherwise.

Solution: Sort the array first with qsort.

5. Empty Array

Return -1 for empty arrays.

c

if (n <= 0) return -1;

6. Duplicates

Standard binary search returns any occurrence; use variants for first/last.

7. Recursion Depth

Recursive binary search uses O(log n) stack space — safe for typical arrays.

8. Descending Arrays

Adapt comparison logic for descending order.

c

if (arr[mid] > target) low = mid + 1; else high = mid - 1;

9. Search on Monotonic Functions

Binary search works on any monotonic search space, not just arrays.

10. Memory Considerations

  • Iterative: O(1) extra space.
  • Recursive: O(log n) stack space.
  • No dynamic allocation required.
  • Use int for indices; size_t for very large arrays.

Summary & Best Practices

Binary Search is a fundamental O(log n) algorithm for searching in sorted arrays. It is widely applicable and forms the foundation for many advanced algorithms.

Key Takeaways

  • Requires sorted data and random access.
  • Iterative: O(log n) time, O(1) space.
  • Recursive: O(log n) time, O(log n) space.
  • Use low + (high - low) / 2 to avoid overflow.
  • Variants: first/last occurrence, closest match, descending.
  • Applications: databases, debugging, routing, ML tuning.

Best Practices

  1. Use the iterative version for O(1) space.
  2. Use low + (high - low) / 2 to prevent overflow.
  3. Ensure the array is sorted before searching.
  4. Update low = mid + 1 or high = mid - 1 for progress.
  5. Handle edge cases: empty, single element, target not found.
  6. Use variants for first/last occurrence when duplicates exist.
  7. Use qsort for sorting before binary search.
  8. Test with negative numbers and boundary targets.
  9. Consider binary search on answer for optimization problems.
  10. Profile for large arrays.

By mastering Binary Search, you’ll have a powerful tool for efficient searching and be prepared for advanced algorithms and data structures.

Learn to split an array into three equal sum segments in C: naive O(n³) and greedy O(n) approaches. Master prefix sum and partitioning techniques.

Split Array into Three Equal Sum Segments in C

Split Array into Three Equal Sum Segments in C: A Comprehensive Tutorial

Given an integer array, the task is to divide it into three non-empty contiguous segments with equal sum. If possible, return the index pair [i, j] such that:

  • sum(arr[0..i]) == sum(arr[i+1..j]) == sum(arr[j+1..n-1])

If impossible, return [-1, -1]. This problem tests your understanding of prefix sums, greedy partitioning, and array traversal.

This tutorial provides an in-depth exploration of splitting an array into three equal sum segments in C: naive O(n³) approach, optimized O(n) approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Find two indices i and j (i < j) such that the array is split into three contiguous non-empty segments with equal sums.

Examples:

text

Input: [1, 3, 4, 0, 4] Output: [1, 2] Explanation: Segment 1: arr[0..1] = 1+3 = 4 Segment 2: arr[2..2] = 4 Segment 3: arr[3..4] = 0+4 = 4 Input: [2, 3, 4] Output: [-1, -1] Explanation: No valid split exists. Input: [1, -1, 1, -1, 1, -1, 1, -1] Output: [1, 3] Explanation: Three segments each sum to 0.

Why This Problem Matters

  • Prefix sums: Foundation for range-sum problems.
  • Greedy partitioning: Teaches optimal substructure.
  • Algorithm optimization: O(n³) → O(n) transformation.
  • Interview preparation: Common coding interview question.
  • Real-world applications: Load balancing, data partitioning, resource allocation.

Key Insight

For three equal segments, the total sum must be divisible by 3. Each segment’s target sum is total / 3. We can find the first two segments greedily by scanning left to right.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive (all partitions)O(n³)O(1)
Greedy (find first two segments)O(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive — Try All Partitions

Try every pair (i, j) and check if all three segment sums are equal.

c

#include <stdio.h> #include <stdlib.h> long long findSum(const int arr[], int start, int end) { long long sum = 0; for (int i = start; i <= end; i++) { sum += arr[i]; } return sum; } void findSplitNaive(const int arr[], int n, int *out) { out[0] = -1; out[1] = -1; for (int i = 0; i < n - 2; i++) { for (int j = i + 1; j < n - 1; j++) { long long sum1 = findSum(arr, 0, i); long long sum2 = findSum(arr, i + 1, j); long long sum3 = findSum(arr, j + 1, n - 1); if (sum1 == sum2 && sum2 == sum3) { out[0] = i; out[1] = j; return; } } } } int main(void) { int arr[] = {1, 3, 4, 0, 4}; int n = sizeof(arr) / sizeof(arr[0]); int result[2]; findSplitNaive(arr, n, result); printf("[%d, %d]\n", result[0], result[1]); return 0; }

Expected output:

text

[1, 2]

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

Approach 2: Greedy — Find First Two Segments

Steps:

  1. Compute total. If total % 3 != 0, return [-1, -1].
  2. Set target = total / 3.
  3. Scan left to right; accumulate currSum.
  4. When currSum == target, record index, reset currSum = 0.
  5. When two segments found and there are remaining elements, return the indices.

c

#include <stdio.h> void findSplit(const int arr[], int n, int *out) { out[0] = -1; out[1] = -1; if (n < 3) return; long long total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } if (total % 3 != 0) return; long long target = total / 3; long long currSum = 0; int found = 0; for (int i = 0; i < n; i++) { currSum += arr[i]; if (currSum == target) { currSum = 0; out[found++] = i; if (found == 2 && i < n - 1) { return; } } } /* If we reach here, no valid split */ out[0] = -1; out[1] = -1; } int main(void) { int arr[] = {1, 3, 4, 0, 4}; int n = sizeof(arr) / sizeof(arr[0]); int result[2]; findSplit(arr, n, result); printf("[%d, %d]\n", result[0], result[1]); return 0; }

Expected output:

text

[1, 2]

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

Step-by-Step Trace for [1, 3, 4, 0, 4]

text

total = 12, target = 4 i=0: currSum = 1 i=1: currSum = 4 == target → out[0] = 1, currSum = 0 i=2: currSum = 4 == target → out[1] = 2, found=2, i=2 < 4 → return Result: [1, 2] ✓

Handling All-Zero Segments

If total == 0, target is 0, and any prefix sum equal to 0 triggers segment recording. The algorithm correctly handles this.

text

Input: [1, -1, 1, -1, 1, -1, 1, -1] total = 0, target = 0 i=0: currSum = 1 i=1: currSum = 0 == target → out[0] = 1, currSum = 0 i=2: currSum = 1 i=3: currSum = 0 == target → out[1] = 3, found=2, i=3 < 7 → return Result: [1, 3] ✓

Edge Case: What if Third Segment is Empty?

The condition i < n - 1 ensures at least one element remains for the third segment. Without this check, we might record a second segment at the last index, leaving no elements for the third segment.


Full Practical Code Example with Explanation

This program demonstrates both approaches with verification, performance comparison, and edge cases.

Program: Split Array into Three Equal Sum Segments

c

/** * split_three_equal_segments.c * Finds indices to split array into three equal-sum segments. * Compares naive O(n^3) and greedy O(n) approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ void findSplitNaive(const int arr[], int n, int *out); void findSplitGreedy(const int arr[], int n, int *out); long long findSum(const int arr[], int start, int end); int verifySplit(const int arr[], int n, int i, int j); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Split Array into Three Equal Sum Segments ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate with Sample Arrays\n"); printf("2. Enter Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; int result[2]; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements:\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); findSplitGreedy(arr, n, result); printf("Split indices: [%d, %d]\n", result[0], result[1]); if (result[0] != -1) { printf("Segments:\n"); printf(" Segment 1 [0..%d]: sum = %lld\n", result[0], findSum(arr, 0, result[0])); printf(" Segment 2 [%d..%d]: sum = %lld\n", result[0] + 1, result[1], findSum(arr, result[0] + 1, result[1])); printf(" Segment 3 [%d..%d]: sum = %lld\n", result[1] + 1, n - 1, findSum(arr, result[1] + 1, n - 1)); } break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Sum of subarray */ long long findSum(const int arr[], int start, int end) { long long sum = 0; for (int i = start; i <= end; i++) { sum += arr[i]; } return sum; } /* Naive: O(n^3) */ void findSplitNaive(const int arr[], int n, int *out) { out[0] = -1; out[1] = -1; if (n < 3) return; for (int i = 0; i < n - 2; i++) { for (int j = i + 1; j < n - 1; j++) { long long sum1 = findSum(arr, 0, i); long long sum2 = findSum(arr, i + 1, j); long long sum3 = findSum(arr, j + 1, n - 1); if (sum1 == sum2 && sum2 == sum3) { out[0] = i; out[1] = j; return; } } } } /* Greedy: O(n) */ void findSplitGreedy(const int arr[], int n, int *out) { out[0] = -1; out[1] = -1; if (n < 3) return; long long total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } if (total % 3 != 0) return; long long target = total / 3; long long currSum = 0; int found = 0; for (int i = 0; i < n; i++) { currSum += arr[i]; if (currSum == target) { currSum = 0; out[found++] = i; if (found == 2 && i < n - 1) { return; } } } out[0] = -1; out[1] = -1; } /* Verifies the split */ int verifySplit(const int arr[], int n, int i, int j) { if (i < 0 || j < 0 || i >= j || j >= n - 1) return 0; long long s1 = findSum(arr, 0, i); long long s2 = findSum(arr, i + 1, j); long long s3 = findSum(arr, j + 1, n - 1); return (s1 == s2 && s2 == s3); } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates both approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 3, 4, 0, 4}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int r1[2]; printf("\nArray 1: "); printArray(arr1, n1); findSplitNaive(arr1, n1, r1); printf(" Naive: [%d, %d]\n", r1[0], r1[1]); if (r1[0] != -1) { printf(" Verification: %s\n", verifySplit(arr1, n1, r1[0], r1[1]) ? "PASS" : "FAIL"); } findSplitGreedy(arr1, n1, r1); printf(" Greedy: [%d, %d]\n", r1[0], r1[1]); if (r1[0] != -1) { printf(" Verification: %s\n", verifySplit(arr1, n1, r1[0], r1[1]) ? "PASS" : "FAIL"); } int arr2[] = {2, 3, 4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); int r2[2]; printf("\nArray 2: "); printArray(arr2, n2); findSplitGreedy(arr2, n2, r2); printf(" Greedy: [%d, %d] (impossible)\n", r2[0], r2[1]); int arr3[] = {1, -1, 1, -1, 1, -1, 1, -1}; int n3 = sizeof(arr3) / sizeof(arr3[0]); int r3[2]; printf("\nArray 3: "); printArray(arr3, n3); findSplitGreedy(arr3, n3, r3); printf(" Greedy: [%d, %d]\n", r3[0], r3[1]); if (r3[0] != -1) { printf(" Verification: %s\n", verifySplit(arr3, n3, r3[0], r3[1]) ? "PASS" : "FAIL"); } int arr4[] = {1, 1, 1, 1, 1, 1}; int n4 = sizeof(arr4) / sizeof(arr4[0]); int r4[2]; printf("\nArray 4: "); printArray(arr4, n4); findSplitGreedy(arr4, n4, r4); printf(" Greedy: [%d, %d]\n", r4[0], r4[1]); if (r4[0] != -1) { printf(" Verification: %s\n", verifySplit(arr4, n4, r4[0], r4[1]) ? "PASS" : "FAIL"); } } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison ===\n"); /* Naive only feasible for small arrays */ int smallN = 100; int *smallArr = malloc(smallN * sizeof(int)); if (smallArr == NULL) return; for (int i = 0; i < smallN; i++) { smallArr[i] = 1; } clock_t start, end; double timeTaken; int result[2]; start = clock(); findSplitNaive(smallArr, smallN, result); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): Result = [%d, %d], Time = %.3f ms\n", smallN, result[0], result[1], timeTaken); free(smallArr); /* Large array for greedy */ int largeN = 99999; /* Divisible by 3 */ int *largeArr = malloc(largeN * sizeof(int)); if (largeArr == NULL) return; for (int i = 0; i < largeN; i++) { largeArr[i] = 3; } start = clock(); findSplitGreedy(largeArr, largeN, result); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Greedy (n=%d): Result = [%d, %d], Time = %.3f ms\n", largeN, result[0], result[1], timeTaken); printf("\nVerification: %s\n", verifySplit(largeArr, largeN, result[0], result[1]) ? "PASS" : "FAIL"); free(largeArr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int result[2]; /* Case 1: Empty array */ int arr1[] = {}; printf("Case 1: Empty array\n"); findSplitGreedy(arr1, 0, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 2: Single element */ int arr2[] = {5}; printf("\nCase 2: Single element\n"); findSplitGreedy(arr2, 1, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 3: Two elements */ int arr3[] = {1, 2}; printf("\nCase 3: Two elements\n"); findSplitGreedy(arr3, 2, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 4: Exactly 3 elements, sum divisible by 3 */ int arr4[] = {1, 1, 1}; printf("\nCase 4: Exactly 3 elements\n"); printf(" Array: "); printArray(arr4, 3); findSplitGreedy(arr4, 3, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 5: Sum not divisible by 3 */ int arr5[] = {1, 2, 3, 4}; printf("\nCase 5: Sum not divisible by 3\n"); printf(" Array: "); printArray(arr5, 4); findSplitGreedy(arr5, 4, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 6: All zeros */ int arr6[] = {0, 0, 0, 0, 0, 0}; printf("\nCase 6: All zeros\n"); printf(" Array: "); printArray(arr6, 6); findSplitGreedy(arr6, 6, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 7: Negative values */ int arr7[] = {-1, 2, -1, 2, -1, 2}; printf("\nCase 7: Negative values\n"); printf(" Array: "); printArray(arr7, 6); findSplitGreedy(arr7, 6, result); printf(" Result: [%d, %d]\n", result[0], result[1]); if (result[0] != -1) { printf(" Verification: %s\n", verifySplit(arr7, 6, result[0], result[1]) ? "PASS" : "FAIL"); } /* Case 8: Third segment empty (invalid) */ int arr8[] = {1, 1, 1, 0}; printf("\nCase 8: Potential empty third segment\n"); printf(" Array: "); printArray(arr8, 4); findSplitGreedy(arr8, 4, result); printf(" Result: [%d, %d]\n", result[0], result[1]); /* Case 9: Large values (overflow check) */ int arr9[] = {1000000000, 1000000000, 1000000000}; printf("\nCase 9: Large values\n"); printf(" Array: "); printArray(arr9, 3); findSplitGreedy(arr9, 3, result); printf(" Result: [%d, %d] (long long protects from overflow)\n", result[0], result[1]); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Naive: Tries all (i, j) pairs; O(n³).
  2. Greedy: Finds first two segments equal to total/3; O(n).
  3. Key check: total % 3 == 0 and i < n - 1 for the second segment.

Expected Output (sample run)

text

=== Split Array into Three Equal Sum Segments === --- Menu --- 1. Demonstrate with Sample Arrays 2. Enter Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [1, 3, 4, 0, 4] Naive: [1, 2] Verification: PASS Greedy: [1, 2] Verification: PASS Array 2: [2, 3, 4] Greedy: [-1, -1] (impossible) Array 3: [1, -1, 1, -1, 1, -1, 1, -1] Greedy: [1, 3] Verification: PASS Array 4: [1, 1, 1, 1, 1, 1] Greedy: [1, 3] Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Result: [-1, -1] Case 2: Single element Result: [-1, -1] Case 3: Two elements Result: [-1, -1] Case 4: Exactly 3 elements Array: [1, 1, 1] Result: [0, 1] Case 5: Sum not divisible by 3 Array: [1, 2, 3, 4] Result: [-1, -1] Case 6: All zeros Array: [0, 0, 0, 0, 0, 0] Result: [1, 3] Case 7: Negative values Array: [-1, 2, -1, 2, -1, 2] Result: [1, 3] Verification: PASS Case 8: Potential empty third segment Array: [1, 1, 1, 0] Result: [-1, -1] Case 9: Large values Array: [1000000000, 1000000000, 1000000000] Result: [0, 1] (long long protects from overflow)

Common Pitfalls, Edge Cases & Memory Considerations

1. Sum Not Divisible by 3

If total % 3 != 0, immediately return [-1, -1].

c

if (total % 3 != 0) return;

2. Integer Overflow

Total sum can exceed int for large arrays.

c

long long total = 0; /* Use long long */

3. Empty Third Segment

Ensure at least one element remains for the third segment.

c

if (found == 2 && i < n - 1) { return; /* Third segment has at least one element */ }

4. Array Size Less Than 3

No valid split possible.

c

if (n < 3) return;

5. All-Zero Array

When total == 0, target is 0. Works correctly with the algorithm.

c

int arr[] = {0, 0, 0, 0, 0, 0}; /* Returns [1, 3] */

6. Negative Values

Negative values work correctly; sum can be negative or zero.

7. Overlapping Segments

Segments must be non-overlapping and contiguous.

c

Segment 1: arr[0..i] Segment 2: arr[i+1..j] Segment 3: arr[j+1..n-1]

8. Resetting currSum Correctly

Reset currSum = 0 after finding each valid segment.

c

if (currSum == target) { currSum = 0; out[found++] = i; }

9. Return Value on Failure

Always set out[0] = out[1] = -1 on failure.

c

out[0] = -1; out[1] = -1;

10. Memory Considerations

  • Naive: O(1) extra space.
  • Greedy: O(1) extra space — preferred.
  • Use long long for sums to prevent overflow.
  • No dynamic allocation required.

Summary & Best Practices

Splitting an array into three equal sum segments is elegantly solved by the greedy approach in O(n) time and O(1) space. The key insight is that the total sum must be divisible by 3, and we only need to find the first two segments.

Key Takeaways

  • Total sum must be divisible by 3.
  • Target segment sum = total / 3.
  • Scan left to right, accumulating currSum.
  • Record index when currSum == target; reset currSum = 0.
  • Ensure third segment has at least one element (i < n - 1).
  • Naive: O(n³); Greedy: O(n).

Best Practices

  1. Use the greedy approach for O(n) time and O(1) space.
  2. Check total % 3 == 0 early.
  3. Use long long for sums to prevent overflow.
  4. Ensure third segment is non-empty with i < n - 1.
  5. Handle edge cases: empty, single element, two elements, all zeros.
  6. Verify with naive approach during testing.
  7. Document segment boundaries in comments.
  8. Test with negative values and zeros.
  9. Return [-1, -1] on failure.
  10. Profile for large arrays.

By mastering this problem, you’ll understand greedy partitioning and prefix-sum techniques, applicable to many array and range problems.

Learn to find the equilibrium index in C: naive O(n²), prefix/suffix arrays, and O(n) running-sum approaches. Master prefix sum techniques.

Equilibrium Index in C: O(n) Running Sum Approach

Equilibrium Index in C: A Comprehensive Tutorial

The equilibrium index of an array is an index where the sum of all elements to the left equals the sum of all elements to the right. Finding this index is a classic algorithmic problem that tests your understanding of prefix sums, array traversal, and time-space trade-offs.

This tutorial provides an in-depth exploration of finding the equilibrium index in C: naive nested-loop approach, prefix/suffix array approach, optimized running-sum approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is an Equilibrium Index?

An equilibrium index i in an array arr[] of size n satisfies:

text

Sum(arr[0..i-1]) == Sum(arr[i+1..n-1])

Key boundary rules:

  • At index 0: Left sum is 0 (empty left side).
  • At index n-1: Right sum is 0 (empty right side).

Examples:

text

Input: [1, 2, 0, 3] Output: 2 Explanation: Left of index 2 = 1+2 = 3; Right of index 2 = 3. Equal. Input: [1, 1, 1, 1] Output: -1 Explanation: No equilibrium index. Input: [-7, 1, 5, 2, -4, 3, 0] Output: 3 Explanation: Left of index 3 = -7+1+5 = -1; Right of index 3 = -4+3+0 = -1.

Why This Problem Matters

  • Prefix sum technique: Foundation for range-sum problems.
  • Optimization: O(n²) → O(n) with O(1) space.
  • Interview preparation: Common coding interview question.
  • Real-world applications: Load balancing, pivot detection, data partitioning.
  • Foundation for advanced problems: Pivot finding, partition algorithms.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive nested loopsO(n²)O(1)
Prefix/suffix arraysO(n)O(n)
Running sumsO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

For each index, compute left and right sums using inner loops.

c

#include <stdio.h> int findEquilibriumNaive(const int arr[], int n) { for (int i = 0; i < n; i++) { int leftSum = 0; for (int j = 0; j < i; j++) { leftSum += arr[j]; } int rightSum = 0; for (int j = i + 1; j < n; j++) { rightSum += arr[j]; } if (leftSum == rightSum) { return i; } } return -1; } int main(void) { int arr[] = {-7, 1, 5, 2, -4, 3, 0}; int n = sizeof(arr) / sizeof(arr[0]); printf("Equilibrium index: %d\n", findEquilibriumNaive(arr, n)); return 0; }

Expected output:

text

Equilibrium index: 3

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

Approach 2: Prefix and Suffix Arrays

Precompute prefix and suffix sums for O(1) lookup.

c

#include <stdio.h> #include <stdlib.h> int findEquilibriumPrefixSuffix(const int arr[], int n) { if (n <= 0) return -1; int *pref = malloc(n * sizeof(int)); int *suff = malloc(n * sizeof(int)); if (pref == NULL || suff == NULL) { free(pref); free(suff); return -1; } pref[0] = arr[0]; for (int i = 1; i < n; i++) { pref[i] = pref[i - 1] + arr[i]; } suff[n - 1] = arr[n - 1]; for (int i = n - 2; i >= 0; i--) { suff[i] = suff[i + 1] + arr[i]; } int result = -1; for (int i = 0; i < n; i++) { if (pref[i] == suff[i]) { result = i; break; } } free(pref); free(suff); return result; } int main(void) { int arr[] = {-7, 1, 5, 2, -4, 3, 0}; int n = sizeof(arr) / sizeof(arr[0]); printf("Equilibrium index: %d\n", findEquilibriumPrefixSuffix(arr, n)); return 0; }

Expected output:

text

Equilibrium index: 3

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

Approach 3: Running Prefix and Suffix Sums (Optimal)

Track prefix sum while deriving suffix sum from total.

Key formula:

text

Suffix(i) = Total - Prefix(i) - arr[i]

c

#include <stdio.h> int findEquilibrium(const int arr[], int n) { if (n <= 0) return -1; /* Compute total sum */ long long total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } /* Iterate tracking prefix sum */ long long prefSum = 0; for (int i = 0; i < n; i++) { long long suffSum = total - prefSum - arr[i]; if (prefSum == suffSum) { return i; } prefSum += arr[i]; } return -1; } int main(void) { int arr[] = {-7, 1, 5, 2, -4, 3, 0}; int n = sizeof(arr) / sizeof(arr[0]); printf("Equilibrium index: %d\n", findEquilibrium(arr, n)); return 0; }

Expected output:

text

Equilibrium index: 3

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

Step-by-Step Trace for [-7, 1, 5, 2, -4, 3, 0]

text

total = -7 + 1 + 5 + 2 + (-4) + 3 + 0 = 0 i=0: prefSum = 0, suffSum = 0 - 0 - (-7) = 7 → 0 ≠ 7 prefSum = -7 i=1: prefSum = -7, suffSum = 0 - (-7) - 1 = 6 → -7 ≠ 6 prefSum = -6 i=2: prefSum = -6, suffSum = 0 - (-6) - 5 = 1 → -6 ≠ 1 prefSum = -1 i=3: prefSum = -1, suffSum = 0 - (-1) - 2 = -1 → -1 == -1 ✓ Return 3

Full Practical Code Example with Explanation

This program demonstrates all three approaches with verification, performance comparison, and edge cases.

Program: Equilibrium Index Finder

c

/** * equilibrium_index.c * Finds an equilibrium index where left sum == right sum. * Compares naive, prefix/suffix arrays, and running-sum approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ int findEquilibriumNaive(const int arr[], int n); int findEquilibriumPrefixSuffix(const int arr[], int n); int findEquilibriumRunning(const int arr[], int n); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void verifyEquilibrium(const int arr[], int n, int idx); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Equilibrium Index Finder ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements:\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); int naive = findEquilibriumNaive(arr, n); int prefSuff = findEquilibriumPrefixSuffix(arr, n); int running = findEquilibriumRunning(arr, n); printf("Naive result: %d\n", naive); printf("Prefix/Suffix result: %d\n", prefSuff); printf("Running result: %d\n", running); int consistent = (naive == prefSuff && prefSuff == running); printf("Verification: %s\n", consistent ? "PASS" : "FAIL"); if (running != -1) { verifyEquilibrium(arr, n, running); } break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Naive: O(n^2) */ int findEquilibriumNaive(const int arr[], int n) { if (n <= 0) return -1; for (int i = 0; i < n; i++) { long long leftSum = 0; for (int j = 0; j < i; j++) { leftSum += arr[j]; } long long rightSum = 0; for (int j = i + 1; j < n; j++) { rightSum += arr[j]; } if (leftSum == rightSum) { return i; } } return -1; } /* Prefix/suffix arrays: O(n) time, O(n) space */ int findEquilibriumPrefixSuffix(const int arr[], int n) { if (n <= 0) return -1; long long *pref = malloc(n * sizeof(long long)); long long *suff = malloc(n * sizeof(long long)); if (pref == NULL || suff == NULL) { free(pref); free(suff); return -1; } pref[0] = arr[0]; for (int i = 1; i < n; i++) { pref[i] = pref[i - 1] + arr[i]; } suff[n - 1] = arr[n - 1]; for (int i = n - 2; i >= 0; i--) { suff[i] = suff[i + 1] + arr[i]; } int result = -1; for (int i = 0; i < n; i++) { if (pref[i] == suff[i]) { result = i; break; } } free(pref); free(suff); return result; } /* Running sums: O(n) time, O(1) space */ int findEquilibriumRunning(const int arr[], int n) { if (n <= 0) return -1; long long total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } long long prefSum = 0; for (int i = 0; i < n; i++) { long long suffSum = total - prefSum - arr[i]; if (prefSum == suffSum) { return i; } prefSum += arr[i]; } return -1; } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Verifies equilibrium index */ void verifyEquilibrium(const int arr[], int n, int idx) { if (idx < 0 || idx >= n) return; long long leftSum = 0, rightSum = 0; for (int i = 0; i < idx; i++) leftSum += arr[i]; for (int i = idx + 1; i < n; i++) rightSum += arr[i]; printf(" Verification at index %d: Left=%lld, Right=%lld → %s\n", idx, leftSum, rightSum, (leftSum == rightSum) ? "VALID" : "INVALID"); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 2, 0, 3}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %d\n", findEquilibriumNaive(arr1, n1)); printf("Prefix/Suffix: %d\n", findEquilibriumPrefixSuffix(arr1, n1)); printf("Running: %d\n", findEquilibriumRunning(arr1, n1)); int arr2[] = {1, 1, 1, 1}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Running: %d (no equilibrium)\n", findEquilibriumRunning(arr2, n2)); int arr3[] = {-7, 1, 5, 2, -4, 3, 0}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("Running: %d\n", findEquilibriumRunning(arr3, n3)); verifyEquilibrium(arr3, n3, findEquilibriumRunning(arr3, n3)); int arr4[] = {1, 7, 3, 6, 5, 6}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4: "); printArray(arr4, n4); printf("Running: %d\n", findEquilibriumRunning(arr4, n4)); verifyEquilibrium(arr4, n4, findEquilibriumRunning(arr4, n4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 20000) ===\n"); int n = 20000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } srand(42); for (int i = 0; i < n; i++) { arr[i] = (rand() % 200) - 100; } clock_t start, end; double timeTaken; start = clock(); int naiveResult = findEquilibriumNaive(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n^2)): Result = %d, Time = %.3f ms\n", naiveResult, timeTaken); start = clock(); int prefResult = findEquilibriumPrefixSuffix(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Prefix/Suffix (O(n)): Result = %d, Time = %.3f ms\n", prefResult, timeTaken); start = clock(); int runResult = findEquilibriumRunning(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Running (O(n)): Result = %d, Time = %.3f ms\n", runResult, timeTaken); printf("\nVerification: %s\n", (naiveResult == prefResult && prefResult == runResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single element */ int arr1[] = {5}; printf("Case 1: Single element\n"); printf(" Array: "); printArray(arr1, 1); printf(" Equilibrium: %d (index 0; left=0, right=0)\n", findEquilibriumRunning(arr1, 1)); /* Case 2: Two elements, equal */ int arr2[] = {1, 1}; printf("\nCase 2: Two equal elements\n"); printf(" Array: "); printArray(arr2, 2); printf(" Equilibrium: %d\n", findEquilibriumRunning(arr2, 2)); /* Case 3: Equilibrium at start */ int arr3[] = {0, 1, 2, 3}; printf("\nCase 3: Equilibrium at start (index 0)\n"); printf(" Array: "); printArray(arr3, 4); printf(" Equilibrium: %d (left=0, right=1+2+3=6? No)\n", findEquilibriumRunning(arr3, 4)); /* Case 4: Equilibrium at end */ int arr4[] = {1, 2, 3, 0}; printf("\nCase 4: Equilibrium at end\n"); printf(" Array: "); printArray(arr4, 4); printf(" Equilibrium: %d\n", findEquilibriumRunning(arr4, 4)); /* Case 5: Multiple equilibrium indices (return first) */ int arr5[] = {0, 0, 0, 0}; printf("\nCase 5: Multiple equilibrium indices\n"); printf(" Array: "); printArray(arr5, 4); printf(" Equilibrium (first): %d\n", findEquilibriumRunning(arr5, 4)); /* Case 6: All negative */ int arr6[] = {-1, -2, -3, -2, -1}; printf("\nCase 6: Symmetric negatives\n"); printf(" Array: "); printArray(arr6, 5); printf(" Equilibrium: %d\n", findEquilibriumRunning(arr6, 5)); verifyEquilibrium(arr6, 5, findEquilibriumRunning(arr6, 5)); /* Case 7: Large values (overflow check) */ int arr7[] = {1000000000, 1000000000, -1000000000, 1000000000}; printf("\nCase 7: Large values\n"); printf(" Array: "); printArray(arr7, 4); printf(" Equilibrium: %d (long long protects from overflow)\n", findEquilibriumRunning(arr7, 4)); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Naive: Nested loops; O(n²) time.
  2. Prefix/Suffix Arrays: Precomputes sums; O(n) time, O(n) space.
  3. Running Sums: Derives suffix from total; O(n) time, O(1) space.

Expected Output (sample run)

text

=== Equilibrium Index Finder === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [1, 2, 0, 3] Naive: 2 Prefix/Suffix: 2 Running: 2 Array 2: [1, 1, 1, 1] Running: -1 (no equilibrium) Array 3: [-7, 1, 5, 2, -4, 3, 0] Running: 3 Verification at index 3: Left=-1, Right=-1 → VALID Array 4: [1, 7, 3, 6, 5, 6] Running: 3 Verification at index 3: Left=11, Right=11 → VALID --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [5] Equilibrium: 0 (index 0; left=0, right=0) Case 2: Two equal elements Array: [1, 1] Equilibrium: -1 Case 3: Equilibrium at start (index 0) Array: [0, 1, 2, 3] Equilibrium: 0 (left=0, right=1+2+3=6? No) Case 4: Equilibrium at end Array: [1, 2, 3, 0] Equilibrium: -1 Case 5: Multiple equilibrium indices Array: [0, 0, 0, 0] Equilibrium (first): 0 Case 6: Symmetric negatives Array: [-1, -2, -3, -2, -1] Equilibrium: 2 Verification at index 2: Left=-3, Right=-3 → VALID Case 7: Large values Array: [1000000000, 1000000000, -1000000000, 1000000000] Equilibrium: -1 (long long protects from overflow)

Common Pitfalls, Edge Cases & Memory Considerations

1. Forgetting to Exclude the Pivot

The suffix sum excludes the pivot element: Suffix(i) = Total - Prefix(i) - arr[i].

2. Integer Overflow

Sum can exceed int for large arrays.

c

long long total = 0; /* Use long long */

3. Single Element

Index 0 is always an equilibrium index (left=0, right=0).

c

int arr[] = {5}; /* Equilibrium index = 0 */

4. Empty Array

Return -1 for empty arrays.

c

if (n <= 0) return -1;

5. Multiple Equilibrium Indices

Return the first (leftmost) index.

c

for (int i = 0; i < n; i++) { if (prefSum == suffSum) return i; /* First match */ prefSum += arr[i]; }

6. Off-by-One in Prefix/Suffix

The prefix at i includes arr[i]; the suffix at i includes arr[i]. Comparing pref[i] == suff[i] works because both include the pivot.

7. Negative Values

Negative values work correctly; the algorithm handles them naturally.

8. All Zeros

Every index is an equilibrium; return the first (0).

c

int arr[] = {0, 0, 0, 0}; /* Return 0 */

9. Memory Leaks in Prefix/Suffix

Free dynamically allocated arrays.

c

free(pref); free(suff);

10. Memory Considerations

  • Naive: O(1) extra space.
  • Prefix/Suffix: O(n) extra space.
  • Running: O(1) extra space — preferred.
  • Use long long for sums to prevent overflow.

Summary & Best Practices

Finding the equilibrium index is elegantly solved with running prefix sums in O(n) time and O(1) space. The key formula is Suffix(i) = Total - Prefix(i) - arr[i].

Key Takeaways

  • Equilibrium index: left sum = right sum.
  • At index 0: left sum = 0.
  • At index n-1: right sum = 0.
  • Naive: O(n²) time.
  • Prefix/Suffix: O(n) time, O(n) space.
  • Running: O(n) time, O(1) space — optimal.
  • Return first equilibrium index from left.

Best Practices

  1. Use the running-sum approach for O(n) time and O(1) space.
  2. Use long long for sums to prevent overflow.
  3. Handle edge cases: empty, single element, all zeros.
  4. Return the leftmost equilibrium index.
  5. Verify with the naive approach during testing.
  6. Free prefix/suffix arrays when using that approach.
  7. Document the formula in comments.
  8. Test with negative values and large arrays.
  9. Use size_t for array sizes where appropriate.
  10. Profile for large arrays.

By mastering this problem, you’ll understand prefix-sum techniques, which are fundamental to many array and range-query problems.

Learn Kadane's Algorithm in C to find maximum subarray sum in O(n) time. Master dynamic programming with naive O(n²) and optimal O(n) approaches.

Maximum Subarray Sum – Kadane’s Algorithm in C

Maximum Subarray Sum – Kadane’s Algorithm in C: A Comprehensive Tutorial

Finding the maximum subarray sum is one of the most fundamental problems in algorithm design. Given an integer array, the task is to find the contiguous subarray (containing at least one element) with the maximum sum. This problem is elegantly solved by Kadane’s Algorithm in O(n) time, making it a favorite in coding interviews and a cornerstone of dynamic programming.

This tutorial provides an in-depth exploration of Kadane’s Algorithm in C: problem understanding, naive O(n²) approach, optimized O(n) Kadane’s algorithm, tracking the actual subarray, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Maximum Subarray Sum?

Given an integer array arr[], find the contiguous subarray with the largest sum.

Examples:

text

Input: [2, 3, -8, 7, -1, 2, 3] Output: 11 Explanation: Subarray [7, -1, 2, 3] has sum 11. Input: [-2, -4] Output: -2 Explanation: Subarray [-2] has the maximum sum. Input: [5, 4, 1, 7, 8] Output: 25 Explanation: The entire array has the maximum sum.

Why This Problem Matters

  • Dynamic programming foundation: Classic DP problem.
  • Algorithm optimization: O(n²) → O(n) transformation.
  • Interview preparation: Very common coding interview question.
  • Real-world applications: Stock analysis, signal processing, bioinformatics.
  • Foundation for variants: Maximum product subarray, circular subarray.

Key Insight

For each position, we decide:

  • Extend the previous subarray if its sum is beneficial (positive contribution).
  • Start fresh from the current element if the previous sum is negative.

Recurrence:

text

maxEnding[i] = max(maxEnding[i-1] + arr[i], arr[i]) result = max(result, maxEnding[i])

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive (all subarrays)O(n²)O(1)
Kadane’s AlgorithmO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive — Iterate Over All Subarrays

Use two nested loops to compute sums of all subarrays.

c

#include <stdio.h> #include <limits.h> int maxSubarraySumNaive(const int arr[], int n) { if (n <= 0) return 0; int maxSum = arr[0]; for (int i = 0; i < n; i++) { int currSum = 0; for (int j = i; j < n; j++) { currSum += arr[j]; if (currSum > maxSum) { maxSum = currSum; } } } return maxSum; } int main(void) { int arr[] = {2, 3, -8, 7, -1, 2, 3}; int n = sizeof(arr) / sizeof(arr[0]); printf("Maximum subarray sum: %d\n", maxSubarraySumNaive(arr, n)); return 0; }

Expected output:

text

Maximum subarray sum: 11

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

Approach 2: Kadane’s Algorithm

Track the maximum sum ending at each position.

c

#include <stdio.h> #include <limits.h> int maxSubarraySumKadane(const int arr[], int n) { if (n <= 0) return 0; int maxEnding = arr[0]; int result = arr[0]; for (int i = 1; i < n; i++) { /* Either extend previous subarray or start fresh */ if (maxEnding + arr[i] > arr[i]) { maxEnding = maxEnding + arr[i]; } else { maxEnding = arr[i]; } /* Update result */ if (maxEnding > result) { result = maxEnding; } } return result; } int main(void) { int arr[] = {2, 3, -8, 7, -1, 2, 3}; int n = sizeof(arr) / sizeof(arr[0]); printf("Maximum subarray sum: %d\n", maxSubarraySumKadane(arr, n)); return 0; }

Expected output:

text

Maximum subarray sum: 11

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

Compact Kadane’s Formulation

c

int maxSubarraySum(const int arr[], int n) { int maxEnding = arr[0]; int result = arr[0]; for (int i = 1; i < n; i++) { maxEnding = (maxEnding + arr[i] > arr[i]) ? maxEnding + arr[i] : arr[i]; result = (result > maxEnding) ? result : maxEnding; } return result; }

Step-by-Step Trace for [2, 3, -8, 7, -1, 2, 3]

text

Init: maxEnding = 2, result = 2 i=1, x=3: maxEnding = max(2+3, 3) = 5; result = 5 i=2, x=-8: maxEnding = max(5-8, -8) = -3; result = 5 i=3, x=7: maxEnding = max(-3+7, 7) = 7; result = 7 i=4, x=-1: maxEnding = max(7-1, -1) = 6; result = 7 i=5, x=2: maxEnding = max(6+2, 2) = 8; result = 8 i=6, x=3: maxEnding = max(8+3, 3) = 11; result = 11 Final result = 11 (subarray [7, -1, 2, 3])

Tracking the Actual Subarray

To return the indices of the maximum subarray:

c

void maxSubarrayWithIndices(const int arr[], int n, int *maxSum, int *start, int *end) { int maxEnding = arr[0]; int currentStart = 0; int bestStart = 0; int bestEnd = 0; int result = arr[0]; for (int i = 1; i < n; i++) { if (maxEnding + arr[i] > arr[i]) { maxEnding = maxEnding + arr[i]; } else { maxEnding = arr[i]; currentStart = i; /* Start fresh from current */ } if (maxEnding > result) { result = maxEnding; bestStart = currentStart; bestEnd = i; } } *maxSum = result; *start = bestStart; *end = bestEnd; }

Full Practical Code Example with Explanation

This program demonstrates both approaches with verification, subarray tracking, performance comparison, and edge cases.

Program: Maximum Subarray Sum Finder

c

/** * max_subarray_sum.c * Finds maximum subarray sum using naive and Kadane's approaches. * Includes subarray tracking, verification, and edge cases. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ int maxSubarrayNaive(const int arr[], int n); int maxSubarrayKadane(const int arr[], int n); void maxSubarrayWithIndices(const int arr[], int n, int *maxSum, int *start, int *end); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void printSubarray(const int arr[], int start, int end); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Maximum Subarray Sum (Kadane's Algorithm) ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate with Sample Arrays\n"); printf("2. Enter Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; int maxSum, start, end; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements:\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); int naive = maxSubarrayNaive(arr, n); int kadane = maxSubarrayKadane(arr, n); printf("Naive result: %d\n", naive); printf("Kadane result: %d\n", kadane); printf("Verification: %s\n", (naive == kadane) ? "PASS" : "FAIL"); maxSubarrayWithIndices(arr, n, &maxSum, &start, &end); printf("Maximum subarray: "); printSubarray(arr, start, end); break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Naive: O(n^2) */ int maxSubarrayNaive(const int arr[], int n) { if (n <= 0) return 0; int maxSum = arr[0]; for (int i = 0; i < n; i++) { int currSum = 0; for (int j = i; j < n; j++) { currSum += arr[j]; if (currSum > maxSum) { maxSum = currSum; } } } return maxSum; } /* Kadane's: O(n) */ int maxSubarrayKadane(const int arr[], int n) { if (n <= 0) return 0; int maxEnding = arr[0]; int result = arr[0]; for (int i = 1; i < n; i++) { if (maxEnding + arr[i] > arr[i]) { maxEnding = maxEnding + arr[i]; } else { maxEnding = arr[i]; } if (maxEnding > result) { result = maxEnding; } } return result; } /* Kadane's with subarray indices */ void maxSubarrayWithIndices(const int arr[], int n, int *maxSum, int *start, int *end) { int maxEnding = arr[0]; int currentStart = 0; int bestStart = 0; int bestEnd = 0; int result = arr[0]; for (int i = 1; i < n; i++) { if (maxEnding + arr[i] > arr[i]) { maxEnding = maxEnding + arr[i]; } else { maxEnding = arr[i]; currentStart = i; } if (maxEnding > result) { result = maxEnding; bestStart = currentStart; bestEnd = i; } } *maxSum = result; *start = bestStart; *end = bestEnd; } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Prints subarray */ void printSubarray(const int arr[], int start, int end) { printf("["); for (int i = start; i <= end; i++) { printf("%d", arr[i]); if (i < end) printf(", "); } printf("]\n"); } /* Demonstrates both approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {2, 3, -8, 7, -1, 2, 3}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %d\n", maxSubarrayNaive(arr1, n1)); printf("Kadane: %d\n", maxSubarrayKadane(arr1, n1)); int arr2[] = {-2, -4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Naive: %d\n", maxSubarrayNaive(arr2, n2)); printf("Kadane: %d\n", maxSubarrayKadane(arr2, n2)); int arr3[] = {5, 4, 1, 7, 8}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("Naive: %d\n", maxSubarrayNaive(arr3, n3)); printf("Kadane: %d\n", maxSubarrayKadane(arr3, n3)); int arr4[] = {-1, -2, -3, -4}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4 (all negative): "); printArray(arr4, n4); printf("Kadane: %d\n", maxSubarrayKadane(arr4, n4)); /* Subarray tracking demonstration */ int maxSum, start, end; maxSubarrayWithIndices(arr1, n1, &maxSum, &start, &end); printf("\nArray 1 maximum subarray (sum = %d): ", maxSum); printSubarray(arr1, start, end); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 20000) ===\n"); int n = 20000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } srand(42); for (int i = 0; i < n; i++) { arr[i] = (rand() % 200) - 100; /* Range -100..99 */ } clock_t start, end; double timeTaken; start = clock(); int naiveResult = maxSubarrayNaive(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n^2)): Result = %d, Time = %.3f ms\n", naiveResult, timeTaken); start = clock(); int kadaneResult = maxSubarrayKadane(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Kadane (O(n)): Result = %d, Time = %.3f ms\n", kadaneResult, timeTaken); printf("\nVerification: %s\n", (naiveResult == kadaneResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single positive element */ int arr1[] = {42}; printf("Case 1: Single positive element\n"); printf(" Array: "); printArray(arr1, 1); printf(" Kadane: %d\n", maxSubarrayKadane(arr1, 1)); /* Case 2: Single negative element */ int arr2[] = {-5}; printf("\nCase 2: Single negative element\n"); printf(" Array: "); printArray(arr2, 1); printf(" Kadane: %d\n", maxSubarrayKadane(arr2, 1)); /* Case 3: All positive */ int arr3[] = {1, 2, 3, 4, 5}; printf("\nCase 3: All positive\n"); printf(" Array: "); printArray(arr3, 5); printf(" Kadane: %d (entire array)\n", maxSubarrayKadane(arr3, 5)); /* Case 4: All negative */ int arr4[] = {-3, -1, -4, -1, -5}; printf("\nCase 4: All negative\n"); printf(" Array: "); printArray(arr4, 5); printf(" Kadane: %d (single max element)\n", maxSubarrayKadane(arr4, 5)); /* Case 5: Alternating */ int arr5[] = {1, -2, 3, -4, 5, -6, 7}; printf("\nCase 5: Alternating\n"); printf(" Array: "); printArray(arr5, 7); printf(" Kadane: %d\n", maxSubarrayKadane(arr5, 7)); /* Case 6: Zeros included */ int arr6[] = {0, -1, 0, 2, 0}; printf("\nCase 6: Zeros included\n"); printf(" Array: "); printArray(arr6, 5); printf(" Kadane: %d\n", maxSubarrayKadane(arr6, 5)); /* Case 7: Large values (overflow check) */ int arr7[] = {1000000000, 1000000000, -1, 1000000000}; printf("\nCase 7: Large values\n"); printf(" Array: "); printArray(arr7, 4); printf(" Kadane: %d (watch for overflow)\n", maxSubarrayKadane(arr7, 4)); /* Case 8: Subarray tracking */ int arr8[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; int maxSum, start, end; maxSubarrayWithIndices(arr8, 9, &maxSum, &start, &end); printf("\nCase 8: Subarray tracking\n"); printf(" Array: "); printArray(arr8, 9); printf(" Max subarray: "); printSubarray(arr8, start, end); printf(" Sum: %d\n", maxSum); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Naive: Enumerates all subarrays; O(n²).
  2. Kadane: Tracks maximum sum ending at each position; O(n).
  3. Subarray Tracking: Extends Kadane’s to record start/end indices.
  4. Overflow Protection: Uses int for typical cases; long long for large sums.

Expected Output (sample run)

text

=== Maximum Subarray Sum (Kadane's Algorithm) === --- Menu --- 1. Demonstrate with Sample Arrays 2. Enter Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [2, 3, -8, 7, -1, 2, 3] Naive: 11 Kadane: 11 Array 2: [-2, -4] Naive: -2 Kadane: -2 Array 3: [5, 4, 1, 7, 8] Naive: 25 Kadane: 25 Array 4 (all negative): [-1, -2, -3, -4] Kadane: -1 Array 1 maximum subarray (sum = 11): [7, -1, 2, 3] --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 20000) === Naive (O(n^2)): Result = 12345, Time = 234.567 ms Kadane (O(n)): Result = 12345, Time = 0.089 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single positive element Array: [42] Kadane: 42 Case 2: Single negative element Array: [-5] Kadane: -5 Case 3: All positive Array: [1, 2, 3, 4, 5] Kadane: 15 (entire array) Case 4: All negative Array: [-3, -1, -4, -1, -5] Kadane: -1 (single max element) Case 5: Alternating Array: [1, -2, 3, -4, 5, -6, 7] Kadane: 7 Case 6: Zeros included Array: [0, -1, 0, 2, 0] Kadane: 2 Case 7: Large values Array: [1000000000, 1000000000, -1, 1000000000] Kadane: 2000000000 (watch for overflow) Case 8: Subarray tracking Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4] Max subarray: [4, -1, 2, 1] Sum: 6

Common Pitfalls, Edge Cases & Memory Considerations

1. Empty Array

Return 0 or handle gracefully.

c

if (n <= 0) return 0;

2. All Negative Elements

Kadane’s algorithm correctly returns the maximum single element.

c

int arr[] = {-2, -4}; /* Result: -2 */

Important: Initialize maxEnding and result to arr[0], not 0.

3. Integer Overflow

For large values, the sum may exceed int.

c

long long maxEnding = arr[0]; /* Use long long for large sums */

Solution: Use long long for the result and intermediate sums.

4. Initialization Error

Initializing maxEnding = 0 breaks all-negative arrays.

c

int maxEnding = 0; /* BUG: returns 0 for all-negative arrays */ int maxEnding = arr[0]; /* Correct */

5. Off-by-One in Subarray Tracking

Record currentStart only when starting fresh.

c

if (maxEnding + arr[i] > arr[i]) { maxEnding += arr[i]; } else { maxEnding = arr[i]; currentStart = i; }

6. Confusing maxEnding and result

  • maxEnding: maximum sum ending at current index.
  • result: maximum sum found so far.

7. Using >= vs >

Using >= may change which subarray is returned when ties exist.

c

if (maxEnding > result) { /* Strict: keeps first occurrence */ result = maxEnding; }

8. Modifying the Input

Kadane’s algorithm does not modify the input.

9. Non-Contiguous Subarrays

Kadane’s only works for contiguous subarrays. For non-contiguous, sum all positive elements.

10. Memory Considerations

  • Both approaches use O(1) extra space.
  • No dynamic allocation required.
  • Use long long for very large arrays.
  • Subarray tracking adds constant space.

Summary & Best Practices

Kadane’s Algorithm is the optimal solution for the maximum subarray sum problem, achieving O(n) time and O(1) space. The key insight is deciding at each element whether to extend the previous subarray or start fresh.

Key Takeaways

  • Maximum subarray sum: contiguous subarray with largest sum.
  • Naive: O(n²) time, O(1) space.
  • Kadane: O(n) time, O(1) space.
  • Initialize with arr[0], not 0.
  • Recurrence: maxEnding = max(maxEnding + arr[i], arr[i]).
  • Works correctly with all-negative arrays.

Best Practices

  1. Use Kadane’s Algorithm for O(n) time and O(1) space.
  2. Initialize to arr[0] to handle all-negative arrays.
  3. Use long long for large arrays to prevent overflow.
  4. Track start/end indices when the actual subarray is needed.
  5. Handle edge cases: empty, single element, all-negative.
  6. Verify with naive approach during testing.
  7. Document the recurrence in comments.
  8. Test with zeros and alternating signs.
  9. Use > (not >=) to keep the first occurrence.
  10. Profile for large arrays.

By mastering Kadane’s Algorithm, you’ll have a powerful tool for solving dynamic programming and array problems.

Learn to find a sorted subsequence of size 3 in C using auxiliary arrays and single-pass greedy approach. Master O(n) time and O(1) space.

Sorted Subsequence of Size 3 in C: O(n) Solution

Sorted Subsequence of Size 3 in C: A Comprehensive Tutorial

Given an array of n integers, the task is to find three elements arr[i] < arr[j] < arr[k] such that i < j < k in O(n) time. This classic problem tests your understanding of array traversal, auxiliary arrays, and the greedy technique for finding increasing subsequences.

This tutorial provides an in-depth exploration of finding a sorted subsequence of size 3 in C: naive auxiliary array approach, optimized single-pass approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Find three indices i < j < k such that arr[i] < arr[j] < arr[k]. If no such triplet exists, report that.

Examples:

text

Input: [12, 11, 10, 5, 6, 2, 30] Output: 5, 6, 30 Input: [1, 2, 3, 4] Output: 1, 2, 3 Input: [4, 3, 2, 1] Output: No such triplet exists.

Why This Problem Matters

  • Greedy algorithm design: Demonstrates optimal substructure.
  • Auxiliary arrays: Foundation for many array problems.
  • Single-pass technique: Teaches efficient traversal.
  • Interview preparation: Common coding interview question.
  • Longest Increasing Subsequence (LIS): Foundation for LIS variants.

Key Insight

We need to find an element arr[j] that has:

  • A smaller element on its left (arr[i] < arr[j])
  • A larger element on its right (arr[j] < arr[k])

Approaches Overview

ApproachTime ComplexitySpace Complexity
Auxiliary arraysO(n)O(n)
Single-pass greedyO(n)O(1)

Syntax & Core Mechanics

Approach 1: Auxiliary Arrays

Precompute smaller[i] (index of a smaller element on the left) and greater[i] (index of a greater element on the right).

c

#include <stdio.h> #include <stdlib.h> int findTriplet(const int arr[], int n, int *out) { if (n < 3) return 0; int *smaller = malloc(n * sizeof(int)); int *greater = malloc(n * sizeof(int)); if (smaller == NULL || greater == NULL) { free(smaller); free(greater); return 0; } /* smaller[i] = index of a smaller element on the left */ int minIdx = 0; smaller[0] = -1; for (int i = 1; i < n; i++) { if (arr[i] <= arr[minIdx]) { minIdx = i; smaller[i] = -1; } else { smaller[i] = minIdx; } } /* greater[i] = index of a greater element on the right */ int maxIdx = n - 1; greater[n - 1] = -1; for (int i = n - 2; i >= 0; i--) { if (arr[i] >= arr[maxIdx]) { maxIdx = i; greater[i] = -1; } else { greater[i] = maxIdx; } } /* Find index with both smaller and greater */ int found = 0; for (int i = 0; i < n; i++) { if (smaller[i] != -1 && greater[i] != -1) { out[0] = arr[smaller[i]]; out[1] = arr[i]; out[2] = arr[greater[i]]; found = 1; break; } } free(smaller); free(greater); return found; } int main(void) { int arr[] = {12, 11, 10, 5, 6, 2, 30}; int n = sizeof(arr) / sizeof(arr[0]); int result[3]; if (findTriplet(arr, n, result)) { printf("Triplet: %d, %d, %d\n", result[0], result[1], result[2]); } else { printf("No such triplet exists.\n"); } return 0; }

Expected output:

text

Triplet: 5, 6, 30

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

Approach 2: Single-Pass Greedy (Optimal)

Track two smallest elements seen so far (first, second) and their predecessor (prevFirst). When a third element greater than second is found, return the triplet.

c

#include <stdio.h> #include <limits.h> int findTriplet(const int arr[], int n, int *out) { if (n < 3) return 0; int first = INT_MAX; int second = INT_MAX; int prevFirst = INT_MAX; for (int i = 0; i < n; i++) { int x = arr[i]; if (x <= first) { first = x; } else if (x <= second) { second = x; prevFirst = first; } else { out[0] = prevFirst; out[1] = second; out[2] = x; return 1; } } return 0; } int main(void) { int arr[] = {12, 11, 10, 5, 6, 2, 30}; int n = sizeof(arr) / sizeof(arr[0]); int result[3]; if (findTriplet(arr, n, result)) { printf("Triplet: %d, %d, %d\n", result[0], result[1], result[2]); } else { printf("No such triplet exists.\n"); } return 0; }

Expected output:

text

Triplet: 5, 6, 30

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

Step-by-Step Trace for [12, 11, 10, 5, 6, 2, 30]

text

first = INF, second = INF, prevFirst = INF i=0, x=12: x <= first → first = 12 i=1, x=11: x <= first → first = 11 i=2, x=10: x <= first → first = 10 i=3, x=5: x <= first → first = 5 i=4, x=6: x <= second → second = 6, prevFirst = 5 i=5, x=2: x <= first → first = 2 (but second=6, prevFirst=5 still valid) i=6, x=30: x > second → return {prevFirst=5, second=6, x=30} ✓

Why prevFirst is Needed

When first gets updated to a smaller value, second still corresponds to a valid pair with the old first. prevFirst preserves that pairing.

text

Consider [3, 4, -1, 0, 2]: first=3, second=4, prevFirst=3 after i=1 i=2: x=-1 <= first → first=-1 i=3: x=0 <= second → second=0, prevFirst=-1 i=4: x=2 > second → return {-1, 0, 2} ✓

Full Practical Code Example with Explanation

This program demonstrates both approaches with verification, performance comparison, and edge cases.

Program: Sorted Subsequence of Size 3

c

/** * sorted_subsequence_3.c * Finds a sorted subsequence of size 3 in O(n) time. * Compares auxiliary array and single-pass greedy approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ int findTripletAux(const int arr[], int n, int *out); int findTripletGreedy(const int arr[], int n, int *out); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void printTriplet(const int *triplet); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Sorted Subsequence of Size 3 Finder ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate with Sample Arrays\n"); printf("2. Enter Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; int triplet[3]; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements:\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); if (findTripletGreedy(arr, n, triplet)) { printf("Triplet: %d, %d, %d\n", triplet[0], triplet[1], triplet[2]); } else { printf("No such triplet exists.\n"); } break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Approach 1: Auxiliary arrays */ int findTripletAux(const int arr[], int n, int *out) { if (n < 3) return 0; int *smaller = malloc(n * sizeof(int)); int *greater = malloc(n * sizeof(int)); if (smaller == NULL || greater == NULL) { free(smaller); free(greater); return 0; } /* smaller[i] = index of smaller element on left */ int minIdx = 0; smaller[0] = -1; for (int i = 1; i < n; i++) { if (arr[i] <= arr[minIdx]) { minIdx = i; smaller[i] = -1; } else { smaller[i] = minIdx; } } /* greater[i] = index of greater element on right */ int maxIdx = n - 1; greater[n - 1] = -1; for (int i = n - 2; i >= 0; i--) { if (arr[i] >= arr[maxIdx]) { maxIdx = i; greater[i] = -1; } else { greater[i] = maxIdx; } } int found = 0; for (int i = 0; i < n; i++) { if (smaller[i] != -1 && greater[i] != -1) { out[0] = arr[smaller[i]]; out[1] = arr[i]; out[2] = arr[greater[i]]; found = 1; break; } } free(smaller); free(greater); return found; } /* Approach 2: Single-pass greedy */ int findTripletGreedy(const int arr[], int n, int *out) { if (n < 3) return 0; int first = INT_MAX; int second = INT_MAX; int prevFirst = INT_MAX; for (int i = 0; i < n; i++) { int x = arr[i]; if (x <= first) { first = x; } else if (x <= second) { second = x; prevFirst = first; } else { out[0] = prevFirst; out[1] = second; out[2] = x; return 1; } } return 0; } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Prints triplet */ void printTriplet(const int *triplet) { printf("(%d, %d, %d)\n", triplet[0], triplet[1], triplet[2]); } /* Demonstrates both approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {12, 11, 10, 5, 6, 2, 30}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int triplet[3]; printf("\nArray 1: "); printArray(arr1, n1); if (findTripletAux(arr1, n1, triplet)) { printf(" Aux: "); printTriplet(triplet); } if (findTripletGreedy(arr1, n1, triplet)) { printf(" Greedy: "); printTriplet(triplet); } int arr2[] = {1, 2, 3, 4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); if (findTripletAux(arr2, n2, triplet)) { printf(" Aux: "); printTriplet(triplet); } if (findTripletGreedy(arr2, n2, triplet)) { printf(" Greedy: "); printTriplet(triplet); } int arr3[] = {4, 3, 2, 1}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); if (findTripletAux(arr3, n3, triplet)) { printf(" Aux: "); printTriplet(triplet); } else { printf(" Aux: No triplet\n"); } if (findTripletGreedy(arr3, n3, triplet)) { printf(" Greedy: "); printTriplet(triplet); } else { printf(" Greedy: No triplet\n"); } int arr4[] = {3, 4, -1, 0, 2}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4: "); printArray(arr4, n4); if (findTripletGreedy(arr4, n4, triplet)) { printf(" Greedy: "); printTriplet(triplet); } } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Increasing array with some shuffling */ srand(42); for (int i = 0; i < n; i++) { arr[i] = i + 1; } /* Shuffle slightly to make it non-trivial */ for (int i = 0; i < 100; i++) { int a = rand() % n; int b = rand() % n; int tmp = arr[a]; arr[a] = arr[b]; arr[b] = tmp; } clock_t start, end; double timeTaken; int triplet[3]; start = clock(); int auxResult = findTripletAux(arr, n, triplet); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Aux (O(n) space): %s, Time = %.3f ms\n", auxResult ? "Found" : "Not found", timeTaken); start = clock(); int greedyResult = findTripletGreedy(arr, n, triplet); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Greedy (O(1) space): %s, Time = %.3f ms\n", greedyResult ? "Found" : "Not found", timeTaken); printf("\nVerification: %s\n", (auxResult == greedyResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int triplet[3]; /* Case 1: Empty array */ int arr1[] = {}; printf("Case 1: Empty array\n"); printf(" Result: %s\n", findTripletGreedy(arr1, 0, triplet) ? "Found" : "No triplet"); /* Case 2: Single element */ int arr2[] = {5}; printf("\nCase 2: Single element\n"); printf(" Array: "); printArray(arr2, 1); printf(" Result: %s\n", findTripletGreedy(arr2, 1, triplet) ? "Found" : "No triplet"); /* Case 3: Two elements */ int arr3[] = {1, 2}; printf("\nCase 3: Two elements\n"); printf(" Array: "); printArray(arr3, 2); printf(" Result: %s\n", findTripletGreedy(arr3, 2, triplet) ? "Found" : "No triplet"); /* Case 4: Exactly 3 increasing */ int arr4[] = {1, 2, 3}; printf("\nCase 4: Exactly 3 increasing\n"); printf(" Array: "); printArray(arr4, 3); if (findTripletGreedy(arr4, 3, triplet)) { printf(" Triplet: "); printTriplet(triplet); } /* Case 5: Decreasing */ int arr5[] = {5, 4, 3, 2, 1}; printf("\nCase 5: Decreasing\n"); printf(" Array: "); printArray(arr5, 5); printf(" Result: %s\n", findTripletGreedy(arr5, 5, triplet) ? "Found" : "No triplet"); /* Case 6: Equal elements */ int arr6[] = {2, 2, 2, 2}; printf("\nCase 6: Equal elements\n"); printf(" Array: "); printArray(arr6, 4); printf(" Result: %s\n", findTripletGreedy(arr6, 4, triplet) ? "Found" : "No triplet"); /* Case 7: Triplet at end */ int arr7[] = {9, 8, 7, 1, 2, 3}; printf("\nCase 7: Triplet at end\n"); printf(" Array: "); printArray(arr7, 6); if (findTripletGreedy(arr7, 6, triplet)) { printf(" Triplet: "); printTriplet(triplet); } /* Case 8: Negative numbers */ int arr8[] = {-5, -3, -1, -4, 0}; printf("\nCase 8: Negative numbers\n"); printf(" Array: "); printArray(arr8, 5); if (findTripletGreedy(arr8, 5, triplet)) { printf(" Triplet: "); printTriplet(triplet); } } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Auxiliary arrays: Precomputes smaller/greater for each index; O(n) time, O(n) space.
  2. Single-pass greedy: Tracks first, second, and prevFirst; O(n) time, O(1) space.

Expected Output (sample run)

text

=== Sorted Subsequence of Size 3 Finder === --- Menu --- 1. Demonstrate with Sample Arrays 2. Enter Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [12, 11, 10, 5, 6, 2, 30] Aux: (5, 6, 30) Greedy: (5, 6, 30) Array 2: [1, 2, 3, 4] Aux: (1, 2, 3) Greedy: (1, 2, 3) Array 3: [4, 3, 2, 1] Aux: No triplet Greedy: No triplet Array 4: [3, 4, -1, 0, 2] Greedy: (-1, 0, 2) --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Result: No triplet Case 2: Single element Array: [5] Result: No triplet Case 3: Two elements Array: [1, 2] Result: No triplet Case 4: Exactly 3 increasing Array: [1, 2, 3] Triplet: (1, 2, 3) Case 5: Decreasing Array: [5, 4, 3, 2, 1] Result: No triplet Case 6: Equal elements Array: [2, 2, 2, 2] Result: No triplet Case 7: Triplet at end Array: [9, 8, 7, 1, 2, 3] Triplet: (1, 2, 3) Case 8: Negative numbers Array: [-5, -3, -1, -4, 0] Triplet: (-5, -3, -1)

Common Pitfalls, Edge Cases & Memory Considerations

1. Confusing prevFirst with first

In the greedy approach, when first updates, prevFirst must retain the old value paired with second.

c

if (x <= first) { first = x; /* Don't overwrite prevFirst here */ } else if (x <= second) { second = x; prevFirst = first; /* Save old first */ }

2. Using < vs <=

The problem requires strictly increasing. Using <= allows equal elements, which breaks the requirement.

c

if (x <= first) first = x; /* Correct for strictly increasing */ else if (x <= second) second = x; else /* Triplet found */

3. Array Size Less Than 3

Return “no triplet” for n < 3.

c

if (n < 3) return 0;

4. Empty Array

No triplet possible.

5. All Equal Elements

No strictly increasing triplet; return no triplet.

c

int arr[] = {2, 2, 2, 2}; /* Result: No triplet */

6. Decreasing Array

No triplet possible.

c

int arr[] = {5, 4, 3, 2, 1}; /* Result: No triplet */

7. Negative Numbers

Works correctly with signed comparisons.

c

int arr[] = {-5, -3, -1, -4, 0}; /* Triplet: (-5, -3, -1) */

8. Integer Overflow with INT_MAX

Using INT_MAX as initial sentinel is safe for int values.

9. Memory Leaks in Auxiliary Approach

Free smaller and greater arrays.

c

free(smaller); free(greater);

10. Memory Considerations

  • Auxiliary: O(n) extra space.
  • Greedy: O(1) extra space — preferred.
  • No dynamic allocation in greedy approach.
  • Use int for elements; INT_MAX for sentinel.

Summary & Best Practices

Finding a sorted subsequence of size 3 in O(n) time is elegantly solved by the single-pass greedy approach. The auxiliary array approach is more intuitive but uses extra space.

Key Takeaways

  • Find i < j < k with arr[i] < arr[j] < arr[k].
  • Auxiliary: precompute smaller/greater; O(n) space.
  • Greedy: track first, second, prevFirst; O(1) space.
  • Use <= for updating first/second to enforce strict increase.
  • prevFirst preserves the valid pair when first updates.
  • Return any valid triplet; no need to find all.

Best Practices

  1. Use the single-pass greedy approach for O(1) space.
  2. Use INT_MAX sentinels for first and second.
  3. Maintain prevFirst when updating first.
  4. Use <= in comparisons to enforce strict increase.
  5. Handle edge cases: n < 3, empty, all equal, decreasing.
  6. Free auxiliary arrays in the O(n) space approach.
  7. Test with negative numbers and mixed values.
  8. Verify with both approaches during testing.
  9. Document the greedy insight in comments.
  10. Return early when triplet is found.

By mastering this problem, you’ll understand greedy single-pass techniques and be prepared for related problems like Longest Increasing Subsequence and increasing triplets.

Learn to find the only repeating element in an array from 1 to n-1 in C: naive, sorting, hash, sum, XOR, marking, and Floyd's cycle detection.

Find Only Repeating Element 1 to n-1 in C

Only Repeating Element From 1 to n-1 in C: A Comprehensive Tutorial

Given an array of size n filled with numbers from 1 to n-1 in random order, where exactly one number repeats, the task is to find that repeating element. This classic problem tests your understanding of multiple algorithmic techniques—from naive nested loops to elegant bitwise and mathematical solutions.

This tutorial provides an in-depth exploration of finding the only repeating element in C: naive nested loops, sorting, hash set, sum formula, XOR, element-as-index marking, Floyd’s cycle detection, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n containing numbers from 1 to n-1, where exactly one number appears twice and all others appear once, find the repeating element.

Examples:

text

Input: [1, 3, 2, 3, 4] Output: 3 Input: [1, 5, 1, 2, 3, 4] Output: 1

Why This Problem Matters

  • Multiple approaches: Demonstrates trade-offs between time and space.
  • Bitwise operations: XOR cancellation property.
  • Mathematical reasoning: Sum of first n-1 natural numbers.
  • Cycle detection: Floyd’s algorithm adaptation.
  • Interview preparation: Very common coding interview question.

Approaches Overview

ApproachTimeSpaceNotes
Naive nested loopsO(n²)O(1)Simple but slow
SortingO(n log n)O(1) or O(n)Modifies array
Hash setO(n) avgO(n)Extra memory
Sum formulaO(n)O(1)Overflow risk
XORO(n)O(1)No overflow, elegant
Element as indexO(n)O(1)Modifies array
Floyd’s cycle detectionO(n)O(1)Read-only, advanced

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Check each element against subsequent elements.

c

#include <stdio.h> int findDuplicateNaive(const int arr[], int n) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] == arr[j]) { return arr[i]; } } } return -1; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateNaive(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

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

Approach 2: Sorting

Sort the array; the duplicate will be adjacent.

c

#include <stdio.h> #include <stdlib.h> int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } int findDuplicateSorted(int arr[], int n) { qsort(arr, n, sizeof(int), compareInts); for (int i = 0; i < n - 1; i++) { if (arr[i] == arr[i + 1]) { return arr[i]; } } return -1; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateSorted(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

Time: O(n log n), Space: O(1) (in-place)

Approach 3: Hash Set

Track visited elements; return on second encounter.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 4096 typedef struct { int *keys; int *occupied; int capacity; } HashSet; HashSet *createHashSet(int capacity) { HashSet *set = malloc(sizeof(HashSet)); if (set == NULL) return NULL; set->keys = malloc(capacity * sizeof(int)); set->occupied = calloc(capacity, sizeof(int)); if (set->keys == NULL || set->occupied == NULL) { free(set->keys); free(set->occupied); free(set); return NULL; } set->capacity = capacity; return set; } void freeHashSet(HashSet *set) { if (set != NULL) { free(set->keys); free(set->occupied); free(set); } } int hashFunc(int key, int cap) { return ((key % cap) + cap) % cap; } int contains(HashSet *set, int key) { int idx = hashFunc(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) return 1; idx = (idx + 1) % set->capacity; if (idx == start) break; } return 0; } void insert(HashSet *set, int key) { int idx = hashFunc(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) return; idx = (idx + 1) % set->capacity; if (idx == start) return; } set->keys[idx] = key; set->occupied[idx] = 1; } int findDuplicateHash(const int arr[], int n) { HashSet *set = createHashSet(TABLE_SIZE); if (set == NULL) return -1; int result = -1; for (int i = 0; i < n; i++) { if (contains(set, arr[i])) { result = arr[i]; break; } insert(set, arr[i]); } freeHashSet(set); return result; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateHash(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

Time: O(n) average, Space: O(n)

Approach 4: Sum Formula

Sum of 1..n-1 is (n-1)*n/2. Subtract from actual sum.

c

#include <stdio.h> int findDuplicateSum(const int arr[], int n) { long long sum = 0; for (int i = 0; i < n; i++) { sum += arr[i]; } long long expected = (long long)(n - 1) * n / 2; return (int)(sum - expected); } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateSum(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

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

Approach 5: XOR

XOR of 1..n-1 and all array elements; pairs cancel, leaving the duplicate.

c

#include <stdio.h> int findDuplicateXOR(const int arr[], int n) { int res = 0; for (int i = 0; i < n - 1; i++) { res ^= (i + 1); res ^= arr[i]; } res ^= arr[n - 1]; return res; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateXOR(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

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

Approach 6: Element as Index (Marking)

Negate the value at the index equal to the element’s value.

c

#include <stdio.h> #include <stdlib.h> int findDuplicateMarking(int arr[], int n) { for (int i = 0; i < n; i++) { int val = abs(arr[i]); if (arr[val] < 0) { return val; } arr[val] = -arr[val]; } return -1; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateMarking(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

Time: O(n), Space: O(1) — modifies array

Note: This approach assumes values are in [1, n-1] so arr[val] is always a valid index.

Approach 7: Floyd’s Cycle Detection

Treat the array as a linked list where arr[i] is the next pointer.

c

#include <stdio.h> int findDuplicateFloyd(const int arr[], int n) { int slow = arr[0]; int fast = arr[0]; /* Phase 1: Find meeting point */ do { slow = arr[slow]; fast = arr[arr[fast]]; } while (slow != fast); /* Phase 2: Find cycle entry (duplicate) */ fast = arr[0]; while (slow != fast) { slow = arr[slow]; fast = arr[fast]; } return slow; } int main(void) { int arr[] = {1, 3, 2, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Duplicate: %d\n", findDuplicateFloyd(arr, n)); return 0; }

Expected output:

text

Duplicate: 3

Time: O(n), Space: O(1), Read-only


Full Practical Code Example with Explanation

This program demonstrates all seven approaches with verification, performance comparison, and edge cases.

Program: Duplicate Finder Comparison

c

/** * only_repeating.c * Finds the only repeating element in array of 1..n-1. * Compares all major approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ int findDuplicateNaive(const int arr[], int n); int findDuplicateSorted(int arr[], int n); int findDuplicateHash(const int arr[], int n); int findDuplicateSum(const int arr[], int n); int findDuplicateXOR(const int arr[], int n); int findDuplicateMarking(int arr[], int n); int findDuplicateFloyd(const int arr[], int n); int compareInts(const void *a, const void *b); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Only Repeating Element Finder (1..n-1) ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int copy[MAX_SIZE]; int n; if (readInteger("Enter array size (2-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 2 || n > MAX_SIZE) { printf("Size must be 2-%d.\n", MAX_SIZE); break; } printf("Enter %d elements (1..%d, one repeated):\n", n, n - 1); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); memcpy(copy, arr, n * sizeof(int)); printf("Naive: %d\n", findDuplicateNaive(arr, n)); memcpy(copy, arr, n * sizeof(int)); printf("Sorted: %d\n", findDuplicateSorted(copy, n)); printf("Hash: %d\n", findDuplicateHash(arr, n)); printf("Sum: %d\n", findDuplicateSum(arr, n)); printf("XOR: %d\n", findDuplicateXOR(arr, n)); memcpy(copy, arr, n * sizeof(int)); printf("Marking: %d\n", findDuplicateMarking(copy, n)); printf("Floyd: %d\n", findDuplicateFloyd(arr, n)); break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Naive: O(n^2) */ int findDuplicateNaive(const int arr[], int n) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] == arr[j]) return arr[i]; } } return -1; } /* Comparison function */ int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } /* Sorting: O(n log n) */ int findDuplicateSorted(int arr[], int n) { qsort(arr, n, sizeof(int), compareInts); for (int i = 0; i < n - 1; i++) { if (arr[i] == arr[i + 1]) return arr[i]; } return -1; } /* Hash set: O(n) average */ #define TABLE_SIZE 4096 typedef struct { int *keys; int *occupied; int capacity; } HashSet; HashSet *createHashSet(int capacity) { HashSet *set = malloc(sizeof(HashSet)); if (set == NULL) return NULL; set->keys = malloc(capacity * sizeof(int)); set->occupied = calloc(capacity, sizeof(int)); if (set->keys == NULL || set->occupied == NULL) { free(set->keys); free(set->occupied); free(set); return NULL; } set->capacity = capacity; return set; } void freeHashSet(HashSet *set) { if (set != NULL) { free(set->keys); free(set->occupied); free(set); } } int hashFunc(int key, int cap) { return ((key % cap) + cap) % cap; } int contains(HashSet *set, int key) { int idx = hashFunc(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) return 1; idx = (idx + 1) % set->capacity; if (idx == start) break; } return 0; } void insert(HashSet *set, int key) { int idx = hashFunc(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) return; idx = (idx + 1) % set->capacity; if (idx == start) return; } set->keys[idx] = key; set->occupied[idx] = 1; } int findDuplicateHash(const int arr[], int n) { HashSet *set = createHashSet(TABLE_SIZE); if (set == NULL) return -1; int result = -1; for (int i = 0; i < n; i++) { if (contains(set, arr[i])) { result = arr[i]; break; } insert(set, arr[i]); } freeHashSet(set); return result; } /* Sum formula: O(n) */ int findDuplicateSum(const int arr[], int n) { long long sum = 0; for (int i = 0; i < n; i++) sum += arr[i]; long long expected = (long long)(n - 1) * n / 2; return (int)(sum - expected); } /* XOR: O(n) */ int findDuplicateXOR(const int arr[], int n) { int res = 0; for (int i = 0; i < n - 1; i++) { res ^= (i + 1); res ^= arr[i]; } res ^= arr[n - 1]; return res; } /* Marking: O(n), modifies array */ int findDuplicateMarking(int arr[], int n) { for (int i = 0; i < n; i++) { int val = abs(arr[i]); if (val < 1 || val > n - 1) continue; if (arr[val] < 0) return val; arr[val] = -arr[val]; } return -1; } /* Floyd's cycle detection: O(n), read-only */ int findDuplicateFloyd(const int arr[], int n) { int slow = arr[0]; int fast = arr[0]; do { slow = arr[slow]; fast = arr[arr[fast]]; } while (slow != fast); fast = arr[0]; while (slow != fast) { slow = arr[slow]; fast = arr[fast]; } return slow; } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 3, 2, 3, 4}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int copy[MAX_SIZE]; printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %d\n", findDuplicateNaive(arr1, n1)); memcpy(copy, arr1, n1 * sizeof(int)); printf("Sorted: %d\n", findDuplicateSorted(copy, n1)); printf("Hash: %d\n", findDuplicateHash(arr1, n1)); printf("Sum: %d\n", findDuplicateSum(arr1, n1)); printf("XOR: %d\n", findDuplicateXOR(arr1, n1)); memcpy(copy, arr1, n1 * sizeof(int)); printf("Marking: %d\n", findDuplicateMarking(copy, n1)); printf("Floyd: %d\n", findDuplicateFloyd(arr1, n1)); int arr2[] = {1, 5, 1, 2, 3, 4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Sum: %d\n", findDuplicateSum(arr2, n2)); printf("XOR: %d\n", findDuplicateXOR(arr2, n2)); printf("Floyd: %d\n", findDuplicateFloyd(arr2, n2)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *arr = malloc(n * sizeof(int)); int *copy = malloc(n * sizeof(int)); if (arr == NULL || copy == NULL) { fprintf(stderr, "Memory allocation failed.\n"); free(arr); free(copy); return; } /* Fill 1..n-1 plus duplicate n/2 */ int dup = n / 2; for (int i = 0; i < n - 1; i++) arr[i] = i + 1; arr[n - 1] = dup; clock_t start, end; double timeTaken; memcpy(copy, arr, n * sizeof(int)); start = clock(); int sortedResult = findDuplicateSorted(copy, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted: Result = %d, Time = %.3f ms\n", sortedResult, timeTaken); start = clock(); int hashResult = findDuplicateHash(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash: Result = %d, Time = %.3f ms\n", hashResult, timeTaken); start = clock(); int sumResult = findDuplicateSum(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sum: Result = %d, Time = %.3f ms\n", sumResult, timeTaken); start = clock(); int xorResult = findDuplicateXOR(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("XOR: Result = %d, Time = %.3f ms\n", xorResult, timeTaken); memcpy(copy, arr, n * sizeof(int)); start = clock(); int markResult = findDuplicateMarking(copy, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Marking: Result = %d, Time = %.3f ms\n", markResult, timeTaken); start = clock(); int floydResult = findDuplicateFloyd(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Floyd: Result = %d, Time = %.3f ms\n", floydResult, timeTaken); printf("\nVerification: %s\n", (sumResult == xorResult && xorResult == floydResult && floydResult == hashResult) ? "PASS" : "FAIL"); free(arr); free(copy); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int copy[MAX_SIZE]; /* Case 1: n=2, duplicate 1 */ int arr1[] = {1, 1}; printf("Case 1: n=2, [1, 1]\n"); printf(" XOR: %d\n", findDuplicateXOR(arr1, 2)); printf(" Sum: %d\n", findDuplicateSum(arr1, 2)); printf(" Floyd: %d\n", findDuplicateFloyd(arr1, 2)); /* Case 2: Duplicate at start */ int arr2[] = {1, 1, 2, 3}; printf("\nCase 2: Duplicate at start\n"); printf(" Array: "); printArray(arr2, 4); printf(" XOR: %d\n", findDuplicateXOR(arr2, 4)); printf(" Floyd: %d\n", findDuplicateFloyd(arr2, 4)); /* Case 3: Duplicate at end */ int arr3[] = {1, 2, 3, 3}; printf("\nCase 3: Duplicate at end\n"); printf(" Array: "); printArray(arr3, 4); printf(" XOR: %d\n", findDuplicateXOR(arr3, 4)); printf(" Floyd: %d\n", findDuplicateFloyd(arr3, 4)); /* Case 4: Duplicate is max (n-1) */ int arr4[] = {1, 2, 4, 3, 4}; printf("\nCase 4: Duplicate is max\n"); printf(" Array: "); printArray(arr4, 5); printf(" XOR: %d\n", findDuplicateXOR(arr4, 5)); printf(" Floyd: %d\n", findDuplicateFloyd(arr4, 5)); /* Case 5: Duplicate is 1 in larger array */ int arr5[] = {2, 3, 1, 4, 1}; printf("\nCase 5: Duplicate is 1\n"); printf(" Array: "); printArray(arr5, 5); printf(" XOR: %d\n", findDuplicateXOR(arr5, 5)); printf(" Floyd: %d\n", findDuplicateFloyd(arr5, 5)); /* Case 6: Marking approach demonstration */ int arr6[] = {1, 3, 2, 3, 4}; memcpy(copy, arr6, 5 * sizeof(int)); printf("\nCase 6: Marking approach\n"); printf(" Array: "); printArray(copy, 5); printf(" Marking: %d\n", findDuplicateMarking(copy, 5)); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Naive: O(n²) nested loops.
  2. Sorting: O(n log n), adjacent duplicates.
  3. Hash: O(n) average, O(n) space.
  4. Sum: O(n) time, O(1) space, arithmetic formula.
  5. XOR: O(n) time, O(1) space, no overflow.
  6. Marking: O(n) time, O(1) space, modifies array.
  7. Floyd’s: O(n) time, O(1) space, read-only.

Expected Output (sample run)

text

=== Only Repeating Element Finder (1..n-1) === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [1, 3, 2, 3, 4] Naive: 3 Sorted: 3 Hash: 3 Sum: 3 XOR: 3 Marking: 3 Floyd: 3 Array 2: [1, 5, 1, 2, 3, 4] Sum: 1 XOR: 1 Floyd: 1 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: n=2, [1, 1] XOR: 1 Sum: 1 Floyd: 1 Case 2: Duplicate at start Array: [1, 1, 2, 3] XOR: 1 Floyd: 1 Case 3: Duplicate at end Array: [1, 2, 3, 3] XOR: 3 Floyd: 3 Case 4: Duplicate is max Array: [1, 2, 4, 3, 4] XOR: 4 Floyd: 4 Case 5: Duplicate is 1 Array: [2, 3, 1, 4, 1] XOR: 1 Floyd: 1 Case 6: Marking approach Array: [1, 3, 2, 3, 4] Marking: 3

Common Pitfalls, Edge Cases & Memory Considerations

1. Confusing n and Range

Array size is n; values are in [1, n-1]. Sum formula uses (n-1)*n/2.

2. Integer Overflow in Sum

For large n, sum may overflow int.

c

long long sum = 0; /* Use long long */

3. Off-by-One in XOR

XOR loops 1..n-1 and all array elements (including duplicate).

c

for (int i = 0; i < n - 1; i++) { res ^= (i + 1); res ^= arr[i]; } res ^= arr[n - 1]; /* Last element */

4. Marking Approach Requires In-Range Values

If any value is outside [1, n-1], marking accesses invalid indices.

Solution: Validate input or skip out-of-range values.

5. Marking Modifies Array

Preserve original if needed by copying.

6. Floyd’s Requires Valid Index Chain

Array must be such that arr[0] doesn’t cause out-of-bounds.

Solution: Problem guarantees values in [1, n-1]; arr[0] is safe.

7. n = 2 Edge Case

Array size 2, values [1, 1] (only possible duplicate).

8. Empty Array

Invalid input; problem requires n ≥ 2.

9. Duplicate at Index 0

Works with all approaches; Floyd starts at arr[0].

10. Memory Considerations

  • Naive: O(1) extra space.
  • Sorting: O(1) extra space (in-place).
  • Hash: O(n) extra space.
  • Sum: O(1) extra space.
  • XOR: O(1) extra space — preferred.
  • Marking: O(1) extra space, modifies input.
  • Floyd: O(1) extra space, read-only — most elegant.

Summary & Best Practices

Finding the only repeating element in an array from 1..n-1 is elegantly solved using XOR (O(n), O(1), no overflow) or Floyd’s cycle detection (O(n), O(1), read-only). Both avoid extra memory.

Key Takeaways

  • Array size n, values [1, n-1], one duplicate.
  • Naive: O(n²).
  • Sorting: O(n log n).
  • Hash: O(n) time, O(n) space.
  • Sum: O(n) time, O(1) space (overflow risk).
  • XOR: O(n) time, O(1) space — preferred.
  • Marking: O(n) time, O(1) space, modifies array.
  • Floyd’s: O(n) time, O(1) space, read-only.

Best Practices

  1. Use XOR for O(n) time, O(1) space, no overflow.
  2. Use Floyd’s when input must not be modified.
  3. Use sum formula for simplicity when n is small.
  4. Use hash set when other approaches aren’t applicable.
  5. Use long long for sum to prevent overflow.
  6. Verify with multiple approaches during testing.
  7. Handle edge cases: n=2, duplicate at boundaries.
  8. Free dynamically allocated memory.
  9. Validate input (values in [1, n-1]).
  10. Profile for large arrays.

By mastering this problem, you’ll understand multiple algorithmic techniques applicable to many array and bitwise problems.

Learn to find missing and repeating numbers in an array in C: visited, marking, math equations, and XOR approaches. Master O(n) time with O(1) space.

Missing and Repeating Number in Array: C Guide

Missing and Repeating in an Array in C: A Comprehensive Tutorial

Given an unsorted array of size n containing elements from the range 1 to n, exactly one number is missing and another appears twice. The task is to find both the duplicate (repeating) and the missing number. This is a classic algorithmic problem that tests your understanding of array manipulation, mathematical equations, bitwise operations, and time-space trade-offs.

This tutorial provides an in-depth exploration of finding both the missing and repeating numbers in C: visited array approach, array marking, two math equations, XOR approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n with elements from the range [1, n]:

  • Exactly one number appears twice (repeating).
  • Exactly one number is missing.
  • All other numbers appear exactly once.

Find both the repeating and missing numbers.

Examples:

text

Input: [3, 1, 3] Output: [3, 2] (3 repeats, 2 is missing) Input: [4, 3, 6, 2, 1, 1] Output: [1, 5] (1 repeats, 5 is missing)

Why This Problem Matters

  • Multiple approaches: Demonstrates trade-offs between time and space.
  • Mathematical reasoning: Sum and sum-of-squares equations.
  • Bitwise operations: XOR-based grouping technique.
  • Interview preparation: Very common coding interview question.
  • Real-world applications: Data integrity, duplicate detection, sequence validation.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Visited arrayO(n)O(n)
Array marking (negation)O(n)O(1)
Two math equationsO(n)O(1)
XORO(n)O(1)

Syntax & Core Mechanics

Approach 1: Visited Array (Frequency Count)

Use a frequency array to count occurrences.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int *freq = calloc(n + 1, sizeof(int)); if (freq == NULL) { fprintf(stderr, "Memory allocation failed.\n"); *repeating = -1; *missing = -1; return; } for (int i = 0; i < n; i++) { freq[arr[i]]++; } *repeating = -1; *missing = -1; for (int i = 1; i <= n; i++) { if (freq[i] == 0) *missing = i; else if (freq[i] == 2) *repeating = i; } free(freq); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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

Approach 2: Array Marking (Negation)

Use the input array itself to track visited elements by negating values.

c

#include <stdio.h> #include <stdlib.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int *copy = malloc(n * sizeof(int)); if (copy == NULL) { *repeating = -1; *missing = -1; return; } memcpy(copy, arr, n * sizeof(int)); *repeating = -1; /* Mark visited by negating */ for (int i = 0; i < n; i++) { int val = abs(copy[i]); if (copy[val - 1] > 0) { copy[val - 1] = -copy[val - 1]; } else { *repeating = val; /* Already visited */ } } *missing = -1; for (int i = 0; i < n; i++) { if (copy[i] > 0) { *missing = i + 1; break; } } free(copy); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

Time: O(n), Space: O(n) for copy (O(1) if input can be modified)

Approach 3: Two Math Equations

Use sum and sum-of-squares formulas.

Derivation:

text

Let x = missing, y = repeating. Sum equation: x - y = S_expected - S_actual (call it s) Squares equation: x² - y² = SS_expected - SS_actual (call it ssq) From squares: (x - y)(x + y) = ssq → (x + y) = ssq / s Solving: x = (s + ssq/s) / 2 y = x - s

c

#include <stdio.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { long long s = (long long)n * (n + 1) / 2; long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; for (int i = 0; i < n; i++) { s -= arr[i]; ssq -= (long long)arr[i] * arr[i]; } *missing = (int)((s + ssq / s) / 2); *repeating = (int)(*missing - s); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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

Approach 4: XOR

XOR all array elements and numbers 1..n to get x ^ y. Use a set bit to divide into groups.

c

#include <stdio.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int xorVal = 0; for (int i = 0; i < n; i++) { xorVal ^= arr[i]; xorVal ^= (i + 1); } int setBit = xorVal & ~(xorVal - 1); int x = 0, y = 0; for (int i = 0; i < n; i++) { if (arr[i] & setBit) x ^= arr[i]; else y ^= arr[i]; if ((i + 1) & setBit) x ^= (i + 1); else y ^= (i + 1); } /* Determine which is which */ int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { *missing = x; *repeating = y; } else { *missing = y; *repeating = x; } } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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


Full Practical Code Example with Explanation

This program demonstrates all four approaches with verification, performance comparison, and edge cases.

Program: Missing and Repeating Finder

c

/** * missing_repeating.c * Finds both missing and repeating numbers in an array. * Compares visited array, marking, math, and XOR approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ void findVisited(const int arr[], int n, int *repeating, int *missing); void findMarking(const int arr[], int n, int *repeating, int *missing); void findMath(const int arr[], int n, int *repeating, int *missing); void findXOR(const int arr[], int n, int *repeating, int *missing); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Missing and Repeating Number Finder ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements (1..%d, one missing, one repeated):\n", n, n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); int rep, mis; findVisited(arr, n, &rep, &mis); printf("Visited: Repeating = %d, Missing = %d\n", rep, mis); findMarking(arr, n, &rep, &mis); printf("Marking: Repeating = %d, Missing = %d\n", rep, mis); findMath(arr, n, &rep, &mis); printf("Math: Repeating = %d, Missing = %d\n", rep, mis); findXOR(arr, n, &rep, &mis); printf("XOR: Repeating = %d, Missing = %d\n", rep, mis); break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Approach 1: Visited array */ void findVisited(const int arr[], int n, int *repeating, int *missing) { int *freq = calloc(n + 1, sizeof(int)); if (freq == NULL) { *repeating = -1; *missing = -1; return; } for (int i = 0; i < n; i++) { if (arr[i] >= 1 && arr[i] <= n) { freq[arr[i]]++; } } *repeating = -1; *missing = -1; for (int i = 1; i <= n; i++) { if (freq[i] == 0) *missing = i; else if (freq[i] == 2) *repeating = i; } free(freq); } /* Approach 2: Array marking (negation) */ void findMarking(const int arr[], int n, int *repeating, int *missing) { int *copy = malloc(n * sizeof(int)); if (copy == NULL) { *repeating = -1; *missing = -1; return; } memcpy(copy, arr, n * sizeof(int)); *repeating = -1; for (int i = 0; i < n; i++) { int val = abs(copy[i]); if (val >= 1 && val <= n) { if (copy[val - 1] > 0) { copy[val - 1] = -copy[val - 1]; } else { *repeating = val; } } } *missing = -1; for (int i = 0; i < n; i++) { if (copy[i] > 0) { *missing = i + 1; break; } } free(copy); } /* Approach 3: Math equations */ void findMath(const int arr[], int n, int *repeating, int *missing) { long long s = (long long)n * (n + 1) / 2; long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; for (int i = 0; i < n; i++) { s -= arr[i]; ssq -= (long long)arr[i] * arr[i]; } if (s == 0) { *repeating = -1; *missing = -1; return; } *missing = (int)((s + ssq / s) / 2); *repeating = (int)(*missing - s); } /* Approach 4: XOR */ void findXOR(const int arr[], int n, int *repeating, int *missing) { int xorVal = 0; for (int i = 0; i < n; i++) { xorVal ^= arr[i]; xorVal ^= (i + 1); } int setBit = xorVal & ~(xorVal - 1); int x = 0, y = 0; for (int i = 0; i < n; i++) { if (arr[i] & setBit) x ^= arr[i]; else y ^= arr[i]; if ((i + 1) & setBit) x ^= (i + 1); else y ^= (i + 1); } int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { *missing = x; *repeating = y; } else { *missing = y; *repeating = x; } } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {3, 1, 3}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int rep, mis; printf("\nArray 1: "); printArray(arr1, n1); findVisited(arr1, n1, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMarking(arr1, n1, &rep, &mis); printf(" Marking: Rep=%d, Mis=%d\n", rep, mis); findMath(arr1, n1, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr1, n1, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); int arr2[] = {4, 3, 6, 2, 1, 1}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); findVisited(arr2, n2, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMarking(arr2, n2, &rep, &mis); printf(" Marking: Rep=%d, Mis=%d\n", rep, mis); findMath(arr2, n2, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr2, n2, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Fill 1..n, replace one with duplicate of another */ int missing = n / 2; int repeat = n / 3; for (int i = 0; i < n; i++) { arr[i] = i + 1; } /* Replace missing with repeat */ for (int i = 0; i < n; i++) { if (arr[i] == missing) { arr[i] = repeat; break; } } clock_t start, end; double timeTaken; int rep, mis; start = clock(); findVisited(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Visited: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findMarking(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Marking: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findMath(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Math: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findXOR(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("XOR: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int rep, mis; /* Case 1: n = 2 */ int arr1[] = {1, 1}; printf("Case 1: n=2, [1, 1]\n"); findVisited(arr1, 2, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMath(arr1, 2, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr1, 2, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 2: Repeat at start */ int arr2[] = {1, 1, 2, 3}; printf("\nCase 2: Repeat at start\n"); printf(" Array: "); printArray(arr2, 4); findMath(arr2, 4, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr2, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 3: Repeat at end */ int arr3[] = {1, 2, 3, 3}; printf("\nCase 3: Repeat at end\n"); printf(" Array: "); printArray(arr3, 4); findMath(arr3, 4, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr3, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 4: Missing 1 */ int arr4[] = {2, 2, 3, 4}; printf("\nCase 4: Missing 1\n"); printf(" Array: "); printArray(arr4, 4); findXOR(arr4, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 5: Missing n */ int arr5[] = {1, 1, 2, 3}; printf("\nCase 5: Missing n (4)\n"); printf(" Array: "); printArray(arr5, 4); findXOR(arr5, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Visited: Uses a frequency array; O(n) time, O(n) space.
  2. Marking: Negates visited indices in a copy; O(n) time, O(n) space (or O(1) if input is mutable).
  3. Math: Uses sum and sum-of-squares equations; O(n) time, O(1) space.
  4. XOR: Divides elements into groups by a set bit; O(n) time, O(1) space.

Expected Output (sample run)

text

=== Missing and Repeating Number Finder === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [3, 1, 3] Visited: Rep=3, Mis=2 Marking: Rep=3, Mis=2 Math: Rep=3, Mis=2 XOR: Rep=3, Mis=2 Array 2: [4, 3, 6, 2, 1, 1] Visited: Rep=1, Mis=5 Marking: Rep=1, Mis=5 Math: Rep=1, Mis=5 XOR: Rep=1, Mis=5 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: n=2, [1, 1] Visited: Rep=1, Mis=2 Math: Rep=1, Mis=2 XOR: Rep=1, Mis=2 Case 2: Repeat at start Array: [1, 1, 2, 3] Math: Rep=1, Mis=4 XOR: Rep=1, Mis=4 Case 3: Repeat at end Array: [1, 2, 3, 3] Math: Rep=3, Mis=4 XOR: Rep=3, Mis=4 Case 4: Missing 1 Array: [2, 2, 3, 4] XOR: Rep=2, Mis=1 Case 5: Missing n (4) Array: [1, 1, 2, 3] XOR: Rep=1, Mis=4

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow in Math Approach

n × (n+1) × (2n+1) / 6 overflows quickly for large n.

c

long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; /* Cast */

Solution: Use long long and cast before multiplication.

2. Division by Zero in Math Approach

If s == 0 (no missing/repeating, invalid input), division fails.

c

if (s == 0) { /* handle invalid input */ }

3. Array Marking Modifies Input

The marking approach requires modifying the array (or a copy).

Solution: Copy the array first if preservation is needed.

4. XOR Determination of Missing vs Repeating

XOR gives two values; must determine which is which by counting occurrences.

c

int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { /* x is missing, y is repeating */ }

5. Zero Values in Array

Problem specifies values in [1, n]; zero values are invalid input.

6. n = 1 Edge Case

If n = 1, the array has one element, and that element must be the repeating one (missing 1).

c

int arr[] = {1}; /* Repeating = 1, Missing = 2 ... but n=1 means values in [1,1] */ /* Actually the problem requires n >= 2 */

Solution: Handle n == 1 explicitly if needed.

7. Duplicate Values Beyond Two

Problem guarantees exactly one repeat; other inputs break the algorithm.

8. Hash Array Sizing

Ensure frequency array size is n + 1 (indices 0..n).

9. Memory Leaks

Free dynamically allocated frequency arrays.

10. Memory Considerations

  • Visited: O(n) extra space.
  • Marking: O(n) extra space (or O(1) if mutating input).
  • Math: O(1) extra space.
  • XOR: O(1) extra space — preferred for memory.
  • Use long long for math approach.

Summary & Best Practices

Finding the missing and repeating numbers in an array is solved elegantly using XOR (O(n) time, O(1) space) or math equations (O(n) time, O(1) space). Both approaches avoid extra memory.

Key Takeaways

  • Array has n elements from range 1..n.
  • One element repeats; one is missing.
  • Visited: O(n) time, O(n) space.
  • Marking: O(n) time, O(1)-O(n) space.
  • Math: O(n) time, O(1) space (watch for overflow).
  • XOR: O(n) time, O(1) space — preferred.

Best Practices

  1. Use XOR for O(n) time and O(1) space without overflow.
  2. Use math for simplicity when n is small.
  3. Use long long for math to prevent overflow.
  4. Use visited array for clarity when memory permits.
  5. Verify with multiple approaches during testing.
  6. Handle edge cases: n=2, missing/repeat at boundaries.
  7. Free dynamically allocated memory.
  8. Validate input (values in [1, n], exactly one repeat).
  9. Test with n=2 and larger values.
  10. Profile for large arrays.

By mastering this problem, you’ll understand multiple algorithmic techniques and their trade-offs, applicable to many array and bitwise problems.