CoreTechED

Loading

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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *