CoreTechED

Loading

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.

Learn to find the unique element where all others appear twice in C: naive, hash map, and optimal XOR approach. Master O(n) time with O(1) space.

Unique Number in Array in C: XOR Method Guide

Unique Number in an Array (Every Element Appears Twice Except One) in C

Finding the unique element in an array where every other element appears exactly twice is a classic problem that tests your understanding of bitwise operations and algorithm optimization. This problem demonstrates the elegant power of XOR and is a favorite in coding interviews.

This tutorial provides an in-depth exploration of finding the unique number in C: naive nested-loop approach, hash-map approach, XOR approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array of integers where every element appears exactly twice except for one element that appears only once, find that unique element.

Examples:

text

Input: [2, 3, 5, 4, 5, 3, 4] Output: 2 Input: [2, 2, 5, 5, 20, 30, 30] Output: 20

Why This Problem Matters

  • Bitwise operations: Demonstrates XOR’s cancellation property.
  • Algorithm optimization: O(n²) → O(n) → O(n) with O(1) space.
  • Interview preparation: Extremely common coding interview question.
  • Memory efficiency: XOR approach uses no extra memory.
  • Foundation for advanced problems: Single Number II, III, and related variants.

Key Insight: XOR Cancellation Property

XOR (exclusive OR) has these properties:

PropertyFormula
Identitya ⊕ 0 = a
Self-inversea ⊕ a = 0
Commutativea ⊕ b = b ⊕ a
Associative(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)

When we XOR all elements, pairs cancel out:

text

2 ⊕ 3 ⊕ 5 ⊕ 4 ⊕ 5 ⊕ 3 ⊕ 4 = 2 ⊕ (3 ⊕ 3) ⊕ (5 ⊕ 5) ⊕ (4 ⊕ 4) = 2 ⊕ 0 ⊕ 0 ⊕ 0 = 2

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive frequency countO(n²)O(1)
Hash mapO(n)O(n)
XORO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Count occurrences of each element using nested loops.

c

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

Expected output:

text

Unique element: 2

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

Approach 2: Hash Map

Use a hash table to count frequencies, then find the element with count 1.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 2048 typedef struct { int *keys; int *counts; int *occupied; int capacity; } HashMap; HashMap *createHashMap(int capacity) { HashMap *map = malloc(sizeof(HashMap)); if (map == NULL) return NULL; map->keys = malloc(capacity * sizeof(int)); map->counts = calloc(capacity, sizeof(int)); map->occupied = calloc(capacity, sizeof(int)); if (map->keys == NULL || map->counts == NULL || map->occupied == NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); return NULL; } map->capacity = capacity; return map; } void freeHashMap(HashMap *map) { if (map != NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); } } int hashFunc(int key, int capacity) { return ((key % capacity) + capacity) % capacity; } void increment(HashMap *map, int key) { int idx = hashFunc(key, map->capacity); int start = idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]++; return; } idx = (idx + 1) % map->capacity; if (idx == start) return; } map->keys[idx] = key; map->counts[idx] = 1; map->occupied[idx] = 1; } int getCount(HashMap *map, int key) { int idx = hashFunc(key, map->capacity); int start = idx; while (map->occupied[idx]) { if (map->keys[idx] == key) return map->counts[idx]; idx = (idx + 1) % map->capacity; if (idx == start) break; } return 0; } int findUniqueHash(const int arr[], int n) { if (n <= 0) return -1; HashMap *map = createHashMap(TABLE_SIZE); if (map == NULL) return -1; for (int i = 0; i < n; i++) { increment(map, arr[i]); } int result = -1; for (int i = 0; i < n; i++) { if (getCount(map, arr[i]) == 1) { result = arr[i]; break; } } freeHashMap(map); return result; } int main(void) { int arr[] = {2, 3, 5, 4, 5, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Unique element: %d\n", findUniqueHash(arr, n)); return 0; }

Expected output:

text

Unique element: 2

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

Approach 3: XOR (Optimal)

XOR all elements; pairs cancel, leaving the unique element.

c

#include <stdio.h> int findUniqueXOR(const int arr[], int n) { int result = 0; for (int i = 0; i < n; i++) { result ^= arr[i]; } return result; } int main(void) { int arr[] = {2, 3, 5, 4, 5, 3, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Unique element: %d\n", findUniqueXOR(arr, n)); return 0; }

Expected output:

text

Unique element: 2

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

Step-by-Step XOR Trace

text

arr = [2, 3, 5, 4, 5, 3, 4] result = 0 result ^= 2 → 2 result ^= 3 → 1 result ^= 5 → 4 result ^= 4 → 0 result ^= 5 → 5 result ^= 3 → 6 result ^= 4 → 2 Final: 2 ✓

Full Practical Code Example with Explanation

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

Program: Unique Element Finder

c

/** * unique_number.c * Finds the unique element where all others appear twice. * Compares naive, hash map, 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 */ int findUniqueNaive(const int arr[], int n); int findUniqueHash(const int arr[], int n); int findUniqueXOR(const int arr[], int n); 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("=== Unique 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 (odd, 1-99999): ", &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 xorResult = findUniqueXOR(arr, n); printf("Unique element (XOR): %d\n", xorResult); 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 findUniqueNaive(const int arr[], int n) { for (int i = 0; i < n; i++) { int count = 0; for (int j = 0; j < n; j++) { if (arr[i] == arr[j]) count++; } if (count == 1) return arr[i]; } return -1; } /* Hash map: O(n) average */ typedef struct { int *keys; int *counts; int *occupied; int capacity; } HashMap; HashMap *createHashMap(int capacity) { HashMap *map = malloc(sizeof(HashMap)); if (map == NULL) return NULL; map->keys = malloc(capacity * sizeof(int)); map->counts = calloc(capacity, sizeof(int)); map->occupied = calloc(capacity, sizeof(int)); if (map->keys == NULL || map->counts == NULL || map->occupied == NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); return NULL; } map->capacity = capacity; return map; } void freeHashMap(HashMap *map) { if (map != NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); } } int hashFunc(int key, int capacity) { return ((key % capacity) + capacity) % capacity; } void increment(HashMap *map, int key) { int idx = hashFunc(key, map->capacity); int start = idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]++; return; } idx = (idx + 1) % map->capacity; if (idx == start) return; } map->keys[idx] = key; map->counts[idx] = 1; map->occupied[idx] = 1; } int getCount(HashMap *map, int key) { int idx = hashFunc(key, map->capacity); int start = idx; while (map->occupied[idx]) { if (map->keys[idx] == key) return map->counts[idx]; idx = (idx + 1) % map->capacity; if (idx == start) break; } return 0; } int findUniqueHash(const int arr[], int n) { if (n <= 0) return -1; HashMap *map = createHashMap(n * 2 + 1); if (map == NULL) return -1; for (int i = 0; i < n; i++) { increment(map, arr[i]); } int result = -1; for (int i = 0; i < n; i++) { if (getCount(map, arr[i]) == 1) { result = arr[i]; break; } } freeHashMap(map); return result; } /* XOR: O(n) time, O(1) space */ int findUniqueXOR(const int arr[], int n) { int result = 0; for (int i = 0; i < n; i++) { result ^= arr[i]; } return result; } /* 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[] = {2, 3, 5, 4, 5, 3, 4}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %d\n", findUniqueNaive(arr1, n1)); printf("Hash: %d\n", findUniqueHash(arr1, n1)); printf("XOR: %d\n", findUniqueXOR(arr1, n1)); int arr2[] = {2, 2, 5, 5, 20, 30, 30}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Naive: %d\n", findUniqueNaive(arr2, n2)); printf("Hash: %d\n", findUniqueHash(arr2, n2)); printf("XOR: %d\n", findUniqueXOR(arr2, n2)); int arr3[] = {7}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3 (single): "); printArray(arr3, n3); printf("XOR: %d\n", findUniqueXOR(arr3, n3)); int arr4[] = {-1, -2, -1, -3, -2}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4 (negatives): "); printArray(arr4, n4); printf("XOR: %d\n", findUniqueXOR(arr4, n4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100001) ===\n"); int n = 100001; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Fill pairs + one unique */ for (int i = 0; i < n / 2; i++) { arr[2 * i] = i + 1; arr[2 * i + 1] = i + 1; } arr[n - 1] = 999999; /* Unique element */ clock_t start, end; double timeTaken; /* Hash */ start = clock(); int hashResult = findUniqueHash(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (O(n)): Result = %d, Time = %.3f ms\n", hashResult, timeTaken); /* XOR */ start = clock(); int xorResult = findUniqueXOR(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("XOR (O(n)): Result = %d, Time = %.3f ms\n", xorResult, timeTaken); printf("\nVerification: %s\n", (hashResult == xorResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single element */ int arr1[] = {42}; printf("Case 1: Single element\n"); printf(" Array: "); printArray(arr1, 1); printf(" Unique: %d\n", findUniqueXOR(arr1, 1)); /* Case 2: Unique at start */ int arr2[] = {99, 1, 1, 2, 2}; printf("\nCase 2: Unique at start\n"); printf(" Array: "); printArray(arr2, 5); printf(" Unique: %d\n", findUniqueXOR(arr2, 5)); /* Case 3: Unique at end */ int arr3[] = {1, 1, 2, 2, 99}; printf("\nCase 3: Unique at end\n"); printf(" Array: "); printArray(arr3, 5); printf(" Unique: %d\n", findUniqueXOR(arr3, 5)); /* Case 4: Negative unique */ int arr4[] = {-5, -5, -10, -3, -3}; printf("\nCase 4: Negative unique\n"); printf(" Array: "); printArray(arr4, 5); printf(" Unique: %d\n", findUniqueXOR(arr4, 5)); /* Case 5: Zero unique */ int arr5[] = {1, 1, 0, 2, 2}; printf("\nCase 5: Zero unique\n"); printf(" Array: "); printArray(arr5, 5); printf(" Unique: %d\n", findUniqueXOR(arr5, 5)); /* Case 6: Large unique value */ int arr6[] = {1, 1, 2147483647, 2, 2}; printf("\nCase 6: Large unique value\n"); printf(" Array: "); printArray(arr6, 5); printf(" Unique: %d\n", findUniqueXOR(arr6, 5)); /* Case 7: All pairs, no unique (invalid input) */ int arr7[] = {1, 1, 2, 2}; printf("\nCase 7: Invalid input (all pairs)\n"); printf(" Array: "); printArray(arr7, 4); printf(" XOR result: %d (no unique exists)\n", findUniqueXOR(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: Counts occurrences with nested loops; O(n²).
  2. Hash: Uses a hash table to count frequencies; O(n) average.
  3. XOR: XORs all elements; pairs cancel leaving the unique; O(n), O(1) space.

Expected Output (sample run)

text

=== Unique 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: [2, 3, 5, 4, 5, 3, 4] Naive: 2 Hash: 2 XOR: 2 Array 2: [2, 2, 5, 5, 20, 30, 30] Naive: 20 Hash: 20 XOR: 20 Array 3 (single): [7] XOR: 7 Array 4 (negatives): [-1, -2, -1, -3, -2] XOR: -3 --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 100001) === Hash (O(n)): Result = 999999, Time = 18.456 ms XOR (O(n)): Result = 999999, Time = 0.312 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [42] Unique: 42 Case 2: Unique at start Array: [99, 1, 1, 2, 2] Unique: 99 Case 3: Unique at end Array: [1, 1, 2, 2, 99] Unique: 99 Case 4: Negative unique Array: [-5, -5, -10, -3, -3] Unique: -10 Case 5: Zero unique Array: [1, 1, 0, 2, 2] Unique: 0 Case 6: Large unique value Array: [1, 1, 2147483647, 2, 2] Unique: 2147483647 Case 7: Invalid input (all pairs) Array: [1, 1, 2, 2] XOR result: 0 (no unique exists)

Common Pitfalls, Edge Cases & Memory Considerations

1. Assumption of Exactly One Unique

XOR returns 0 if all elements are paired (no unique). The problem guarantees exactly one unique.

2. Empty Array

XOR returns 0 for empty arrays, which may be misinterpreted as a valid unique value.

c

if (n <= 0) return -1; /* Handle explicitly */

3. Integer Overflow

XOR does not overflow; it operates bit-by-bit.

4. Negative Values

XOR works correctly with negative numbers (two’s complement).

c

int arr[] = {-1, -2, -1, -3, -2}; /* XOR = -3 */

5. Hash Table Sizing

Hash table should be sized ≥ 2× array size for good performance.

c

createHashMap(n * 2 + 1);

6. Hash Table Overflow

If the table fills up, insertions silently fail.

Solution: Size the table generously (2× or more).

7. Using unordered_map in C++

C does not have built-in hash maps; you must implement or use uthash.

Solution: Use XOR when possible (simplest and fastest).

8. Memory Leaks

Free hash table memory after use.

c

freeHashMap(map);

9. Large Unique Value

Unique value can be any int; use int not unsigned.

10. Memory Considerations

  • Naive: O(1) extra space.
  • Hash: O(n) extra space.
  • XOR: O(1) extra space — preferred.
  • XOR is the most efficient and simplest approach.

Summary & Best Practices

Finding the unique element where all others appear twice is elegantly solved using XOR. The XOR of all elements cancels pairs, leaving only the unique element.

Key Takeaways

  • XOR cancels identical pairs: a ⊕ a = 0.
  • XOR identity: a ⊕ 0 = a.
  • XOR all elements → unique element.
  • Naive: O(n²) time, O(1) space.
  • Hash: O(n) time, O(n) space.
  • XOR: O(n) time, O(1) space — optimal.
  • Problem guarantees exactly one unique element.

Best Practices

  1. Use XOR for O(n) time and O(1) space.
  2. Verify the problem’s guarantee (exactly one unique).
  3. Handle empty arrays explicitly.
  4. Test with negative values and zeros.
  5. Use hash map only when XOR isn’t applicable (e.g., unique appears 3 times).
  6. Free hash map memory after use.
  7. Size hash tables generously for good performance.
  8. Use int (signed) for the result.
  9. Document the XOR property in comments.
  10. Profile for large arrays.

By mastering this problem, you’ll understand the power of XOR and be prepared for related bitwise problems like Single Number II and III.

Learn to maximize stock profit with multiple transactions in C: naive, local min/max, and O(n) accumulation approaches. Master greedy algorithms.

Stock Buy and Sell – Multiple Transactions in C

Stock Buy and Sell – Multiple Transactions Allowed in C: A Comprehensive Tutorial

The Stock Buy and Sell problem with multiple transactions allowed is a classic algorithmic problem that tests your understanding of greedy algorithms, array traversal, and profit accumulation. Given an array of stock prices, the goal is to find the maximum total profit by buying and selling the stock any number of times.

This tutorial provides an in-depth exploration of this problem: naive recursive approach, local minima/maxima approach, profit accumulation approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array prices[] representing daily stock prices, find the maximum total profit that can be earned by buying and selling the stock any number of times.

Constraints:

  • We can only sell a stock we have bought earlier.
  • We cannot hold multiple stocks simultaneously.
  • We can perform as many transactions as we want.

Example:

text

Input: prices = [100, 180, 260, 310, 40, 535, 695] Output: 865 Explanation: Buy at 100, sell at 310 → Profit = 210 Buy at 40, sell at 695 → Profit = 655 Total = 865

Why This Problem Matters

  • Greedy algorithm design: Demonstrates optimal substructure.
  • Profit accumulation: Foundation for financial algorithms.
  • Array traversal: Practical application of scanning patterns.
  • Interview preparation: Very common coding interview question.
  • Real-world applications: Trading strategies, resource allocation.

Key Insight

Profit is earned whenever the price rises from one day to the next. We can capture every upward movement by accumulating the differences between adjacent days where prices increase.

Formula:

text

Total Profit = Σ max(0, prices[i] - prices[i-1]) for i = 1 to n-1

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive recursionO(2^n)O(n)
Local minima/maximaO(n)O(1)
Profit accumulationO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive Recursive (Try All Possibilities)

Try every pair of buy/sell days recursively.

c

#include <stdio.h> #include <limits.h> int maxProfitRec(const int prices[], int start, int end) { if (start >= end) return 0; int res = 0; for (int i = start; i < end; i++) { for (int j = i + 1; j <= end; j++) { if (prices[j] > prices[i]) { int curr = (prices[j] - prices[i]) + maxProfitRec(prices, start, i - 1) + maxProfitRec(prices, j + 1, end); if (curr > res) res = curr; } } } return res; } int maxProfitNaive(const int prices[], int n) { return maxProfitRec(prices, 0, n - 1); } int main(void) { int prices[] = {100, 180, 260, 310, 40, 535, 695}; int n = sizeof(prices) / sizeof(prices[0]); printf("Maximum profit: %d\n", maxProfitNaive(prices, n)); return 0; }

Expected output:

text

Maximum profit: 865

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

Approach 2: Local Minima and Maxima

Find local minima (buy points) and local maxima (sell points).

c

#include <stdio.h> int maxProfitLocalMinMax(const int prices[], int n) { if (n <= 1) return 0; int res = 0; int i = 0; while (i < n - 1) { /* Find local minima */ while (i < n - 1 && prices[i] >= prices[i + 1]) { i++; } int lMin = prices[i]; /* Find local maxima */ while (i < n - 1 && prices[i] <= prices[i + 1]) { i++; } int lMax = prices[i]; res += (lMax - lMin); } return res; } int main(void) { int prices[] = {100, 180, 260, 310, 40, 535, 695}; int n = sizeof(prices) / sizeof(prices[0]); printf("Maximum profit: %d\n", maxProfitLocalMinMax(prices, n)); return 0; }

Expected output:

text

Maximum profit: 865

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

Approach 3: Profit Accumulation (Simplest and Optimal)

Add every positive difference between adjacent days.

c

#include <stdio.h> int maxProfit(const int prices[], int n) { if (n <= 1) return 0; int res = 0; for (int i = 1; i < n; i++) { if (prices[i] > prices[i - 1]) { res += prices[i] - prices[i - 1]; } } return res; } int main(void) { int prices[] = {100, 180, 260, 310, 40, 535, 695}; int n = sizeof(prices) / sizeof(prices[0]); printf("Maximum profit: %d\n", maxProfit(prices, n)); return 0; }

Expected output:

text

Maximum profit: 865

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

Why Accumulation Works

Buying at a valley and selling at a peak is equivalent to capturing every small upward move between them.

text

Prices: [100, 180, 260, 310] Buy at 100, sell at 310 = 210 Accumulate: (180-100) + (260-180) + (310-260) = 80 + 80 + 50 = 210 ✓

Full Practical Code Example with Explanation

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

Program: Stock Profit Maximizer

c

/** * stock_profit.c * Finds maximum profit with multiple stock transactions. * Compares naive, local min/max, and accumulation 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 maxProfitNaive(const int prices[], int n); int maxProfitRec(const int prices[], int start, int end); int maxProfitLocalMinMax(const int prices[], int n); int maxProfitAccumulate(const int prices[], int n); int readInteger(const char *prompt, int *outValue); void printArray(const int prices[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Stock Buy and Sell (Multiple Transactions) ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Prices\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 prices[MAX_SIZE]; int n; if (readInteger("Enter number of days (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 prices:\n", n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Day %d price: ", i); if (readInteger(prompt, &prices[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nPrices: "); printArray(prices, n); int result = maxProfitAccumulate(prices, n); printf("Maximum profit: %d\n", result); 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 recursive helper */ int maxProfitRec(const int prices[], int start, int end) { if (start >= end) return 0; int res = 0; for (int i = start; i < end; i++) { for (int j = i + 1; j <= end; j++) { if (prices[j] > prices[i]) { int curr = (prices[j] - prices[i]) + maxProfitRec(prices, start, i - 1) + maxProfitRec(prices, j + 1, end); if (curr > res) res = curr; } } } return res; } /* Naive approach */ int maxProfitNaive(const int prices[], int n) { if (n <= 1) return 0; return maxProfitRec(prices, 0, n - 1); } /* Local minima/maxima approach */ int maxProfitLocalMinMax(const int prices[], int n) { if (n <= 1) return 0; int res = 0; int i = 0; while (i < n - 1) { while (i < n - 1 && prices[i] >= prices[i + 1]) i++; int lMin = prices[i]; while (i < n - 1 && prices[i] <= prices[i + 1]) i++; int lMax = prices[i]; res += (lMax - lMin); } return res; } /* Accumulation approach */ int maxProfitAccumulate(const int prices[], int n) { if (n <= 1) return 0; int res = 0; for (int i = 1; i < n; i++) { if (prices[i] > prices[i - 1]) { res += prices[i] - prices[i - 1]; } } return res; } /* Prints array */ void printArray(const int prices[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", prices[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int prices1[] = {100, 180, 260, 310, 40, 535, 695}; int n1 = sizeof(prices1) / sizeof(prices1[0]); printf("\nPrices 1: "); printArray(prices1, n1); printf("Naive: %d\n", maxProfitNaive(prices1, n1)); printf("MinMax: %d\n", maxProfitLocalMinMax(prices1, n1)); printf("Accumulate: %d\n", maxProfitAccumulate(prices1, n1)); int prices2[] = {4, 2}; int n2 = sizeof(prices2) / sizeof(prices2[0]); printf("\nPrices 2: "); printArray(prices2, n2); printf("Naive: %d\n", maxProfitNaive(prices2, n2)); printf("MinMax: %d\n", maxProfitLocalMinMax(prices2, n2)); printf("Accumulate: %d\n", maxProfitAccumulate(prices2, n2)); int prices3[] = {1, 2, 3, 4, 5}; int n3 = sizeof(prices3) / sizeof(prices3[0]); printf("\nPrices 3 (increasing): "); printArray(prices3, n3); printf("Naive: %d\n", maxProfitNaive(prices3, n3)); printf("MinMax: %d\n", maxProfitLocalMinMax(prices3, n3)); printf("Accumulate: %d\n", maxProfitAccumulate(prices3, n3)); int prices4[] = {5, 4, 3, 2, 1}; int n4 = sizeof(prices4) / sizeof(prices4[0]); printf("\nPrices 4 (decreasing): "); printArray(prices4, n4); printf("Naive: %d\n", maxProfitNaive(prices4, n4)); printf("MinMax: %d\n", maxProfitLocalMinMax(prices4, n4)); printf("Accumulate: %d\n", maxProfitAccumulate(prices4, n4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison ===\n"); /* Naive is only feasible for small arrays */ int smallSize = 15; int *smallArr = malloc(smallSize * sizeof(int)); if (smallArr == NULL) return; srand(42); for (int i = 0; i < smallSize; i++) { smallArr[i] = rand() % 100 + 1; } clock_t start, end; double timeTaken; start = clock(); int naiveResult = maxProfitNaive(smallArr, smallSize); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d, O(2^n)): Result = %d, Time = %.3f ms\n", smallSize, naiveResult, timeTaken); free(smallArr); /* Larger arrays for efficient approaches */ int largeSize = 100000; int *largeArr = malloc(largeSize * sizeof(int)); if (largeArr == NULL) return; for (int i = 0; i < largeSize; i++) { largeArr[i] = rand() % 1000 + 1; } start = clock(); int minMaxResult = maxProfitLocalMinMax(largeArr, largeSize); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("MinMax (n=%d, O(n)): Result = %d, Time = %.3f ms\n", largeSize, minMaxResult, timeTaken); start = clock(); int accResult = maxProfitAccumulate(largeArr, largeSize); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Accumulate (n=%d, O(n)): Result = %d, Time = %.3f ms\n", largeSize, accResult, timeTaken); printf("\nVerification (MinMax vs Accumulate): %s\n", (minMaxResult == accResult) ? "PASS" : "FAIL"); free(largeArr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Empty array */ int prices1[] = {}; printf("Case 1: Empty array\n"); printf(" Profit: %d\n", maxProfitAccumulate(prices1, 0)); /* Case 2: Single day */ int prices2[] = {42}; printf("\nCase 2: Single day\n"); printf(" Prices: "); printArray(prices2, 1); printf(" Profit: %d\n", maxProfitAccumulate(prices2, 1)); /* Case 3: Two days, profit possible */ int prices3[] = {5, 10}; printf("\nCase 3: Two days, profit possible\n"); printf(" Prices: "); printArray(prices3, 2); printf(" Profit: %d\n", maxProfitAccumulate(prices3, 2)); /* Case 4: Two days, no profit */ int prices4[] = {10, 5}; printf("\nCase 4: Two days, no profit\n"); printf(" Prices: "); printArray(prices4, 2); printf(" Profit: %d\n", maxProfitAccumulate(prices4, 2)); /* Case 5: All same prices */ int prices5[] = {7, 7, 7, 7}; printf("\nCase 5: All same prices\n"); printf(" Prices: "); printArray(prices5, 4); printf(" Profit: %d\n", maxProfitAccumulate(prices5, 4)); /* Case 6: Zigzag prices */ int prices6[] = {10, 5, 15, 8, 20}; printf("\nCase 6: Zigzag prices\n"); printf(" Prices: "); printArray(prices6, 5); printf(" Profit: %d\n", maxProfitAccumulate(prices6, 5)); /* Case 7: Large price differences */ int prices7[] = {1, 10000, 1, 10000}; printf("\nCase 7: Large price differences\n"); printf(" Prices: "); printArray(prices7, 4); printf(" Profit: %d (watch for overflow)\n", maxProfitAccumulate(prices7, 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: Tries all buy/sell pairs recursively; O(2^n).
  2. Local Min/Max: Finds valleys and peaks; O(n).
  3. Accumulation: Adds all positive adjacent differences; O(n) — simplest and optimal.

Expected Output (sample run)

text

=== Stock Buy and Sell (Multiple Transactions) === --- Menu --- 1. Demonstrate All Approaches 2. Custom Prices 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Prices 1: [100, 180, 260, 310, 40, 535, 695] Naive: 865 MinMax: 865 Accumulate: 865 Prices 2: [4, 2] Naive: 0 MinMax: 0 Accumulate: 0 Prices 3 (increasing): [1, 2, 3, 4, 5] Naive: 4 MinMax: 4 Accumulate: 4 Prices 4 (decreasing): [5, 4, 3, 2, 1] Naive: 0 MinMax: 0 Accumulate: 0 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Profit: 0 Case 2: Single day Prices: [42] Profit: 0 Case 3: Two days, profit possible Prices: [5, 10] Profit: 5 Case 4: Two days, no profit Prices: [10, 5] Profit: 0 Case 5: All same prices Prices: [7, 7, 7, 7] Profit: 0 Case 6: Zigzag prices Prices: [10, 5, 15, 8, 20] Profit: 22 Case 7: Large price differences Prices: [1, 10000, 1, 10000] Profit: 19998 (watch for overflow)

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow

Total profit can exceed int for large arrays with big price differences.

c

long long profit = 0; /* Use long long for safety */

2. Empty Array

Return 0 for empty or single-element arrays.

c

if (n <= 1) return 0;

3. Decreasing Prices

No profit possible; return 0.

c

int prices[] = {5, 4, 3, 2, 1}; /* Profit = 0 */

4. All Same Prices

No profit; return 0.

c

int prices[] = {7, 7, 7, 7}; /* Profit = 0 */

5. Holding Multiple Stocks

Problem prohibits holding multiple stocks; accumulation approach respects this.

6. Selling Before Buying

The accumulation approach naturally only counts upward moves, respecting buy-before-sell.

7. Using Naive for Large Arrays

Naive O(2^n) is only feasible for n ≤ 20.

Solution: Use O(n) approaches.

8. Off-by-One in Loops

The accumulation loop starts at i = 1 (comparing with i-1).

c

for (int i = 1; i < n; i++) { ... } /* Correct */

9. Local Min/Max Edge Cases

Ensure the while loops terminate correctly at array boundaries.

10. Memory Considerations

  • Naive: O(n) stack space due to recursion.
  • MinMax: O(1) extra space.
  • Accumulate: O(1) extra space — preferred.
  • Use long long for profit on large inputs.

Summary & Best Practices

The Stock Buy and Sell problem with multiple transactions is elegantly solved by accumulating all positive adjacent price differences. This captures every profitable opportunity in O(n) time and O(1) space.

Key Takeaways

  • Profit comes from upward price movements.
  • Accumulate all positive prices[i] - prices[i-1].
  • Naive: O(2^n) time.
  • MinMax: O(n) time.
  • Accumulate: O(n) time, O(1) space — simplest and optimal.
  • Use long long for large profits.

Best Practices

  1. Use the accumulation approach for O(n) time and O(1) space.
  2. Use long long for profit to prevent overflow.
  3. Handle edge cases: empty, single element, decreasing, all same.
  4. Verify with local min/max during testing.
  5. Avoid naive recursion for large arrays.
  6. Document the greedy insight in comments.
  7. Validate input (array size, values).
  8. Test with zigzag patterns and large differences.
  9. Use const for input arrays.
  10. Profile for large arrays.

By mastering this problem, you’ll understand greedy accumulation and be prepared for related financial and optimization problems.

Learn to compute sum of all subarrays in C using naive O(n²) and optimal O(n) element contribution approaches. Master array algorithms.

Sum of All Subarrays in C: O(n) Contribution Method

Sum of All Subarrays in C: A Comprehensive Tutorial

Computing the sum of all possible subarrays of an array is a classic algorithmic problem. A subarray is a contiguous part of the array. This problem tests your understanding of array traversal, optimization techniques, and the element contribution method. Understanding this problem is essential for solving range-sum queries, prefix sum computations, and many dynamic programming problems.

This tutorial provides an in-depth exploration of computing the sum of all subarrays in C: naive nested-loop approach, optimized element contribution method, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an integer array arr[], compute the sum of all possible subarrays.

Example:

text

Input: arr = [1, 4, 5, 3, 2] Output: 116

Understanding Subarrays:

text

[1], [1,4], [1,4,5], [1,4,5,3], [1,4,5,3,2] [4], [4,5], [4,5,3], [4,5,3,2] [5], [5,3], [5,3,2] [3], [3,2] [2]

Total = 116.

Why This Problem Matters

  • Prefix sums: Foundation for range query problems.
  • Element contribution: Powerful technique for many array problems.
  • Time-space trade-off: O(n²) to O(n) transformation.
  • Interview preparation: Common coding interview question.
  • Algorithm design: Teaches recognizing patterns and mathematical simplification.

Key Insight: Element Contribution

Instead of enumerating all subarrays, count how many subarrays include each element. For element at index i in an array of size n:

text

Number of subarrays containing arr[i] = (i + 1) × (n - i)
  • (i + 1) choices for the starting index (0 to i)
  • (n - i) choices for the ending index (i to n-1)

Total Sum = Σ arr[i] × (i + 1) × (n – i)

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive nested loopsO(n²)O(1)
Element contributionO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Iterate over all starting indices, extend to all ending indices, and accumulate sums.

c

#include <stdio.h> #include <limits.h> long long subarraySumNaive(const int arr[], int n) { if (n <= 0) return 0; long long result = 0; for (int i = 0; i < n; i++) { long long temp = 0; for (int j = i; j < n; j++) { temp += arr[j]; result += temp; } } return result; } int main(void) { int arr[] = {1, 4, 5, 3, 2}; int n = sizeof(arr) / sizeof(arr[0]); printf("Sum of all subarrays: %lld\n", subarraySumNaive(arr, n)); return 0; }

Expected output:

text

Sum of all subarrays: 116

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

Approach 2: Element Contribution Method

Use the formula arr[i] × (i + 1) × (n - i) for each element.

c

#include <stdio.h> long long subarraySumContribution(const int arr[], int n) { if (n <= 0) return 0; long long result = 0; for (int i = 0; i < n; i++) { result += (long long)arr[i] * (i + 1) * (n - i); } return result; } int main(void) { int arr[] = {1, 4, 5, 3, 2}; int n = sizeof(arr) / sizeof(arr[0]); printf("Sum of all subarrays: %lld\n", subarraySumContribution(arr, n)); return 0; }

Expected output:

text

Sum of all subarrays: 116

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

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

text

i=0: 1 × 1 × 5 = 5 i=1: 4 × 2 × 4 = 32 i=2: 5 × 3 × 3 = 45 i=3: 3 × 4 × 2 = 24 i=4: 2 × 5 × 1 = 10 Total = 5 + 32 + 45 + 24 + 10 = 116

Verification Against Naive

For [1, 2, 3]:

text

Element contribution: i=0: 1 × 1 × 3 = 3 i=1: 2 × 2 × 2 = 8 i=2: 3 × 3 × 1 = 9 Total = 20 Naive verification: [1]=1, [1,2]=3, [1,2,3]=6, [2]=2, [2,3]=5, [3]=3 Total = 1+3+6+2+5+3 = 20 ✓

Full Practical Code Example with Explanation

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

Program: Sum of All Subarrays Comparison

c

/** * sum_of_all_subarrays.c * Computes the sum of all subarrays. * Compares naive and element contribution 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 */ long long subarraySumNaive(const int arr[], int n); long long subarraySumContribution(const int arr[], int n); 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("=== Sum of All Subarrays Demo ===\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; 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); long long naive = subarraySumNaive(arr, n); long long contrib = subarraySumContribution(arr, n); printf("Naive result: %lld\n", naive); printf("Contribution result: %lld\n", contrib); printf("Verification: %s\n", (naive == contrib) ? "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; } /* Naive approach: O(n^2) */ long long subarraySumNaive(const int arr[], int n) { if (n <= 0) return 0; long long result = 0; for (int i = 0; i < n; i++) { long long temp = 0; for (int j = i; j < n; j++) { temp += arr[j]; result += temp; } } return result; } /* Element contribution: O(n) */ long long subarraySumContribution(const int arr[], int n) { if (n <= 0) return 0; long long result = 0; for (int i = 0; i < n; i++) { result += (long long)arr[i] * (i + 1) * (n - i); } return result; } /* 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, 4, 5, 3, 2}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %lld\n", subarraySumNaive(arr1, n1)); printf("Contribution: %lld\n", subarraySumContribution(arr1, n1)); int arr2[] = {1, 2, 3, 4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Naive: %lld\n", subarraySumNaive(arr2, n2)); printf("Contribution: %lld\n", subarraySumContribution(arr2, n2)); int arr3[] = {1, 2, 3}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("Naive: %lld\n", subarraySumNaive(arr3, n3)); printf("Contribution: %lld\n", subarraySumContribution(arr3, n3)); int arr4[] = {5}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4 (single): "); printArray(arr4, n4); printf("Naive: %lld\n", subarraySumNaive(arr4, n4)); printf("Contribution: %lld\n", subarraySumContribution(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; } for (int i = 0; i < n; i++) { arr[i] = (i % 100) + 1; } clock_t start, end; double timeTaken; /* Naive approach */ start = clock(); long long naiveResult = subarraySumNaive(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n^2)): Result = %lld, Time = %.3f ms\n", naiveResult, timeTaken); /* Contribution approach */ start = clock(); long long contribResult = subarraySumContribution(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Contribution (O(n)): Result = %lld, Time = %.3f ms\n", contribResult, timeTaken); printf("\nVerification: %s\n", (naiveResult == contribResult) ? "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(" Sum: %lld\n", subarraySumContribution(arr1, 0)); /* Case 2: Single element */ int arr2[] = {42}; printf("\nCase 2: Single element\n"); printf(" Array: "); printArray(arr2, 1); printf(" Sum: %lld\n", subarraySumContribution(arr2, 1)); /* Case 3: Two elements */ int arr3[] = {3, 7}; printf("\nCase 3: Two elements\n"); printf(" Array: "); printArray(arr3, 2); printf(" Sum: %lld\n", subarraySumContribution(arr3, 2)); /* Case 4: All same elements */ int arr4[] = {5, 5, 5, 5}; printf("\nCase 4: All same elements\n"); printf(" Array: "); printArray(arr4, 4); printf(" Sum: %lld\n", subarraySumContribution(arr4, 4)); /* Case 5: Negative numbers */ int arr5[] = {1, -2, 3, -4, 5}; printf("\nCase 5: Negative numbers\n"); printf(" Array: "); printArray(arr5, 5); printf(" Sum: %lld\n", subarraySumContribution(arr5, 5)); /* Case 6: All negatives */ int arr6[] = {-1, -2, -3}; printf("\nCase 6: All negatives\n"); printf(" Array: "); printArray(arr6, 3); printf(" Sum: %lld\n", subarraySumContribution(arr6, 3)); /* Case 7: Large values (overflow check) */ int arr7[] = {100000, 200000, 300000}; printf("\nCase 7: Large values\n"); printf(" Array: "); printArray(arr7, 3); printf(" Sum: %lld (long long)\n", subarraySumContribution(arr7, 3)); } /* 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: Uses nested loops to enumerate all subarrays; O(n²).
  2. Contribution: Uses formula arr[i] × (i+1) × (n-i); O(n).
  3. Overflow protection: Uses long long for the sum.

Expected Output (sample run)

text

=== Sum of All Subarrays Demo === --- 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, 4, 5, 3, 2] Naive: 116 Contribution: 116 Array 2: [1, 2, 3, 4] Naive: 50 Contribution: 50 Array 3: [1, 2, 3] Naive: 20 Contribution: 20 Array 4 (single): [5] Naive: 5 Contribution: 5 --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 20000) === Naive (O(n^2)): Result = 1308533334000, Time = 812.456 ms Contribution (O(n)): Result = 1308533334000, Time = 0.089 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Sum: 0 Case 2: Single element Array: [42] Sum: 42 Case 3: Two elements Array: [3, 7] Sum: 20 Case 4: All same elements Array: [5, 5, 5, 5] Sum: 100 Case 5: Negative numbers Array: [1, -2, 3, -4, 5] Sum: 15 Case 6: All negatives Array: [-1, -2, -3] Sum: -20 Case 7: Large values Array: [100000, 200000, 300000] Sum: 1400000000 (long long)

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow

The sum of all subarrays grows as O(n³) × max_value, easily exceeding int.

c

int result = 0; /* BUG: overflows quickly */ long long result = 0; /* Correct */

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

2. Casting Before Multiplication

Without casting, arr[i] * (i+1) * (n-i) can overflow even if the result fits in long long.

c

result += (long long)arr[i] * (i + 1) * (n - i); /* Correct */

3. Empty Array

Return 0 for empty arrays.

c

if (n <= 0) return 0;

4. Single Element

The only subarray is the element itself.

c

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

5. Negative Numbers

Negative values work correctly; results can be negative.

c

int arr[] = {-1, -2, -3}; /* Sum = -20 */

6. Off-by-One in Formula

The formula is (i + 1) × (n - i) for 0-based index i.

c

result += arr[i] * (i + 1) * (n - i); /* Correct */

7. Assuming Subarrays are Non-Empty

By definition, subarrays are non-empty contiguous segments.

8. Very Large Arrays

For n = 10⁶, even long long may overflow if values are large.

Solution: Use modular arithmetic or __int128 (GCC/Clang extension).

9. Performance of Naive

Naive O(n²) is unusable for n > 10000.

Solution: Use the O(n) contribution approach.

10. Memory Considerations

  • Both approaches use O(1) extra space.
  • No dynamic allocation required.
  • Use long long for results.
  • Contribution approach is preferred for large arrays.

Summary & Best Practices

Computing the sum of all subarrays is elegantly solved using the element contribution method in O(n) time. Each element arr[i] contributes arr[i] × (i + 1) × (n - i) to the total.

Key Takeaways

  • Subarray = contiguous part of an array.
  • Number of subarrays = n × (n + 1) / 2.
  • Element arr[i] appears in (i + 1) × (n - i) subarrays.
  • Naive approach: O(n²) time.
  • Contribution approach: O(n) time, O(1) space.
  • Use long long to prevent overflow.

Best Practices

  1. Use the contribution approach for O(n) time.
  2. Use long long for results and intermediate values.
  3. Cast before multiplication to prevent overflow.
  4. Handle empty arrays and single elements.
  5. Test with negative values and zeros.
  6. Verify with naive approach during testing.
  7. Avoid naive O(n²) for large arrays.
  8. Document the formula in comments.
  9. Consider modular arithmetic for very large sums.
  10. Profile performance for large arrays.

By mastering this problem, you’ll understand the element contribution technique, which is widely applicable to range-sum and prefix-sum problems.