CoreTechED

Loading

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 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.