CoreTechED

Loading

Learn to find the missing number in an array in C: naive, hash, sum formula, and XOR approaches. Master O(n) time with O(1) space.

Find the Missing Number in C: Sum & XOR Methods

Find the Missing Number in C: A Comprehensive Tutorial

Finding the missing number in an array of distinct integers from 1 to n is a classic algorithmic problem. Given an array of size n-1 containing distinct integers from the range [1, n], exactly one number is missing. This problem tests your understanding of arithmetic sums, bitwise operations, and time-space trade-offs.

This tutorial provides an in-depth exploration of finding the missing number in C: naive linear search, hash-based approach, sum-of-n-terms formula, XOR operation, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n - 1 containing distinct integers from 1 to n (inclusive), find the missing element.

Examples:

text

Input: arr = [8, 2, 4, 5, 3, 7, 1] Output: 6 Explanation: n = 8, numbers 1 to 8 present except 6. Input: arr = [1, 2, 3, 5] Output: 4 Explanation: n = 5, numbers 1 to 5 present except 4.

Why This Problem Matters

  • Arithmetic series: Teaches the sum formula n(n+1)/2.
  • Bitwise XOR: Demonstrates cancellation property.
  • Algorithm optimization: O(n²) → O(n) with O(1) space.
  • Interview preparation: Very common coding interview question.
  • Real-world applications: Detecting missing records, sequence gaps.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive linear searchO(n²)O(1)
Hash arrayO(n)O(n)
Sum formulaO(n)O(1)
XOR operationO(n)O(1)

Key Insight

Since the array contains all numbers from 1 to n except one:

  • Sum approach: Missing = Sum(1..n) - Sum(arr)
  • XOR approach: XOR of 1..n and XOR of arr cancel all present numbers, leaving the missing one.

Syntax & Core Mechanics

Approach 1: Naive Linear Search

For each number from 1 to n, check if it exists in the array.

c

#include <stdio.h> int findMissingNaive(const int arr[], int n) { /* n is the size of the array; range is 1..n+1 */ int range = n + 1; for (int i = 1; i <= range; i++) { int found = 0; for (int j = 0; j < n; j++) { if (arr[j] == i) { found = 1; break; } } if (!found) return i; } return -1; } int main(void) { int arr[] = {8, 2, 4, 5, 3, 7, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Missing number: %d\n", findMissingNaive(arr, n)); return 0; }

Expected output:

text

Missing number: 6

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

Approach 2: Hash Array

Use a boolean array to mark presence.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> int findMissingHash(const int arr[], int n) { int range = n + 1; int *hash = calloc(range + 1, sizeof(int)); if (hash == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return -1; } for (int i = 0; i < n; i++) { hash[arr[i]] = 1; } int result = -1; for (int i = 1; i <= range; i++) { if (hash[i] == 0) { result = i; break; } } free(hash); return result; } int main(void) { int arr[] = {8, 2, 4, 5, 3, 7, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Missing number: %d\n", findMissingHash(arr, n)); return 0; }

Expected output:

text

Missing number: 6

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

Approach 3: Sum of n Terms Formula

Compute expected sum and subtract actual sum.

c

#include <stdio.h> int findMissingSum(const int arr[], int n) { int range = n + 1; /* Sum of array elements */ long long sumArr = 0; for (int i = 0; i < n; i++) { sumArr += arr[i]; } /* Expected sum: 1 + 2 + ... + range */ long long expectedSum = (long long)range * (range + 1) / 2; return (int)(expectedSum - sumArr); } int main(void) { int arr[] = {8, 2, 4, 5, 3, 7, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Missing number: %d\n", findMissingSum(arr, n)); return 0; }

Expected output:

text

Missing number: 6

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

Approach 4: XOR Operation

XOR all array elements and all numbers from 1 to n+1; the result is the missing number.

c

#include <stdio.h> int findMissingXOR(const int arr[], int n) { int range = n + 1; int xorAll = 0; /* XOR all array elements */ for (int i = 0; i < n; i++) { xorAll ^= arr[i]; } /* XOR all numbers from 1 to range */ for (int i = 1; i <= range; i++) { xorAll ^= i; } return xorAll; } int main(void) { int arr[] = {8, 2, 4, 5, 3, 7, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Missing number: %d\n", findMissingXOR(arr, n)); return 0; }

Expected output:

text

Missing number: 6

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

Step-by-Step XOR Trace

text

arr = [8, 2, 4, 5, 3, 7, 1], range = 8 XOR of arr: 8 ^ 2 ^ 4 ^ 5 ^ 3 ^ 7 ^ 1 XOR of 1..8: 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 Combined: (1^1) ^ (2^2) ^ (3^3) ^ (4^4) ^ (5^5) ^ (7^7) ^ 8 ^ 6 = 0 ^ 0 ^ 0 ^ 0 ^ 0 ^ 0 ^ 8 ^ 6 = 8 ^ 6 = 14 ... wait, let's recompute Actually: XOR of arr cancels 1,2,3,4,5,7 with XOR of 1..8 Remaining: 8 (from 1..8) ^ 6 (from 1..8) Wait, 6 isn't in arr, so XOR of 1..8 includes 6. XOR of arr includes 8 (which is also in 1..8). Cancellations: 1,2,3,4,5,7 cancel between the two XORs. Remaining: 6 (from 1..8) and 8 (from arr) ... Hmm, let me redo: XOR of arr = 8 ^ 2 ^ 4 ^ 5 ^ 3 ^ 7 ^ 1 XOR of 1..8 = 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 Combined = (1^1) ^ (2^2) ^ (3^3) ^ (4^4) ^ (5^5) ^ (7^7) ^ 8 ^ 8 ^ 6 = 0 ^ ... ^ 0 ^ 6 = 6 ✓

So the missing number is 6.


Full Practical Code Example with Explanation

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

Program: Missing Number Finder

c

/** * missing_number.c * Finds the missing number in an array of distinct integers from 1..n. * Compares naive, hash, sum, 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 findMissingNaive(const int arr[], int n); int findMissingHash(const int arr[], int n); int findMissingSum(const int arr[], int n); int findMissingXOR(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("=== Missing 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 (n-1, 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 (distinct, from 1 to %d):\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); printf("Range: 1 to %d\n", n + 1); int sumResult = findMissingSum(arr, n); int xorResult = findMissingXOR(arr, n); printf("Missing (sum): %d\n", sumResult); printf("Missing (XOR): %d\n", xorResult); printf("Verification: %s\n", (sumResult == xorResult) ? "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: O(n^2) */ int findMissingNaive(const int arr[], int n) { int range = n + 1; for (int i = 1; i <= range; i++) { int found = 0; for (int j = 0; j < n; j++) { if (arr[j] == i) { found = 1; break; } } if (!found) return i; } return -1; } /* Hash array: O(n) time, O(n) space */ int findMissingHash(const int arr[], int n) { int range = n + 1; int *hash = calloc(range + 2, sizeof(int)); if (hash == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return -1; } for (int i = 0; i < n; i++) { if (arr[i] >= 1 && arr[i] <= range) { hash[arr[i]] = 1; } } int result = -1; for (int i = 1; i <= range; i++) { if (hash[i] == 0) { result = i; break; } } free(hash); return result; } /* Sum formula: O(n) time, O(1) space */ int findMissingSum(const int arr[], int n) { int range = n + 1; long long sumArr = 0; for (int i = 0; i < n; i++) { sumArr += arr[i]; } long long expectedSum = (long long)range * (range + 1) / 2; return (int)(expectedSum - sumArr); } /* XOR: O(n) time, O(1) space */ int findMissingXOR(const int arr[], int n) { int range = n + 1; int xorAll = 0; for (int i = 0; i < n; i++) { xorAll ^= arr[i]; } for (int i = 1; i <= range; i++) { xorAll ^= i; } return xorAll; } /* 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[] = {8, 2, 4, 5, 3, 7, 1}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Range: 1..%d\n", n1 + 1); printf("Naive: %d\n", findMissingNaive(arr1, n1)); printf("Hash: %d\n", findMissingHash(arr1, n1)); printf("Sum: %d\n", findMissingSum(arr1, n1)); printf("XOR: %d\n", findMissingXOR(arr1, n1)); int arr2[] = {1, 2, 3, 5}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Range: 1..%d\n", n2 + 1); printf("Naive: %d\n", findMissingNaive(arr2, n2)); printf("Hash: %d\n", findMissingHash(arr2, n2)); printf("Sum: %d\n", findMissingSum(arr2, n2)); printf("XOR: %d\n", findMissingXOR(arr2, n2)); int arr3[] = {1}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3 (n=1): "); printArray(arr3, n3); printf("Range: 1..2\n"); printf("Naive: %d\n", findMissingNaive(arr3, n3)); printf("Hash: %d\n", findMissingHash(arr3, n3)); printf("Sum: %d\n", findMissingSum(arr3, n3)); printf("XOR: %d\n", findMissingXOR(arr3, n3)); int arr4[] = {2, 3, 4, 5}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4: "); printArray(arr4, n4); printf("Range: 1..%d\n", n4 + 1); printf("Sum: %d\n", findMissingSum(arr4, n4)); printf("XOR: %d\n", findMissingXOR(arr4, n4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 1000000) ===\n"); int n = 1000000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Fill 1..n+1 except missing n/2 */ int missing = n / 2; int idx = 0; for (int i = 1; i <= n + 1; i++) { if (i != missing) { arr[idx++] = i; } } clock_t start, end; double timeTaken; /* Hash */ start = clock(); int hashResult = findMissingHash(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); /* Sum */ start = clock(); int sumResult = findMissingSum(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sum (O(n)): Result = %d, Time = %.3f ms\n", sumResult, timeTaken); /* XOR */ start = clock(); int xorResult = findMissingXOR(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", (sumResult == xorResult && xorResult == hashResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single element, missing 1 */ int arr1[] = {2}; printf("Case 1: n=1, missing 1\n"); printf(" Array: "); printArray(arr1, 1); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr1, 1), findMissingXOR(arr1, 1)); /* Case 2: Single element, missing 2 */ int arr2[] = {1}; printf("\nCase 2: n=1, missing 2\n"); printf(" Array: "); printArray(arr2, 1); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr2, 1), findMissingXOR(arr2, 1)); /* Case 3: Missing 1 */ int arr3[] = {2, 3, 4, 5}; printf("\nCase 3: Missing 1\n"); printf(" Array: "); printArray(arr3, 4); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr3, 4), findMissingXOR(arr3, 4)); /* Case 4: Missing n+1 */ int arr4[] = {1, 2, 3, 4}; printf("\nCase 4: Missing last (n+1)\n"); printf(" Array: "); printArray(arr4, 4); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr4, 4), findMissingXOR(arr4, 4)); /* Case 5: Large array */ int n5 = 1000; int *arr5 = malloc(n5 * sizeof(int)); if (arr5 == NULL) return; for (int i = 1; i <= n5; i++) { arr5[i - 1] = i; } /* Missing n5+1 */ printf("\nCase 5: Large array, missing %d\n", n5 + 1); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr5, n5), findMissingXOR(arr5, n5)); free(arr5); /* Case 6: Overflow check */ int n6 = 100000; int *arr6 = malloc(n6 * sizeof(int)); if (arr6 == NULL) return; for (int i = 1; i <= n6; i++) { arr6[i - 1] = i; } /* Missing n6+1 */ printf("\nCase 6: Overflow-safe with long long (n=%d, missing %d)\n", n6, n6 + 1); printf(" Sum: %d, XOR: %d\n", findMissingSum(arr6, n6), findMissingXOR(arr6, n6)); free(arr6); } /* 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: Checks each number 1..n+1 against the array; O(n²).
  2. Hash: Uses a boolean array to mark presence; O(n) time, O(n) space.
  3. Sum: Uses arithmetic series formula; O(n) time, O(1) space.
  4. XOR: Cancels pairs leaving the missing number; O(n) time, O(1) space.

Expected Output (sample run)

text

=== Missing 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: [8, 2, 4, 5, 3, 7, 1] Range: 1..8 Naive: 6 Hash: 6 Sum: 6 XOR: 6 Array 2: [1, 2, 3, 5] Range: 1..5 Naive: 4 Hash: 4 Sum: 4 XOR: 4 Array 3 (n=1): [1] Range: 1..2 Naive: 2 Hash: 2 Sum: 2 XOR: 2 Array 4: [2, 3, 4, 5] Range: 1..5 Sum: 1 XOR: 1 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: n=1, missing 1 Array: [2] Sum: 1, XOR: 1 Case 2: n=1, missing 2 Array: [1] Sum: 2, XOR: 2 Case 3: Missing 1 Array: [2, 3, 4, 5] Sum: 1, XOR: 1 Case 4: Missing last (n+1) Array: [1, 2, 3, 4] Sum: 5, XOR: 5 Case 5: Large array, missing 1001 Sum: 1001, XOR: 1001 Case 6: Overflow-safe with long long (n=100000, missing 100001) Sum: 100001, XOR: 100001

Common Pitfalls, Edge Cases & Memory Considerations

1. Off-by-One in Range

If the array size is n, the range is 1..n+1 (not 1..n).

c

int range = n + 1; /* Correct: array has n elements from 1..n+1 */

2. Integer Overflow in Sum

n × (n+1) / 2 can overflow int for large n.

c

long long expectedSum = (long long)range * (range + 1) / 2;

3. Order of Operations for Overflow

(range * (range + 1)) / 2 may overflow before dividing. Cast first.

c

(long long)range * (range + 1) / 2 /* Safe */

4. Empty Array

For an empty array, the missing number is 1 (range 1..1).

c

int arr[] = {}; /* Missing = 1 */

5. Single Element

If the array has one element, the range is 1..2.

c

int arr[] = {1}; /* Missing = 2 */ int arr[] = {2}; /* Missing = 1 */

6. Missing First (1)

Works correctly with both sum and XOR.

c

int arr[] = {2, 3, 4}; /* Missing = 1 */

7. Missing Last (n+1)

Works correctly with both sum and XOR.

c

int arr[] = {1, 2, 3}; /* Missing = 4 */

8. Duplicate Values (Invalid Input)

The problem guarantees distinct integers. Duplicates break the assumptions.

9. Values Out of Range (Invalid Input)

The problem guarantees values in [1, n+1]. Invalid input breaks the algorithm.

10. Memory Considerations

  • Naive: O(1) extra space.
  • Hash: O(n) extra space.
  • Sum: O(1) extra space.
  • XOR: O(1) extra space — preferred for memory and avoiding overflow.
  • Use long long for sums when n is large.

Summary & Best Practices

Finding the missing number in an array of distinct integers from 1..n is elegantly solved using either the sum formula or XOR. XOR is preferred for avoiding overflow; sum is preferred for simplicity.

Key Takeaways

  • Array has n elements; range is 1..n+1.
  • Sum formula: Missing = n(n+1)/2 - Sum(arr).
  • XOR: XOR all elements and 1..n+1; result is missing.
  • Naive: O(n²) time, O(1) space.
  • Hash: O(n) time, O(n) space.
  • Sum/XOR: O(n) time, O(1) space — optimal.
  • Use long long for sums to prevent overflow.

Best Practices

  1. Use XOR for O(n) time, O(1) space, and no overflow risk.
  2. Use sum formula for simplicity when n is small.
  3. Use long long for the sum to prevent overflow.
  4. Handle edge cases: empty, single element, missing first, missing last.
  5. Verify the problem’s guarantees (distinct, in-range values).
  6. Use hash when the problem allows duplicates or values outside the range.
  7. Document the formula in comments.
  8. Test with n = 1 and large n.
  9. Profile for very large arrays.
  10. Avoid int overflow in intermediate computations.

By mastering this problem, you’ll understand arithmetic series and XOR cancellation, 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.

Learn to rearrange an array so even positions are greater than odd in C. Master O(n) single-pass swap and O(n log n) sort approaches.

Rearrange Array: Even Positioned Greater Than Odd

Rearrange Array Such That Even Positioned Are Greater Than Odd in C: A Comprehensive Tutorial

Given an array, the task is to rearrange its elements so that for every even position (1-based), the element is greater than or equal to the previous element, and for every odd position, the element is less than or equal to the previous element. This problem tests your understanding of array manipulation, sorting, and in-place rearrangement.

This tutorial provides an in-depth exploration of rearranging an array with the even-positioned-greater-than-odd condition: the sort-based approach, the single-pass swap approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[], rearrange it (using 1-based indexing) such that:

  • For every even index i: arr[i] >= arr[i-1]
  • For every odd index i: arr[i] <= arr[i-1]

Example:

text

Input: [1, 2, 2, 1] Output: [1, 2, 1, 2] Check (1-based): i=2 (even): arr[2]=2 >= arr[1]=1 ✓ i=3 (odd): arr[3]=1 <= arr[2]=2 ✓ i=4 (even): arr[4]=2 >= arr[3]=1 ✓

Why This Problem Matters

  • Array rearrangement: Common in sorting and partitioning problems.
  • Local swap technique: Demonstrates single-pass in-place transformations.
  • Sort-based reasoning: Shows how ordering simplifies constraints.
  • Interview preparation: Common coding interview question.
  • Pattern recognition: Teaches alternating peak-valley patterns.

Understanding the Pattern

The resulting array follows a zigzag pattern:

  • Odd positions hold smaller values (valleys).
  • Even positions hold larger values (peaks).

text

Index (1-based): 1 2 3 4 5 6 Pattern: low high low high low high

Approaches Overview

ApproachTime ComplexitySpace Complexity
Sort + assign max to evenO(n log n)O(n)
Single-pass swapO(n)O(1)

Syntax & Core Mechanics

Approach 1: Sort and Assign Max to Even Positions

Sort the array, then assign the largest n/2 elements to even positions and the remaining to odd positions.

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); } void rearrangeArraySort(int arr[], int n) { if (n <= 1) return; /* Sort array in ascending order */ qsort(arr, n, sizeof(int), compareInts); /* Create result array */ int *result = malloc(n * sizeof(int)); if (result == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } int ptr1 = 0; /* Points to smallest available */ int ptr2 = n - 1; /* Points to largest available */ for (int i = 0; i < n; i++) { /* i is 0-based; (i+1) is 1-based index */ if ((i + 1) % 2 == 0) { /* Even position: assign largest */ result[i] = arr[ptr2--]; } else { /* Odd position: assign smallest */ result[i] = arr[ptr1++]; } } /* Copy back to original array */ for (int i = 0; i < n; i++) { arr[i] = result[i]; } free(result); } int main(void) { int arr[] = {1, 2, 2, 1}; int n = sizeof(arr) / sizeof(arr[0]); rearrangeArraySort(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

1 2 1 2

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

Approach 2: Single-Pass Swap (In-Place)

Traverse the array once and swap adjacent elements when the condition is violated.

Rules (1-based):

  • At even index i: if arr[i] < arr[i-1], swap them.
  • At odd index i: if arr[i] > arr[i-1], swap them.

c

#include <stdio.h> void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } void rearrangeArraySwap(int arr[], int n) { if (n <= 1) return; for (int i = 1; i < n; i++) { /* i is 0-based; (i+1) is 1-based index */ if ((i + 1) % 2 == 0) { /* Even position (1-based): ensure arr[i] >= arr[i-1] */ if (arr[i] < arr[i - 1]) { swap(&arr[i], &arr[i - 1]); } } else { /* Odd position (1-based): ensure arr[i] <= arr[i-1] */ if (arr[i] > arr[i - 1]) { swap(&arr[i], &arr[i - 1]); } } } } int main(void) { int arr[] = {1, 2, 2, 1}; int n = sizeof(arr) / sizeof(arr[0]); rearrangeArraySwap(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

1 2 1 2

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

Step-by-Step Trace for [1, 2, 2, 1]

text

Initial: [1, 2, 2, 1] i = 1 (1-based i+1 = 2, even): arr[1]=2 >= arr[0]=1 → OK i = 2 (1-based i+1 = 3, odd): arr[2]=2 > arr[1]=2? No (equal) → OK i = 3 (1-based i+1 = 4, even): arr[3]=1 < arr[2]=2 → swap Array: [1, 2, 1, 2] Final: [1, 2, 1, 2] ✓

Full Practical Code Example with Explanation

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

Program: Rearrange Array Even-Positioned Greater

c

/** * rearrange_even_greater.c * Rearranges array so even positions (1-based) are >= previous. * Compares sort-based and single-pass swap 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 compareInts(const void *a, const void *b); void rearrangeSort(int arr[], int n); void rearrangeSwap(int arr[], int n); void swap(int *a, int *b); int verifyArrangement(const int arr[], int n); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); int arraysEqual(const int a[], const int b[], int n); void copyArray(const int src[], int dst[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Rearrange Array (Even Positioned Greater) ===\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("\nOriginal: "); printArray(arr, n); int *copy = malloc(n * sizeof(int)); if (copy == NULL) break; copyArray(arr, copy, n); rearrangeSort(copy, n); printf("Sort approach: "); printArray(copy, n); printf(" Verify: %s\n", verifyArrangement(copy, n) ? "PASS" : "FAIL"); copyArray(arr, copy, n); rearrangeSwap(copy, n); printf("Swap approach: "); printArray(copy, n); printf(" Verify: %s\n", verifyArrangement(copy, n) ? "PASS" : "FAIL"); free(copy); 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; } /* Comparison function for qsort */ 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); } /* Sort-based approach */ void rearrangeSort(int arr[], int n) { if (n <= 1) return; qsort(arr, n, sizeof(int), compareInts); int *result = malloc(n * sizeof(int)); if (result == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } int ptr1 = 0; int ptr2 = n - 1; for (int i = 0; i < n; i++) { if ((i + 1) % 2 == 0) { result[i] = arr[ptr2--]; /* Even position: largest */ } else { result[i] = arr[ptr1++]; /* Odd position: smallest */ } } for (int i = 0; i < n; i++) { arr[i] = result[i]; } free(result); } /* Swap helper */ void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } /* Single-pass swap approach */ void rearrangeSwap(int arr[], int n) { if (n <= 1) return; for (int i = 1; i < n; i++) { if ((i + 1) % 2 == 0) { /* Even position (1-based): ensure arr[i] >= arr[i-1] */ if (arr[i] < arr[i - 1]) { swap(&arr[i], &arr[i - 1]); } } else { /* Odd position (1-based): ensure arr[i] <= arr[i-1] */ if (arr[i] > arr[i - 1]) { swap(&arr[i], &arr[i - 1]); } } } } /* Verifies the arrangement condition */ int verifyArrangement(const int arr[], int n) { for (int i = 1; i < n; i++) { if ((i + 1) % 2 == 0) { /* Even position (1-based): arr[i] >= arr[i-1] */ if (arr[i] < arr[i - 1]) return 0; } else { /* Odd position (1-based): arr[i] <= arr[i-1] */ if (arr[i] > arr[i - 1]) return 0; } } 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"); } /* Compares arrays */ int arraysEqual(const int a[], const int b[], int n) { for (int i = 0; i < n; i++) { if (a[i] != b[i]) return 0; } return 1; } /* Copies array */ void copyArray(const int src[], int dst[], int n) { memcpy(dst, src, n * sizeof(int)); } /* Demonstrates both approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 2, 2, 1}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int copy1[MAX_SIZE]; printf("\nArray 1: "); printArray(arr1, n1); copyArray(arr1, copy1, n1); rearrangeSort(copy1, n1); printf(" Sort: "); printArray(copy1, n1); copyArray(arr1, copy1, n1); rearrangeSwap(copy1, n1); printf(" Swap: "); printArray(copy1, n1); int arr2[] = {1, 3, 2}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); copyArray(arr2, copy1, n2); rearrangeSort(copy1, n2); printf(" Sort: "); printArray(copy1, n2); copyArray(arr2, copy1, n2); rearrangeSwap(copy1, n2); printf(" Swap: "); printArray(copy1, n2); int arr3[] = {10, 5, 8, 3, 7, 2}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); copyArray(arr3, copy1, n3); rearrangeSort(copy1, n3); printf(" Sort: "); printArray(copy1, n3); copyArray(arr3, copy1, n3); rearrangeSwap(copy1, n3); printf(" Swap: "); printArray(copy1, n3); int arr4[] = {5, 5, 5, 5, 5}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4 (all same): "); printArray(arr4, n4); copyArray(arr4, copy1, n4); rearrangeSwap(copy1, n4); printf(" Swap: "); printArray(copy1, n4); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *original = malloc(n * sizeof(int)); int *arr = malloc(n * sizeof(int)); if (original == NULL || arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); free(original); free(arr); return; } srand(42); for (int i = 0; i < n; i++) { original[i] = rand() % 10000; } clock_t start, end; double timeTaken; /* Sort approach */ copyArray(original, arr, n); start = clock(); rearrangeSort(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sort approach: %.3f ms\n", timeTaken); /* Swap approach */ copyArray(original, arr, n); start = clock(); rearrangeSwap(arr, n); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Swap approach: %.3f ms\n", timeTaken); free(original); 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); rearrangeSwap(arr1, 1); printf(" After: "); printArray(arr1, 1); /* Case 2: Two elements */ int arr2[] = {5, 3}; printf("\nCase 2: Two elements\n"); printf(" Array: "); printArray(arr2, 2); rearrangeSwap(arr2, 2); printf(" After: "); printArray(arr2, 2); /* Case 3: Already satisfies condition */ int arr3[] = {1, 3, 2, 5, 4}; printf("\nCase 3: Already satisfies\n"); printf(" Array: "); printArray(arr3, 5); printf(" Verify before: %s\n", verifyArrangement(arr3, 5) ? "PASS" : "FAIL"); rearrangeSwap(arr3, 5); printf(" After: "); printArray(arr3, 5); printf(" Verify after: %s\n", verifyArrangement(arr3, 5) ? "PASS" : "FAIL"); /* Case 4: Decreasing array */ int arr4[] = {5, 4, 3, 2, 1}; printf("\nCase 4: Decreasing array\n"); printf(" Array: "); printArray(arr4, 5); rearrangeSwap(arr4, 5); printf(" After: "); printArray(arr4, 5); printf(" Verify: %s\n", verifyArrangement(arr4, 5) ? "PASS" : "FAIL"); /* Case 5: Increasing array */ int arr5[] = {1, 2, 3, 4, 5}; printf("\nCase 5: Increasing array\n"); printf(" Array: "); printArray(arr5, 5); rearrangeSwap(arr5, 5); printf(" After: "); printArray(arr5, 5); printf(" Verify: %s\n", verifyArrangement(arr5, 5) ? "PASS" : "FAIL"); /* Case 6: All same */ int arr6[] = {7, 7, 7, 7}; printf("\nCase 6: All same\n"); printf(" Array: "); printArray(arr6, 4); rearrangeSwap(arr6, 4); printf(" After: "); printArray(arr6, 4); printf(" Verify: %s\n", verifyArrangement(arr6, 4) ? "PASS" : "FAIL"); /* Case 7: Negative values */ int arr7[] = {-1, -2, -3, -4, -5}; printf("\nCase 7: Negative values\n"); printf(" Array: "); printArray(arr7, 5); rearrangeSwap(arr7, 5); printf(" After: "); printArray(arr7, 5); printf(" Verify: %s\n", verifyArrangement(arr7, 5) ? "PASS" : "FAIL"); } /* 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. Sort + Assign: Sort array, assign largest to even positions, smallest to odd. O(n log n).
  2. Single-Pass Swap: Traverse once, swapping adjacent elements when condition is violated. O(n).

Expected Output (sample run)

text

=== Rearrange Array (Even Positioned Greater) === --- 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, 2, 1] Sort: [1, 2, 1, 2] Swap: [1, 2, 1, 2] Array 2: [1, 3, 2] Sort: [1, 3, 2] Swap: [1, 3, 2] Array 3: [10, 5, 8, 3, 7, 2] Sort: [2, 10, 3, 8, 5, 7] Swap: [5, 10, 3, 8, 2, 7] Array 4 (all same): [5, 5, 5, 5, 5] Swap: [5, 5, 5, 5, 5] --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 100000) === Sort approach: 12.456 ms Swap approach: 0.512 ms --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [42] After: [42] Case 2: Two elements Array: [5, 3] After: [3, 5] Case 3: Already satisfies Array: [1, 3, 2, 5, 4] Verify before: PASS After: [1, 3, 2, 5, 4] Verify after: PASS Case 4: Decreasing array Array: [5, 4, 3, 2, 1] After: [4, 5, 2, 3, 1] Verify: PASS Case 5: Increasing array Array: [1, 2, 3, 4, 5] After: [1, 3, 2, 5, 4] Verify: PASS Case 6: All same Array: [7, 7, 7, 7] After: [7, 7, 7, 7] Verify: PASS Case 7: Negative values Array: [-1, -2, -3, -4, -5] After: [-2, -1, -4, -3, -5] Verify: PASS

Common Pitfalls, Edge Cases & Memory Considerations

1. Confusing 0-Based and 1-Based Indexing

The problem uses 1-based indexing. In C, arrays are 0-based.

c

/* i is 0-based; (i+1) is 1-based */ if ((i + 1) % 2 == 0) { /* even 1-based position */ }

Solution: Always convert: (i + 1) % 2 == 0 for even 1-based positions.

2. Wrong Swap Direction

Swapping in the wrong direction violates the condition.

c

/* Even 1-based position: ensure arr[i] >= arr[i-1] */ if (arr[i] < arr[i - 1]) swap(&arr[i], &arr[i-1]); /* Odd 1-based position: ensure arr[i] <= arr[i-1] */ if (arr[i] > arr[i - 1]) swap(&arr[i], &arr[i-1]);

3. Single Element

No swaps needed for n = 1.

c

if (n <= 1) return;

4. Two Elements

For n = 2, only the even position (1-based) matters.

c

int arr[] = {5, 3}; /* After swap: [3, 5] (arr[1] >= arr[0]) */

5. Equal Elements

Equality satisfies both >= and <=.

c

int arr[] = {5, 5, 5}; /* Already satisfies condition */

6. Memory Leaks in Sort Approach

Forgetting to free the result array.

c

free(result); /* Required */

7. Sort Stability

qsort is not stable, but this problem doesn’t require stability.

8. Integer Overflow in Comparisons

Comparisons of int values are safe; no overflow.

9. Performance for Large Arrays

Sort approach is O(n log n); swap approach is O(n) — prefer swap for performance.

10. Memory Considerations

  • Sort approach: O(n) extra space.
  • Swap approach: O(1) extra space.
  • Swap approach is preferred for memory and time efficiency.

Summary & Best Practices

Rearranging an array so even positions are greater than odd is elegantly solved by the single-pass swap approach in O(n) time and O(1) space. The sort-based approach provides an alternative but uses O(n) extra space and O(n log n) time.

Key Takeaways

  • Resulting array follows a zigzag (peak-valley) pattern.
  • Even 1-based positions hold larger values.
  • Odd 1-based positions hold smaller values.
  • Sort approach: O(n log n) time, O(n) space.
  • Swap approach: O(n) time, O(1) space — preferred.
  • Use (i + 1) % 2 == 0 for even 1-based positions.

Best Practices

  1. Use the single-pass swap approach for O(n) time and O(1) space.
  2. Use (i + 1) % 2 == 0 to check even 1-based positions.
  3. Handle edge cases: single element, all same, negative values.
  4. Free dynamically allocated memory in the sort approach.
  5. Verify the arrangement after rearrangement.
  6. Document the indexing convention (1-based) in comments.
  7. Test with increasing, decreasing, and random arrays.
  8. Use size_t for array sizes where appropriate.
  9. Avoid unnecessary sorting when O(n) suffices.
  10. Profile performance for large arrays.

By mastering this problem, you’ll understand local swap techniques and be prepared for related array rearrangement problems.

Learn to check duplicates within k distance in C: naive O(n×k) and hash-based O(n) sliding window approaches. Master efficient duplicate detection.

Duplicate Within K Distance in Array: C Guide

Duplicate Within K Distance in an Array in C: A Comprehensive Tutorial

Given an integer array and a value k, determine whether there exist two indices i and j such that arr[i] == arr[j] and |i - j| ≤ k. This problem tests your understanding of sliding windows, hash-based lookup, and time-space trade-offs. It appears frequently in coding interviews and has practical applications in duplicate detection and data validation.

This tutorial provides an in-depth exploration of detecting duplicates within K distance in C: naive nested-loop approach, hash-based sliding window approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n and an integer k, check if any two equal elements are within k indices of each other.

Formal condition: There exist i and j with i ≠ j such that:

  • arr[i] == arr[j]
  • |i - j| ≤ k

Examples:

text

Input: k = 3, arr = [1, 2, 3, 4, 1, 2, 3, 4] Output: No (duplicates are 4 apart) Input: k = 3, arr = [1, 2, 3, 1, 4, 5] Output: Yes (1 at indices 0 and 3, distance = 3) Input: k = 3, arr = [1, 2, 3, 4, 5] Output: No (no duplicates)

Why This Problem Matters

  • Sliding window technique: Foundation for many array algorithms.
  • Hash-based lookup: O(1) average-time membership testing.
  • Time-space trade-offs: O(n×k) time vs. O(n) time with O(k) space.
  • Real-world applications: Fraud detection, log analysis, network packet deduplication.
  • Interview preparation: Common coding interview question.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive nested loopsO(n × k)O(1)
Hash-based sliding windowO(n) avgO(k)

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

For each element, compare with the next k elements.

c

#include <stdio.h> int checkDuplicatesWithinKNaive(const int arr[], int n, int k) { for (int i = 0; i < n; i++) { for (int c = 1; c <= k && (i + c) < n; c++) { if (arr[i] == arr[i + c]) { return 1; /* Found */ } } } return 0; /* Not found */ } int main(void) { int arr[] = {10, 5, 3, 4, 3, 5, 6}; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; printf("%s\n", checkDuplicatesWithinKNaive(arr, n, k) ? "Yes" : "No"); return 0; }

Expected output:

text

Yes

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

Approach 2: Hash-Based Sliding Window

Maintain a hash set of the last k elements. If the current element is in the set, a duplicate exists within distance k.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #define TABLE_SIZE 1024 /* Simple hash set with linear probing */ 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 hash(int key, int capacity) { return ((key % capacity) + capacity) % capacity; } int contains(HashSet *set, int key) { int idx = hash(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 = hash(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) return; /* Already present */ idx = (idx + 1) % set->capacity; if (idx == start) return; /* Full */ } set->keys[idx] = key; set->occupied[idx] = 1; } void removeKey(HashSet *set, int key) { int idx = hash(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) { set->occupied[idx] = 0; /* Rehash subsequent elements in cluster */ int next = (idx + 1) % set->capacity; while (set->occupied[next]) { int rehashKey = set->keys[next]; set->occupied[next] = 0; insert(set, rehashKey); next = (next + 1) % set->capacity; } return; } idx = (idx + 1) % set->capacity; if (idx == start) break; } } int checkDuplicatesWithinK(const int arr[], int n, int k) { if (n <= 1 || k < 1) return 0; int capacity = (k + 1) * 2 + 1; HashSet *set = createHashSet(capacity); if (set == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return -1; } for (int i = 0; i < n; i++) { if (contains(set, arr[i])) { freeHashSet(set); return 1; /* Duplicate within k */ } insert(set, arr[i]); /* Remove element that is now more than k away */ if (i >= k) { removeKey(set, arr[i - k]); } } freeHashSet(set); return 0; } int main(void) { int arr[] = {10, 5, 3, 4, 3, 5, 6}; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; int result = checkDuplicatesWithinK(arr, n, k); if (result >= 0) { printf("%s\n", result ? "Yes" : "No"); } return 0; }

Expected output:

text

Yes

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

Simplified Approach: Using a Sorted Sliding Window (Alternative)

For small k, a fixed-size circular buffer with linear search works well.

c

#include <stdio.h> #include <string.h> int checkDuplicatesWithinKBuffer(const int arr[], int n, int k) { if (n <= 1 || k < 1) return 0; int windowSize = (k + 1 < n) ? k + 1 : n; int *window = malloc(windowSize * sizeof(int)); if (window == NULL) return -1; int count = 0; for (int i = 0; i < n; i++) { /* Linear search in current window */ for (int j = 0; j < count; j++) { if (window[j] == arr[i]) { free(window); return 1; } } /* Add current element */ if (count < windowSize) { window[count++] = arr[i]; } else { /* Shift window left */ for (int j = 0; j < windowSize - 1; j++) { window[j] = window[j + 1]; } window[windowSize - 1] = arr[i]; } } free(window); return 0; }

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


Full Practical Code Example with Explanation

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

Program: Duplicate Within K Distance Checker

c

/** * duplicate_within_k.c * Checks if array contains duplicates within k distance. * Compares naive and hash-based sliding window 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 checkDuplicatesNaive(const int arr[], int n, int k); int checkDuplicatesHash(const int arr[], int n, int k); 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("=== Duplicate Within K Distance Checker ===\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, k; 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; } } if (readInteger("Enter k: ", &k) != 0 || k < 1) { fprintf(stderr, "Invalid k.\n"); break; } printf("\nArray: "); printArray(arr, n); printf("k = %d\n", k); int naive = checkDuplicatesNaive(arr, n, k); int hash = checkDuplicatesHash(arr, n, k); printf("Naive result: %s\n", naive ? "Yes" : "No"); printf("Hash result: %s\n", hash ? "Yes" : "No"); printf("Verification: %s\n", (naive == hash) ? "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 * k) */ int checkDuplicatesNaive(const int arr[], int n, int k) { if (n <= 1 || k < 1) return 0; for (int i = 0; i < n; i++) { for (int c = 1; c <= k && (i + c) < n; c++) { if (arr[i] == arr[i + c]) { return 1; } } } return 0; } /* Hash-based sliding window: O(n) average, O(k) space */ 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 capacity) { return ((key % capacity) + capacity) % capacity; } int containsHash(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 insertHash(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; } void removeHash(HashSet *set, int key) { int idx = hashFunc(key, set->capacity); int start = idx; while (set->occupied[idx]) { if (set->keys[idx] == key) { set->occupied[idx] = 0; /* Rehash the cluster */ int next = (idx + 1) % set->capacity; while (set->occupied[next]) { int rehashKey = set->keys[next]; set->occupied[next] = 0; insertHash(set, rehashKey); next = (next + 1) % set->capacity; } return; } idx = (idx + 1) % set->capacity; if (idx == start) break; } } int checkDuplicatesHash(const int arr[], int n, int k) { if (n <= 1 || k < 1) return 0; int capacity = (k + 1) * 2 + 1; HashSet *set = createHashSet(capacity); if (set == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return -1; } for (int i = 0; i < n; i++) { if (containsHash(set, arr[i])) { freeHashSet(set); return 1; } insertHash(set, arr[i]); if (i >= k) { removeHash(set, arr[i - k]); } } freeHashSet(set); 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"); } /* Demonstrates both approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 2, 3, 4, 1, 2, 3, 4}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("k = 3 → Naive: %s, Hash: %s\n", checkDuplicatesNaive(arr1, n1, 3) ? "Yes" : "No", checkDuplicatesHash(arr1, n1, 3) ? "Yes" : "No"); int arr2[] = {1, 2, 3, 1, 4, 5}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("k = 3 → Naive: %s, Hash: %s\n", checkDuplicatesNaive(arr2, n2, 3) ? "Yes" : "No", checkDuplicatesHash(arr2, n2, 3) ? "Yes" : "No"); int arr3[] = {1, 2, 3, 4, 5}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("k = 3 → Naive: %s, Hash: %s\n", checkDuplicatesNaive(arr3, n3, 3) ? "Yes" : "No", checkDuplicatesHash(arr3, n3, 3) ? "Yes" : "No"); int arr4[] = {10, 5, 3, 4, 3, 5, 6}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4: "); printArray(arr4, n4); printf("k = 3 → Naive: %s, Hash: %s\n", checkDuplicatesNaive(arr4, n4, 3) ? "Yes" : "No", checkDuplicatesHash(arr4, n4, 3) ? "Yes" : "No"); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000, k = 100) ===\n"); int n = 100000; int k = 100; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Fill with distinct values to avoid early exit */ for (int i = 0; i < n; i++) { arr[i] = i; } clock_t start, end; double timeTaken; /* Naive */ start = clock(); int naiveResult = checkDuplicatesNaive(arr, n, k); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n*k)): Result = %s, Time = %.3f ms\n", naiveResult ? "Yes" : "No", timeTaken); /* Hash */ start = clock(); int hashResult = checkDuplicatesHash(arr, n, k); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (O(n)): Result = %s, Time = %.3f ms\n", hashResult ? "Yes" : "No", timeTaken); 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: %s\n", checkDuplicatesHash(arr1, 0, 3) ? "Yes" : "No"); /* Case 2: Single element */ int arr2[] = {42}; printf("\nCase 2: Single element\n"); printf(" Array: "); printArray(arr2, 1); printf(" k = 3 → %s\n", checkDuplicatesHash(arr2, 1, 3) ? "Yes" : "No"); /* Case 3: Adjacent duplicates */ int arr3[] = {1, 1, 2, 3}; printf("\nCase 3: Adjacent duplicates\n"); printf(" Array: "); printArray(arr3, 4); printf(" k = 1 → %s\n", checkDuplicatesHash(arr3, 4, 1) ? "Yes" : "No"); /* Case 4: Duplicates exactly at distance k */ int arr4[] = {1, 2, 3, 1}; printf("\nCase 4: Duplicates exactly at distance k\n"); printf(" Array: "); printArray(arr4, 4); printf(" k = 3 → %s\n", checkDuplicatesHash(arr4, 4, 3) ? "Yes" : "No"); /* Case 5: k = 0 (no distance allowed) */ int arr5[] = {1, 1, 2}; printf("\nCase 5: k = 0\n"); printf(" Array: "); printArray(arr5, 3); printf(" k = 0 → %s (no duplicates possible)\n", checkDuplicatesHash(arr5, 3, 0) ? "Yes" : "No"); /* Case 6: k larger than array */ int arr6[] = {1, 2, 3, 1}; printf("\nCase 6: k > array size\n"); printf(" Array: "); printArray(arr6, 4); printf(" k = 100 → %s\n", checkDuplicatesHash(arr6, 4, 100) ? "Yes" : "No"); /* Case 7: All same elements */ int arr7[] = {5, 5, 5, 5, 5}; printf("\nCase 7: All same elements\n"); printf(" Array: "); printArray(arr7, 5); printf(" k = 1 → %s\n", checkDuplicatesHash(arr7, 5, 1) ? "Yes" : "No"); /* Case 8: Negative values */ int arr8[] = {-1, -2, -3, -1}; printf("\nCase 8: Negative values\n"); printf(" Array: "); printArray(arr8, 4); printf(" k = 3 → %s\n", checkDuplicatesHash(arr8, 4, 3) ? "Yes" : "No"); } /* 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: Compares each element with the next k elements; O(n×k).
  2. Hash: Maintains a hash set of the last k elements; O(n) average.

Expected Output (sample run)

text

=== Duplicate Within K Distance Checker === --- 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, 2, 3, 4, 1, 2, 3, 4] k = 3 → Naive: No, Hash: No Array 2: [1, 2, 3, 1, 4, 5] k = 3 → Naive: Yes, Hash: Yes Array 3: [1, 2, 3, 4, 5] k = 3 → Naive: No, Hash: No Array 4: [10, 5, 3, 4, 3, 5, 6] k = 3 → Naive: Yes, Hash: Yes --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 100000, k = 100) === Naive (O(n*k)): Result = No, Time = 45.234 ms Hash (O(n)): Result = No, Time = 12.567 ms --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Empty array Result: No Case 2: Single element Array: [42] k = 3 → No Case 3: Adjacent duplicates Array: [1, 1, 2, 3] k = 1 → Yes Case 4: Duplicates exactly at distance k Array: [1, 2, 3, 1] k = 3 → Yes Case 5: k = 0 Array: [1, 1, 2] k = 0 → No (no duplicates possible) Case 6: k > array size Array: [1, 2, 3, 1] k = 100 → Yes Case 7: All same elements Array: [5, 5, 5, 5, 5] k = 1 → Yes Case 8: Negative values Array: [-1, -2, -3, -1] k = 3 → Yes

Common Pitfalls, Edge Cases & Memory Considerations

1. Off-by-One in Window Size

The window should contain exactly the last k elements (not k + 1).

c

if (i >= k) { removeHash(set, arr[i - k]); /* Correct: remove element at distance k + 1 */ }

Solution: Remove arr[i - k] when i >= k to keep window size at most k.

2. k = 0 Handling

With k = 0, only the same index can be “within distance 0”, but indices must differ.

c

if (k < 1) return 0; /* No duplicates possible */

3. k >= n Handling

If k >= n, the entire array is the window; any duplicate counts.

c

/* Handled naturally by the algorithm */

4. Empty Array

No duplicates possible.

c

if (n <= 1) return 0;

5. Hash Set Sizing

The hash set must have capacity > k to hold the window.

c

int capacity = (k + 1) * 2 + 1; /* Safe sizing */

6. Hash Collisions

Linear probing handles collisions but must be sized appropriately.

Solution: Size the table at least twice the window size.

7. Removing from Hash Set

Incorrect removal breaks the sliding window invariant.

c

removeHash(set, arr[i - k]); /* Only when i >= k */

8. Negative Values in Hash

Hash function must handle negative keys.

c

return ((key % capacity) + capacity) % capacity;

9. Memory Leaks

Free the hash set after use.

c

freeHashSet(set);

10. Memory Considerations

  • Naive: O(1) extra space, O(n×k) time.
  • Hash: O(k) extra space, O(n) average time.
  • Hash approach preferred for large arrays and moderate k.
  • Use a balanced BST or sorted array for worst-case O(n log k).

Summary & Best Practices

Detecting duplicates within K distance is a classic sliding window problem. The hash-based approach achieves O(n) average time with O(k) space.

Key Takeaways

  • Naive: O(n × k) time, O(1) space.
  • Hash sliding window: O(n) average time, O(k) space.
  • Maintain exactly the last k elements in the window.
  • Remove elements at distance k + 1 as the window slides.
  • Handle k = 0, empty arrays, and single elements.

Best Practices

  1. Use the hash-based approach for O(n) average time.
  2. Size the hash table to at least 2 × (k + 1).
  3. Handle negative keys in the hash function.
  4. Check n <= 1 and k < 1 early.
  5. Remove elements at i - k when i >= k.
  6. Free the hash set after use.
  7. Test with edge cases: empty, single, k = 0, k >= n.
  8. Document window size in comments.
  9. Consider k-sized sorted arrays for worst-case guarantees.
  10. Profile for large arrays with varying k.

By mastering this problem, you’ll understand the sliding window technique and hash-based lookups, essential for many array and string problems.

Learn to find minimum cost to reduce an array to size 1 by removing the larger of pairs in C. Master the (n-1) × min formula with O(n) time.

Minimum Cost to Make Array Size 1 by Removing Larger

Minimum Cost to Make Array Size 1 by Removing Larger of Pairs in C

Given an array of n integers, we need to reduce its size to one. In each operation, we select a pair of integers and remove the larger one. This decreases the array size by 1. The cost of this operation is the value of the smaller element in the pair. The task is to find the minimum sum of costs to reduce the array to a single element.

This tutorial provides an in-depth exploration of this problem: greedy insight, mathematical derivation, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n, repeatedly:

  • Select a pair of elements.
  • Remove the larger one.
  • Add the smaller one to the total cost.

Continue until only one element remains. Find the minimum total cost.

Example 1:

text

Input: [4, 3, 2] Operations: 1. Choose (4, 2): remove 4, cost = 2. Array: [2, 3] 2. Choose (2, 3): remove 3, cost = 2. Array: [2] Total cost = 2 + 2 = 4

Example 2:

text

Input: [3, 4] Operations: 1. Choose (3, 4): remove 4, cost = 3. Array: [3] Total cost = 3

Why This Problem Matters

  • Greedy algorithm design: Demonstrates optimal substructure.
  • Mathematical insight: Reveals a simple closed-form solution.
  • Optimization: Teaches recognizing patterns in cost problems.
  • Interview preparation: Common coding interview question.
  • Algorithm analysis: Shows how complex-looking problems simplify.

Key Insight

The optimal strategy is to always pair the minimum element with every other element. Each operation removes the larger element and adds the minimum to the cost.

Since the array must be reduced from n to 1 element, exactly n - 1 operations are needed. If we always use the minimum element in each operation, the total cost is:

text

Total Cost = (n - 1) × min(arr)

Why is this optimal?

  • The minimum element can never be removed (it’s always the smaller in any pair it participates in).
  • Every removal operation must have some element as the “smaller” — the minimum is the cheapest choice.
  • Using the minimum in all n - 1 operations minimizes the total.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Simulation (greedy pairing)O(n log n)O(1)
Mathematical formulaO(n)O(1)

Syntax & Core Mechanics

Approach 1: Simulation (Greedy)

Repeatedly pair the minimum with the current largest, removing the larger.

c

#include <stdio.h> #include <stdlib.h> #include <limits.h> int minimumCostSimulation(int arr[], int n) { if (n <= 1) return 0; /* Find minimum */ int minVal = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] < minVal) minVal = arr[i]; } /* n - 1 operations, each costing minVal */ return (n - 1) * minVal; } int main(void) { int arr[] = {4, 3, 2}; int n = sizeof(arr) / sizeof(arr[0]); printf("Minimum cost: %d\n", minimumCostSimulation(arr, n)); return 0; }

Expected output:

text

Minimum cost: 4

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

Approach 2: Mathematical Formula (Direct)

Simply compute (n - 1) * min(arr).

c

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

Expected output:

text

Minimum cost: 4

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

Detailed Example

For arr = [4, 3, 2]:

text

min = 2, n = 3 Total cost = (3 - 1) × 2 = 4

For arr = [10, 5, 8, 3, 7]:

text

min = 3, n = 5 Total cost = (5 - 1) × 3 = 12

Verification:

text

Operations (using min = 3): Pair (3, 10): remove 10, cost = 3 Pair (3, 8): remove 8, cost = 3 Pair (3, 7): remove 7, cost = 3 Pair (3, 5): remove 5, cost = 3 Total = 12 ✓

Full Practical Code Example with Explanation

This program demonstrates the greedy insight with simulation and formula verification, plus edge cases.

Program: Minimum Cost to Make Array Size 1

c

/** * min_cost_make_array_size_one.c * Finds minimum cost to reduce array to size 1 by removing larger of pairs. * Demonstrates greedy simulation and mathematical formula. */ #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 minimumCostSimulation(const int arr[], int n); int minimumCostFormula(const int arr[], int n); int findMin(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("=== Minimum Cost to Make Array Size 1 ===\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); int result = minimumCostFormula(arr, n); printf("Minimum cost: %d\n", result); printf("Formula: (%d - 1) × %d = %d\n", n, findMin(arr, 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; } /* Finds minimum element */ int findMin(const int arr[], int n) { if (n <= 0) return INT_MAX; int minVal = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] < minVal) { minVal = arr[i]; } } return minVal; } /* Simulation-based greedy approach */ int minimumCostSimulation(const int arr[], int n) { if (n <= 1) return 0; int minVal = findMin(arr, n); /* n - 1 operations, each costing minVal */ return (n - 1) * minVal; } /* Mathematical formula approach */ int minimumCostFormula(const int arr[], int n) { if (n <= 1) return 0; int minVal = findMin(arr, n); return (n - 1) * minVal; } /* 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[] = {4, 3, 2}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Min: %d, Size: %d\n", findMin(arr1, n1), n1); printf("Simulation: %d\n", minimumCostSimulation(arr1, n1)); printf("Formula: %d\n", minimumCostFormula(arr1, n1)); int arr2[] = {3, 4}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Min: %d, Size: %d\n", findMin(arr2, n2), n2); printf("Simulation: %d\n", minimumCostSimulation(arr2, n2)); printf("Formula: %d\n", minimumCostFormula(arr2, n2)); int arr3[] = {10, 5, 8, 3, 7}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("Min: %d, Size: %d\n", findMin(arr3, n3), n3); printf("Simulation: %d\n", minimumCostSimulation(arr3, n3)); printf("Formula: %d\n", minimumCostFormula(arr3, n3)); int arr4[] = {1, 2, 3, 4, 5}; int n4 = sizeof(arr4) / sizeof(arr4[0]); printf("\nArray 4: "); printArray(arr4, n4); printf("Min: %d, Size: %d\n", findMin(arr4, n4), n4); printf("Simulation: %d\n", minimumCostSimulation(arr4, n4)); printf("Formula: %d\n", minimumCostFormula(arr4, n4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 10000000) ===\n"); int size = 10000000; int *arr = malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } for (int i = 0; i < size; i++) { arr[i] = (i % 100) + 1; } clock_t start, end; double timeTaken; start = clock(); int result = minimumCostFormula(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Result: %d\n", result); printf("Time: %.3f ms\n", timeTaken); 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(" Cost: %d (no operations needed)\n", minimumCostFormula(arr1, 1)); /* Case 2: Two elements */ int arr2[] = {10, 20}; printf("\nCase 2: Two elements\n"); printf(" Array: "); printArray(arr2, 2); printf(" Cost: %d\n", minimumCostFormula(arr2, 2)); /* Case 3: All same elements */ int arr3[] = {5, 5, 5, 5}; printf("\nCase 3: All same elements\n"); printf(" Array: "); printArray(arr3, 4); printf(" Cost: %d\n", minimumCostFormula(arr3, 4)); /* Case 4: Negative numbers */ int arr4[] = {-3, -1, -4, -2}; printf("\nCase 4: Negative numbers\n"); printf(" Array: "); printArray(arr4, 4); printf(" Cost: %d\n", minimumCostFormula(arr4, 4)); /* Case 5: Zero values */ int arr5[] = {0, 5, 10}; printf("\nCase 5: Zero values\n"); printf(" Array: "); printArray(arr5, 3); printf(" Cost: %d\n", minimumCostFormula(arr5, 3)); /* Case 6: Large array with min at end */ int arr6[] = {100, 200, 300, 1}; printf("\nCase 6: Min at end\n"); printf(" Array: "); printArray(arr6, 4); printf(" Cost: %d\n", minimumCostFormula(arr6, 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 Approach

  1. Greedy insight: Always pair the minimum with every other element.
  2. Formula: (n - 1) × min(arr).
  3. Simulation: Conceptually the same as the formula; every operation costs the minimum.
  4. Verification: Both approaches yield identical results.
  5. Overflow protection: For very large arrays, use long long.

Expected Output (sample run)

text

=== Minimum Cost to Make Array Size 1 === --- 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: [4, 3, 2] Min: 2, Size: 3 Simulation: 4 Formula: 4 Array 2: [3, 4] Min: 3, Size: 2 Simulation: 3 Formula: 3 Array 3: [10, 5, 8, 3, 7] Min: 3, Size: 5 Simulation: 12 Formula: 12 Array 4: [1, 2, 3, 4, 5] Min: 1, Size: 5 Simulation: 4 Formula: 4 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [42] Cost: 0 (no operations needed) Case 2: Two elements Array: [10, 20] Cost: 10 Case 3: All same elements Array: [5, 5, 5, 5] Cost: 15 Case 4: Negative numbers Array: [-3, -1, -4, -2] Cost: -12 Case 5: Zero values Array: [0, 5, 10] Cost: 0 Case 6: Min at end Array: [100, 200, 300, 1] Cost: 3

Common Pitfalls, Edge Cases & Memory Considerations

1. Misunderstanding the Operation

The cost is the smaller of the two selected elements, and the larger is removed.

c

/* Cost = smaller element, remove larger */

Solution: Always pair the minimum with another element.

2. Wrong Formula

Using n × min instead of (n - 1) × min.

c

return n * minVal; /* Wrong */ return (n - 1) * minVal; /* Correct */

Solution: Remember exactly n - 1 operations are needed.

3. Empty Array

Return 0 or handle gracefully.

c

if (n <= 0) return 0;

4. Single Element

No operations needed; cost is 0.

c

if (n == 1) return 0;

5. Negative Minimum

If minimum is negative, the “cost” is negative (gain).

c

int arr[] = {-5, 10, 20}; /* Cost = (3 - 1) × (-5) = -10 */

Solution: The formula works for negatives; document the interpretation.

6. Overflow in Multiplication

(n - 1) × min can overflow int.

c

long long cost = (long long)(n - 1) * minVal; /* Safe */

Solution: Use long long for large arrays.

7. Zero Minimum

If minimum is 0, total cost is 0.

c

int arr[] = {0, 5, 10}; /* Cost = (3 - 1) × 0 = 0 */

8. All Same Elements

Total cost = (n - 1) × value.

c

int arr[] = {5, 5, 5, 5}; /* Cost = 3 × 5 = 15 */

9. Duplicate Minimums

Multiple minimums don’t change the formula.

c

int arr[] = {2, 2, 5, 7}; /* Cost = 3 × 2 = 6 */

10. Memory Considerations

  • Both approaches use O(1) extra space.
  • No sorting or dynamic allocation required.
  • Use long long for cost on large arrays.
  • Simple and efficient for any input size.

Summary & Best Practices

The minimum cost to reduce an array to size 1 by removing the larger of each pair is simply (n - 1) × min(arr). This elegant formula comes from the greedy insight that we should always pair the minimum element with every other element.

Key Takeaways

  • Only n - 1 operations are needed.
  • Each operation costs the smaller element in the pair.
  • Greedy strategy: always use the minimum element.
  • Total cost = (n - 1) × min(arr).
  • Time: O(n), Space: O(1).
  • Works for negative and zero values.

Best Practices

  1. Use the formula for O(n) time and O(1) space.
  2. Find the minimum in a single pass.
  3. Use long long for large arrays to prevent overflow.
  4. Handle edge cases: empty, single element, all same.
  5. Document the greedy insight in comments.
  6. Test with negative values and zeros.
  7. Verify with simulation during testing.
  8. Use size_t for array sizes.
  9. Validate input (array size).
  10. Profile for very large arrays.

By mastering this problem, you’ll recognize greedy patterns and mathematical simplifications in algorithm design.

Learn to find minimum increment operations by k to make all array elements equal in C. Master O(n) greedy approach with divisibility checks.

Minimum Increment by k to Make Equal in C

Minimum Increment by k to Make Equal in C: A Comprehensive Tutorial

Given an array of integers and a value k, you can increment any element by k in one operation. The task is to find the minimum number of operations needed to make all elements equal. If it’s impossible, return -1. This problem tests your understanding of modular arithmetic, array analysis, and greedy optimization.

This tutorial provides an in-depth exploration of finding the minimum increment operations to make all array elements equal: naive approach, optimized maximum-target approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] and an integer k, you can perform operations that increment any element by exactly k. Find the minimum number of operations to make all elements equal, or -1 if impossible.

Example 1:

text

arr = [4, 4, 4, 2], k = 2 Increment 2 by 2 → 4 Operations = 1 Output: 1

Example 2:

text

arr = [4, 2, 6, 8], k = 3 Differences from max (8): 4, 6, 2, 0 4 % 3 ≠ 0 → impossible Output: -1

Example 3:

text

arr = [4, 7, 19, 16], k = 3 Max = 19 4 → 19: diff = 15, ops = 5 7 → 19: diff = 12, ops = 4 19 → 19: diff = 0, ops = 0 16 → 19: diff = 3, ops = 1 Total = 10 Output: 10

Why This Problem Matters

  • Modular arithmetic: Tests divisibility reasoning.
  • Greedy optimization: Demonstrates why max is the optimal target.
  • Feasibility checking: Determines when a solution exists.
  • Interview preparation: Common coding interview question.
  • Data normalization: Making datasets uniform.

Key Insight

Since we can only increment, the final target must be at least the maximum element. To minimize operations, the target should be exactly the maximum. If any element’s difference from the maximum isn’t divisible by k, the task is impossible.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive (try each as target)O(n²)O(1)
Optimized (target = max)O(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive (Try Every Element as Target)

For each element, check if all others can be incremented to match it.

c

#include <stdio.h> #include <limits.h> int minOpsNaive(const int arr[], int n, int k) { if (n <= 0 || k <= 0) return -1; int ans = INT_MAX; for (int i = 0; i < n; i++) { int target = arr[i]; int operations = 0; int valid = 1; for (int j = 0; j < n; j++) { if (arr[j] > target || (target - arr[j]) % k != 0) { valid = 0; break; } operations += (target - arr[j]) / k; } if (valid && operations < ans) { ans = operations; } } return (ans == INT_MAX) ? -1 : ans; } int main(void) { int arr[] = {4, 4, 4, 2}; int n = sizeof(arr) / sizeof(arr[0]); int k = 2; printf("Minimum operations: %d\n", minOpsNaive(arr, n, k)); return 0; }

Expected output:

text

Minimum operations: 1

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

Approach 2: Optimized (Target = Maximum)

Since we can only increment, the target must be at least the maximum. Making all elements equal to the maximum minimizes operations.

c

#include <stdio.h> #include <limits.h> int minOpsOptimized(const int arr[], int n, int k) { if (n <= 0 || k <= 0) return -1; /* Find maximum element */ int maxVal = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] > maxVal) { maxVal = arr[i]; } } /* Check divisibility and count operations */ long long operations = 0; for (int i = 0; i < n; i++) { int diff = maxVal - arr[i]; if (diff % k != 0) { return -1; } operations += diff / k; } return (int)operations; } int main(void) { int arr[] = {4, 4, 4, 2}; int n = sizeof(arr) / sizeof(arr[0]); int k = 2; printf("Minimum operations: %d\n", minOpsOptimized(arr, n, k)); return 0; }

Expected output:

text

Minimum operations: 1

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

Why Target = Maximum is Optimal

  • We can only increment, so no element can be reduced.
  • The final value must be ≥ max element.
  • Any target > max requires extra operations for all elements.
  • Target = max gives the minimum total operations.

Example:

text

arr = [4, 7, 19, 16], k = 3, max = 19 Target = 19: ops = 5 + 4 + 0 + 1 = 10 Target = 22: ops = 6 + 5 + 1 + 2 = 14 (more operations)

Full Practical Code Example with Explanation

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

Program: Minimum Increment by k to Make Equal

c

/** * min_increment_equal.c * Finds minimum increment operations by k to make all array elements equal. * Compares naive and optimized 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 minOpsNaive(const int arr[], int n, int k); int minOpsOptimized(const int arr[], int n, int k); 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("=== Minimum Increment by k to Make Equal ===\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, k; 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; } } if (readInteger("Enter k: ", &k) != 0 || k <= 0) { fprintf(stderr, "Invalid k.\n"); break; } printf("\nArray: "); printArray(arr, n); printf("k = %d\n", k); int naive = minOpsNaive(arr, n, k); int optimized = minOpsOptimized(arr, n, k); printf("Naive result: %d\n", naive); printf("Optimized result: %d\n", optimized); printf("Verification: %s\n", (naive == optimized) ? "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) */ int minOpsNaive(const int arr[], int n, int k) { if (n <= 0 || k <= 0) return -1; int ans = INT_MAX; for (int i = 0; i < n; i++) { int target = arr[i]; long long operations = 0; int valid = 1; for (int j = 0; j < n; j++) { if (arr[j] > target || (target - arr[j]) % k != 0) { valid = 0; break; } operations += (target - arr[j]) / k; } if (valid && operations < ans) { ans = (int)operations; } } return (ans == INT_MAX) ? -1 : ans; } /* Optimized approach: O(n) */ int minOpsOptimized(const int arr[], int n, int k) { if (n <= 0 || k <= 0) return -1; /* Find maximum element */ int maxVal = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] > maxVal) { maxVal = arr[i]; } } /* Check divisibility and count operations */ long long operations = 0; for (int i = 0; i < n; i++) { int diff = maxVal - arr[i]; if (diff % k != 0) { return -1; } operations += diff / k; } return (int)operations; } /* 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[] = {4, 4, 4, 2}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int k1 = 2; printf("\nArray 1: "); printArray(arr1, n1); printf("k = %d\n", k1); printf("Naive: %d\n", minOpsNaive(arr1, n1, k1)); printf("Optimized: %d\n", minOpsOptimized(arr1, n1, k1)); int arr2[] = {4, 2, 6, 8}; int n2 = sizeof(arr2) / sizeof(arr2[0]); int k2 = 3; printf("\nArray 2: "); printArray(arr2, n2); printf("k = %d\n", k2); printf("Naive: %d\n", minOpsNaive(arr2, n2, k2)); printf("Optimized: %d\n", minOpsOptimized(arr2, n2, k2)); int arr3[] = {4, 7, 19, 16}; int n3 = sizeof(arr3) / sizeof(arr3[0]); int k3 = 3; printf("\nArray 3: "); printArray(arr3, n3); printf("k = %d\n", k3); printf("Naive: %d\n", minOpsNaive(arr3, n3, k3)); printf("Optimized: %d\n", minOpsOptimized(arr3, n3, k3)); int arr4[] = {5, 5, 5}; int n4 = sizeof(arr4) / sizeof(arr4[0]); int k4 = 4; printf("\nArray 4 (already equal): "); printArray(arr4, n4); printf("k = %d\n", k4); printf("Naive: %d\n", minOpsNaive(arr4, n4, k4)); printf("Optimized: %d\n", minOpsOptimized(arr4, n4, k4)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 5000) ===\n"); int size = 5000; int k = 3; int *arr = malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Create array with elements differing by multiples of k */ for (int i = 0; i < size; i++) { arr[i] = 100 + (i % 10) * k; } clock_t start, end; double timeTaken; /* Naive approach */ start = clock(); int naiveResult = minOpsNaive(arr, size, k); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n^2)): Result = %d, Time = %.3f ms\n", naiveResult, timeTaken); /* Optimized approach */ start = clock(); int optResult = minOpsOptimized(arr, size, k); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Optimized (O(n)): Result = %d, Time = %.3f ms\n", optResult, timeTaken); printf("\nVerification: %s\n", (naiveResult == optResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single element */ int arr1[] = {10}; printf("Case 1: Single element\n"); printf(" Array: "); printArray(arr1, 1); printf(" k = 3 → Result: %d\n", minOpsOptimized(arr1, 1, 3)); /* Case 2: Two elements, already equal */ int arr2[] = {5, 5}; printf("\nCase 2: Already equal\n"); printf(" Array: "); printArray(arr2, 2); printf(" k = 3 → Result: %d\n", minOpsOptimized(arr2, 2, 3)); /* Case 3: k = 1 (always possible) */ int arr3[] = {1, 5, 3}; printf("\nCase 3: k = 1 (always possible)\n"); printf(" Array: "); printArray(arr3, 3); printf(" k = 1 → Result: %d\n", minOpsOptimized(arr3, 3, 1)); /* Case 4: Difference not divisible by k */ int arr4[] = {1, 2, 3}; printf("\nCase 4: Difference not divisible by k\n"); printf(" Array: "); printArray(arr4, 3); printf(" k = 5 → Result: %d (impossible)\n", minOpsOptimized(arr4, 3, 5)); /* Case 5: Large k */ int arr5[] = {0, 100}; printf("\nCase 5: Large k\n"); printf(" Array: "); printArray(arr5, 2); printf(" k = 100 → Result: %d\n", minOpsOptimized(arr5, 2, 100)); /* Case 6: All same element */ int arr6[] = {7, 7, 7, 7}; printf("\nCase 6: All same element\n"); printf(" Array: "); printArray(arr6, 4); printf(" k = 5 → Result: %d\n", minOpsOptimized(arr6, 4, 5)); /* Case 7: Zero values */ int arr7[] = {0, 0, 0}; printf("\nCase 7: Zero values\n"); printf(" Array: "); printArray(arr7, 3); printf(" k = 4 → Result: %d\n", minOpsOptimized(arr7, 3, 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 each element as target; O(n²).
  2. Optimized: Uses maximum as target; O(n).
  3. Feasibility: All differences from max must be divisible by k.
  4. Overflow protection: Uses long long for operation count.

Expected Output (sample run)

text

=== Minimum Increment by k to Make Equal === --- 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: [4, 4, 4, 2] k = 2 Naive: 1 Optimized: 1 Array 2: [4, 2, 6, 8] k = 3 Naive: -1 Optimized: -1 Array 3: [4, 7, 19, 16] k = 3 Naive: 10 Optimized: 10 Array 4 (already equal): [5, 5, 5] k = 4 Naive: 0 Optimized: 0 --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 5000) === Naive (O(n^2)): Result = 4500, Time = 12.345 ms Optimized (O(n)): Result = 4500, Time = 0.045 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [10] k = 3 → Result: 0 Case 2: Already equal Array: [5, 5] k = 3 → Result: 0 Case 3: k = 1 (always possible) Array: [1, 5, 3] k = 1 → Result: 5 Case 4: Difference not divisible by k Array: [1, 2, 3] k = 5 → Result: -1 (impossible) Case 5: Large k Array: [0, 100] k = 100 → Result: 1 Case 6: All same element Array: [7, 7, 7, 7] k = 5 → Result: 0 Case 7: Zero values Array: [0, 0, 0] k = 4 → Result: 0

Common Pitfalls, Edge Cases & Memory Considerations

1. Using a Target Smaller Than Maximum

Since we can only increment, the target must be ≥ max.

c

/* Wrong: trying a target smaller than max */ if (arr[j] > target) valid = 0;

Solution: Always use max as target in the optimized approach.

2. Not Checking Divisibility

If any difference from max isn’t divisible by k, the task is impossible.

c

if ((maxVal - x) % k != 0) return -1;

3. Integer Overflow in Operations

The total operations can exceed int for large arrays.

c

long long operations = 0; /* Use long long */

Solution: Use long long for operation count.

4. Negative k

k must be positive for the operation to make sense.

c

if (k <= 0) return -1;

5. Empty Array

Return -1 or handle gracefully.

c

if (n <= 0) return -1;

6. Single Element

A single element is trivially equal to itself; 0 operations.

c

int arr[] = {10}; /* Result: 0 */

7. Already Equal Array

All elements equal means 0 operations.

c

int arr[] = {5, 5, 5}; /* Result: 0 */

8. Zero Values

Zeros are valid; they can be incremented by k.

c

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

9. All Elements Same but Not Zero

Same as above; result is 0.

c

int arr[] = {7, 7, 7, 7}; /* Result: 0 */

10. Memory Considerations

  • Both approaches use O(1) extra space.
  • Naive is O(n²) time — avoid for large arrays.
  • Optimized is O(n) time — preferred.
  • Use long long for operation counts on large inputs.

Summary & Best Practices

Finding the minimum increment operations by k to make all array elements equal is elegantly solved by targeting the maximum element. If any difference isn’t divisible by k, the task is impossible.

Key Takeaways

  • Only increment is allowed → target must be ≥ max.
  • Target = max minimizes operations.
  • Check (max - arr[i]) % k == 0 for feasibility.
  • Count operations as Σ (max - arr[i]) / k.
  • Naive: O(n²); Optimized: O(n).
  • Return -1 if impossible.

Best Practices

  1. Use the optimized approach (target = max) for O(n).
  2. Check divisibility for all elements.
  3. Use long long for operation counts.
  4. Validate k > 0 before processing.
  5. Handle edge cases: empty, single element, already equal.
  6. Return -1 for impossible cases.
  7. Test with large k and negative values.
  8. Verify with the naive approach during testing.
  9. Document the greedy insight in comments.
  10. Profile for large arrays.

By mastering this problem, you’ll understand greedy optimization and modular arithmetic, skills applicable to many array and mathematical problems.

Learn to move all zeros to end of array in C: temp array, two-pass, and one-pass swap approaches. Master in-place O(n) array manipulation.

Move All Zeros to End of Array in C

Move All Zeros to End of Array in C: A Comprehensive Tutorial

Moving all zeros to the end of an array while maintaining the relative order of non-zero elements is a classic array manipulation problem. It tests your understanding of in-place operations, two-pointer techniques, and time-space trade-offs. This problem appears frequently in coding interviews and has practical applications in data compaction and memory management.

This tutorial provides an in-depth exploration of moving zeros to the end in C: naive temporary array approach, two-traversal method, one-traversal swap approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array of integers, rearrange it so that all zeros appear at the end while non-zero elements maintain their original relative order.

Example:

text

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

Why This Problem Matters

  • In-place manipulation: Teaches efficient array transformation.
  • Two-pointer technique: Foundation for many array algorithms.
  • Stable partitioning: Maintains relative order of elements.
  • Real-world applications: Data compaction, memory defragmentation.
  • Interview preparation: Very common coding interview question.

Key Insight

Use a “write pointer” (count) to track where the next non-zero element should be placed. As we scan the array, non-zero elements are moved to the front, and zeros naturally end up at the back.

Approaches Overview

ApproachTimeSpaceTraversals
Temporary arrayO(n)O(n)3
Two traversalsO(n)O(1)2
One traversal (swap)O(n)O(1)1

Syntax & Core Mechanics

Approach 1: Temporary Array (Naive)

Copy non-zero elements to a temporary array, fill the rest with zeros, then copy back.

c

#include <stdio.h> #include <stdlib.h> void pushZerosToEndTemp(int arr[], int n) { if (n <= 1) return; int *temp = malloc(n * sizeof(int)); if (temp == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } int j = 0; /* Copy non-zero elements to temp */ for (int i = 0; i < n; i++) { if (arr[i] != 0) { temp[j++] = arr[i]; } } /* Fill remaining with zeros */ while (j < n) { temp[j++] = 0; } /* Copy back */ for (int i = 0; i < n; i++) { arr[i] = temp[i]; } free(temp); } int main(void) { int arr[] = {1, 2, 0, 4, 3, 0, 5, 0}; int n = sizeof(arr) / sizeof(arr[0]); pushZerosToEndTemp(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

1 2 4 3 5 0 0 0

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

Approach 2: Two Traversals (In-Place)

Use a count pointer; copy non-zero elements to the front, then fill the rest with zeros.

c

#include <stdio.h> void pushZerosToEndTwoPass(int arr[], int n) { if (n <= 1) return; int count = 0; /* Move non-zero elements to the front */ for (int i = 0; i < n; i++) { if (arr[i] != 0) { arr[count++] = arr[i]; } } /* Fill remaining positions with zeros */ while (count < n) { arr[count++] = 0; } } int main(void) { int arr[] = {1, 2, 0, 4, 3, 0, 5, 0}; int n = sizeof(arr) / sizeof(arr[0]); pushZerosToEndTwoPass(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

1 2 4 3 5 0 0 0

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

Approach 3: One Traversal (Swap) — Optimal

Swap non-zero elements with the element at count, pushing zeros toward the end.

c

#include <stdio.h> void pushZerosToEnd(int arr[], int n) { if (n <= 1) return; int count = 0; for (int i = 0; i < n; i++) { if (arr[i] != 0) { /* Swap arr[i] with arr[count] */ int temp = arr[i]; arr[i] = arr[count]; arr[count] = temp; count++; } } } int main(void) { int arr[] = {1, 2, 0, 4, 3, 0, 5, 0}; int n = sizeof(arr) / sizeof(arr[0]); pushZerosToEnd(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

Expected output:

text

1 2 4 3 5 0 0 0

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

How it works:

  • count tracks the position where the next non-zero element should go.
  • When a non-zero is found, swap it with arr[count].
  • If arr[count] was 0, the zero moves to position i (later swapped forward again if needed).
  • Non-zero elements retain their relative order.

Full Practical Code Example with Explanation

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

Program: Move Zeros to End Comparison

c

/** * move_zeros_to_end.c * Demonstrates multiple approaches to move zeros to the end of an array. * Includes verification, timing, and edge case handling. */ #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 pushZerosTemp(int arr[], int n); void pushZerosTwoPass(int arr[], int n); void pushZerosOnePass(int arr[], int n); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); int arraysEqual(const int a[], const int b[], int n); void copyArray(const int src[], int dst[], int n); int verifyResult(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("=== Move Zeros to End Demo ===\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("\nOriginal: "); printArray(arr, n); int *copy = malloc(n * sizeof(int)); if (copy == NULL) break; copyArray(arr, copy, n); pushZerosTemp(copy, n); printf("Temp Array: "); printArray(copy, n); copyArray(arr, copy, n); pushZerosTwoPass(copy, n); printf("Two Pass: "); printArray(copy, n); copyArray(arr, copy, n); pushZerosOnePass(copy, n); printf("One Pass: "); printArray(copy, n); free(copy); 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: Temporary array */ void pushZerosTemp(int arr[], int n) { if (n <= 1) return; int *temp = malloc(n * sizeof(int)); if (temp == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } int j = 0; for (int i = 0; i < n; i++) { if (arr[i] != 0) { temp[j++] = arr[i]; } } while (j < n) { temp[j++] = 0; } for (int i = 0; i < n; i++) { arr[i] = temp[i]; } free(temp); } /* Approach 2: Two traversals */ void pushZerosTwoPass(int arr[], int n) { if (n <= 1) return; int count = 0; for (int i = 0; i < n; i++) { if (arr[i] != 0) { arr[count++] = arr[i]; } } while (count < n) { arr[count++] = 0; } } /* Approach 3: One traversal with swap */ void pushZerosOnePass(int arr[], int n) { if (n <= 1) return; int count = 0; for (int i = 0; i < n; i++) { if (arr[i] != 0) { int temp = arr[i]; arr[i] = arr[count]; arr[count] = temp; count++; } } } /* Verifies that all zeros are at end and non-zeros retain order */ int verifyResult(const int arr[], int n) { int seenZero = 0; for (int i = 0; i < n; i++) { if (arr[i] == 0) { seenZero = 1; } else if (seenZero) { return 0; /* Non-zero after zero: invalid */ } } 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"); } /* Compares arrays */ int arraysEqual(const int a[], const int b[], int n) { for (int i = 0; i < n; i++) { if (a[i] != b[i]) return 0; } return 1; } /* Copies array */ void copyArray(const int src[], int dst[], int n) { memcpy(dst, src, n * sizeof(int)); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== All Approaches ===\n"); int original[] = {1, 2, 0, 4, 3, 0, 5, 0}; int n = sizeof(original) / sizeof(original[0]); int expected[] = {1, 2, 4, 3, 5, 0, 0, 0}; int arr[MAX_SIZE]; printf("Original: [1, 2, 0, 4, 3, 0, 5, 0]\n"); printf("Expected: [1, 2, 4, 3, 5, 0, 0, 0]\n\n"); copyArray(original, arr, n); pushZerosTemp(arr, n); printf("Temp Array: "); printArray(arr, n); printf(" Verify: %s\n", arraysEqual(arr, expected, n) ? "PASS" : "FAIL"); copyArray(original, arr, n); pushZerosTwoPass(arr, n); printf("Two Pass: "); printArray(arr, n); printf(" Verify: %s\n", arraysEqual(arr, expected, n) ? "PASS" : "FAIL"); copyArray(original, arr, n); pushZerosOnePass(arr, n); printf("One Pass: "); printArray(arr, n); printf(" Verify: %s\n", arraysEqual(arr, expected, n) ? "PASS" : "FAIL"); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int size = 100000; int *original = malloc(size * sizeof(int)); int *arr = malloc(size * sizeof(int)); if (original == NULL || arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); free(original); free(arr); return; } /* Fill with half zeros, half non-zeros */ for (int i = 0; i < size; i++) { original[i] = (i % 2 == 0) ? 0 : (i + 1); } clock_t start, end; double timeTaken; /* Temp approach */ copyArray(original, arr, size); start = clock(); pushZerosTemp(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Temp Array: %.3f ms\n", timeTaken); /* Two pass */ copyArray(original, arr, size); start = clock(); pushZerosTwoPass(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Two Pass: %.3f ms\n", timeTaken); /* One pass */ copyArray(original, arr, size); start = clock(); pushZerosOnePass(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("One Pass: %.3f ms\n", timeTaken); free(original); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: No zeros */ int arr1[] = {10, 20, 30}; printf("Case 1: No zeros\n"); printf(" Before: "); printArray(arr1, 3); pushZerosOnePass(arr1, 3); printf(" After: "); printArray(arr1, 3); /* Case 2: All zeros */ int arr2[] = {0, 0, 0}; printf("\nCase 2: All zeros\n"); printf(" Before: "); printArray(arr2, 3); pushZerosOnePass(arr2, 3); printf(" After: "); printArray(arr2, 3); /* Case 3: Single element (zero) */ int arr3[] = {0}; printf("\nCase 3: Single zero\n"); printf(" Before: "); printArray(arr3, 1); pushZerosOnePass(arr3, 1); printf(" After: "); printArray(arr3, 1); /* Case 4: Single element (non-zero) */ int arr4[] = {5}; printf("\nCase 4: Single non-zero\n"); printf(" Before: "); printArray(arr4, 1); pushZerosOnePass(arr4, 1); printf(" After: "); printArray(arr4, 1); /* Case 5: Zeros at beginning */ int arr5[] = {0, 0, 1, 2, 3}; printf("\nCase 5: Zeros at beginning\n"); printf(" Before: "); printArray(arr5, 5); pushZerosOnePass(arr5, 5); printf(" After: "); printArray(arr5, 5); /* Case 6: Zeros at end (already correct) */ int arr6[] = {1, 2, 3, 0, 0}; printf("\nCase 6: Zeros already at end\n"); printf(" Before: "); printArray(arr6, 5); pushZerosOnePass(arr6, 5); printf(" After: "); printArray(arr6, 5); /* Case 7: Negative numbers */ int arr7[] = {-1, 0, -2, 0, -3}; printf("\nCase 7: Negative numbers\n"); printf(" Before: "); printArray(arr7, 5); pushZerosOnePass(arr7, 5); printf(" After: "); printArray(arr7, 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. Temp Array: Simple, but uses O(n) extra space.
  2. Two Pass: In-place, O(1) space, two traversals.
  3. One Pass: In-place, O(1) space, single traversal with swaps — most efficient.

Expected Output (sample run)

text

=== Move Zeros to End Demo === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === All Approaches === Original: [1, 2, 0, 4, 3, 0, 5, 0] Expected: [1, 2, 4, 3, 5, 0, 0, 0] Temp Array: [1, 2, 4, 3, 5, 0, 0, 0] Verify: PASS Two Pass: [1, 2, 4, 3, 5, 0, 0, 0] Verify: PASS One Pass: [1, 2, 4, 3, 5, 0, 0, 0] Verify: PASS --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 100000) === Temp Array: 1.234 ms Two Pass: 0.512 ms One Pass: 0.487 ms --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: No zeros Before: [10, 20, 30] After: [10, 20, 30] Case 2: All zeros Before: [0, 0, 0] After: [0, 0, 0] Case 3: Single zero Before: [0] After: [0] Case 4: Single non-zero Before: [5] After: [5] Case 5: Zeros at beginning Before: [0, 0, 1, 2, 3] After: [1, 2, 3, 0, 0] Case 6: Zeros already at end Before: [1, 2, 3, 0, 0] After: [1, 2, 3, 0, 0] Case 7: Negative numbers Before: [-1, 0, -2, 0, -3] After: [-1, -2, -3, 0, 0]

Common Pitfalls, Edge Cases & Memory Considerations

1. Losing Relative Order of Non-Zeros

Using an unstable partition (e.g., sorting or certain swap patterns) destroys the original order.

c

/* Wrong: swaps first non-zero with last, changing order */

Solution: Use the count-based approach that preserves order.

2. Overwriting Non-Zeros in Two-Pass

In two-pass, overwriting with arr[count++] = arr[i] is safe because count <= i always.

c

for (int i = 0; i < n; i++) { if (arr[i] != 0) { arr[count++] = arr[i]; /* Safe: count <= i */ } }

3. Forgetting to Fill Zeros

In two-pass, forgetting to fill remaining positions with zeros.

c

while (count < n) { arr[count++] = 0; /* Required */ }

4. Empty Array

Handle n <= 0 before processing.

c

if (n <= 1) return;

5. Single Element

A single element is trivially correct.

c

int arr[] = {0}; /* Already correct */

6. All Zeros

All-zero arrays remain unchanged.

c

int arr[] = {0, 0, 0};

7. No Zeros

Arrays without zeros remain unchanged.

c

int arr[] = {1, 2, 3};

8. Memory Leaks with Temp Array

Forgetting to free the temporary array.

c

int *temp = malloc(n * sizeof(int)); /* ... */ free(temp); /* Required */

9. Integer Overflow in count

Using int for count is fine for typical arrays; use size_t for very large arrays.

10. Memory Considerations

  • Temp Array: O(n) extra space.
  • Two Pass: O(1) extra space.
  • One Pass: O(1) extra space; preferred for memory efficiency.
  • No dynamic allocation in in-place approaches.

Summary & Best Practices

Moving all zeros to the end of an array is a fundamental in-place array manipulation problem. The one-pass swap approach is the most efficient, using O(n) time and O(1) space.

Key Takeaways

  • Maintain relative order of non-zero elements.
  • Use a count pointer to track the next non-zero position.
  • Temp array: O(n) time, O(n) space.
  • Two pass: O(n) time, O(1) space, two traversals.
  • One pass: O(n) time, O(1) space, single traversal with swaps.

Best Practices

  1. Use the one-pass swap approach for optimal efficiency.
  2. Preserve relative order of non-zero elements.
  3. Handle edge cases: empty, single element, all zeros, no zeros.
  4. Free dynamically allocated memory in the temp approach.
  5. Use size_t for very large arrays.
  6. Verify results with a helper function during testing.
  7. Validate input (array size, values).
  8. Document the approach with comments.
  9. Test with negative numbers and mixed values.
  10. Profile performance for large arrays.

By mastering this problem, you’ll understand the two-pointer technique and be prepared for related problems like removing duplicates, partitioning arrays, and stable sorting.

Learn to quickly find multiple left rotations of an array in C: naive, doubled array, and modular indexing approaches. Master O(n) per query rotation.

Quickly Find Multiple Left Rotations of Array in C

Quickly Find Multiple Left Rotations of an Array in C: A Comprehensive Tutorial

Given an array of size n and multiple values k representing the number of left rotations, the task is to efficiently find the rotated array for each query. Instead of rotating the array for each query separately (which would be O(n × q)), we can preprocess the array to answer each query in O(n) time (for printing) with O(1) overhead.

This tutorial provides an in-depth exploration of quickly finding multiple left rotations: naive repeated rotation, doubled array preprocessing, and space-optimized modular indexing approaches, with practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array and multiple queries k1, k2, k3, ..., kq, find the array after left rotating by each k positions.

Example:

text

Array: [1, 3, 5, 7, 9] Queries: k = 2, 3, 4 Left rotate by 2: [5, 7, 9, 1, 3] Left rotate by 3: [7, 9, 1, 3, 5] Left rotate by 4: [9, 1, 3, 5, 7]

Why This Problem Matters

  • Multiple queries: Efficient handling of repeated rotation requests.
  • Preprocessing: Trade-off between space and time.
  • Algorithm design: Demonstrates the power of precomputation.
  • Real-world applications: Circular buffers, data streaming, cyclic scheduling.
  • Interview preparation: Common coding interview question.

Key Insight

After k left rotations, the element at position i in the rotated array is arr[(i + k) % n]. This simple observation enables O(1) rotation lookup.

Approaches Overview

ApproachPreprocessingPer QuerySpace
Naive repeated rotationNoneO(n)O(n)
Doubled arrayO(n)O(n)O(n)
Modular indexingNoneO(n)O(1)

Syntax & Core Mechanics

Approach 1: Naive Repeated Rotation

For each query, rotate the array by k positions.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> void reverse(int arr[], int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } } void leftRotate(int arr[], int n, int k) { k %= n; if (k == 0) return; reverse(arr, 0, k - 1); reverse(arr, k, n - 1); reverse(arr, 0, n - 1); } void printArray(const int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); } int main(void) { int original[] = {1, 3, 5, 7, 9}; int n = sizeof(original) / sizeof(original[0]); int queries[] = {2, 3, 4, 6, 14}; int q = sizeof(queries) / sizeof(queries[0]); for (int i = 0; i < q; i++) { int *arr = malloc(n * sizeof(int)); if (arr == NULL) return 1; memcpy(arr, original, n * sizeof(int)); leftRotate(arr, n, queries[i]); printArray(arr, n); free(arr); } return 0; }

Expected output:

text

5 7 9 1 3 7 9 1 3 5 9 1 3 5 7 3 5 7 9 1 9 1 3 5 7

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

Approach 2: Doubled Array Preprocessing

Create a temporary array of size 2n containing two copies of the original array. For each query, print n elements starting from index k % n.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> void preprocess(const int arr[], int n, int temp[]) { for (int i = 0; i < n; i++) { temp[i] = arr[i]; temp[i + n] = arr[i]; } } void leftRotate(const int temp[], int n, int k) { int start = k % n; for (int i = start; i < start + n; i++) { printf("%d ", temp[i]); } printf("\n"); } int main(void) { int arr[] = {1, 3, 5, 7, 9}; int n = sizeof(arr) / sizeof(arr[0]); int *temp = malloc(2 * n * sizeof(int)); if (temp == NULL) return 1; preprocess(arr, n, temp); int queries[] = {2, 3, 4, 6, 14}; int q = sizeof(queries) / sizeof(queries[0]); for (int i = 0; i < q; i++) { leftRotate(temp, n, queries[i]); } free(temp); return 0; }

Expected output:

text

5 7 9 1 3 7 9 1 3 5 9 1 3 5 7 3 5 7 9 1 9 1 3 5 7

Preprocessing: O(n), Per query: O(n), Space: O(n)

Approach 3: Space-Optimized Modular Indexing

Without extra space, use modular arithmetic to access elements directly.

c

#include <stdio.h> void leftRotate(const int arr[], int n, int k) { k %= n; for (int i = k; i < k + n; i++) { printf("%d ", arr[i % n]); } printf("\n"); } int main(void) { int arr[] = {1, 3, 5, 7, 9}; int n = sizeof(arr) / sizeof(arr[0]); int queries[] = {2, 3, 4, 6, 14}; int q = sizeof(queries) / sizeof(queries[0]); for (int i = 0; i < q; i++) { leftRotate(arr, n, queries[i]); } return 0; }

Expected output:

text

5 7 9 1 3 7 9 1 3 5 9 1 3 5 7 3 5 7 9 1 9 1 3 5 7

Preprocessing: None, Per query: O(n), Space: O(1)


Full Practical Code Example with Explanation

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

Program: Multiple Left Rotations Demo

c

/** * multiple_left_rotations.c * Demonstrates approaches to quickly find multiple left rotations. * Compares naive, doubled array, and modular indexing 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 reverse(int arr[], int start, int end); void leftRotateInPlace(int arr[], int n, int k); void printRotatedNaive(const int arr[], int n, int k); void preprocessDoubled(const int arr[], int n, int temp[]); void printRotatedDoubled(const int temp[], int n, int k); void printRotatedModular(const int arr[], int n, int k); 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("=== Multiple Left Rotations Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Array with Multiple Queries\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, q; 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; } } if (readInteger("Enter number of queries: ", &q) != 0) { fprintf(stderr, "Invalid q.\n"); break; } printf("\nOriginal: "); printArray(arr, n); /* Preprocess for doubled approach */ int *temp = malloc(2 * n * sizeof(int)); if (temp == NULL) { fprintf(stderr, "Memory allocation failed.\n"); break; } preprocessDoubled(arr, n, temp); printf("\nResults:\n"); for (int i = 0; i < q; i++) { int k; char prompt[50]; snprintf(prompt, sizeof(prompt), "Query %d k: ", i + 1); if (readInteger(prompt, &k) != 0) { fprintf(stderr, "Invalid k.\n"); i--; continue; } printf(" k=%-4d | Naive: ", k); printRotatedNaive(arr, n, k); printf(" | Doubled: "); printRotatedDoubled(temp, n, k); printf(" | Modular: "); printRotatedModular(arr, n, k); } free(temp); 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; } /* Reverses array portion */ void reverse(int arr[], int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } } /* In-place left rotation using reversal algorithm */ void leftRotateInPlace(int arr[], int n, int k) { if (n <= 1) return; k %= n; if (k == 0) return; reverse(arr, 0, k - 1); reverse(arr, k, n - 1); reverse(arr, 0, n - 1); } /* Naive: rotate a copy and print */ void printRotatedNaive(const int arr[], int n, int k) { int *copy = malloc(n * sizeof(int)); if (copy == NULL) return; memcpy(copy, arr, n * sizeof(int)); leftRotateInPlace(copy, n, k); printArray(copy, n); free(copy); } /* Preprocess doubled array */ void preprocessDoubled(const int arr[], int n, int temp[]) { for (int i = 0; i < n; i++) { temp[i] = arr[i]; temp[i + n] = arr[i]; } } /* Print using doubled array */ void printRotatedDoubled(const int temp[], int n, int k) { int start = k % n; printf("["); for (int i = start; i < start + n; i++) { printf("%d", temp[i]); if (i < start + n - 1) printf(", "); } printf("]\n"); } /* Print using modular indexing */ void printRotatedModular(const int arr[], int n, int k) { k %= n; printf("["); for (int i = k; i < k + n; i++) { printf("%d", arr[i % n]); if (i < k + n - 1) printf(", "); } printf("]\n"); } /* 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 three approaches */ void demonstrateApproaches(void) { printf("\n=== All Approaches ===\n"); int arr[] = {1, 3, 5, 7, 9}; int n = sizeof(arr) / sizeof(arr[0]); int queries[] = {2, 3, 4, 6, 14}; int q = sizeof(queries) / sizeof(queries[0]); printf("Original: "); printArray(arr, n); printf("\n"); int *temp = malloc(2 * n * sizeof(int)); if (temp == NULL) return; preprocessDoubled(arr, n, temp); for (int i = 0; i < q; i++) { int k = queries[i]; printf("k = %d\n", k); printf(" Naive: "); printRotatedNaive(arr, n, k); printf(" Doubled: "); printRotatedDoubled(temp, n, k); printf(" Modular: "); printRotatedModular(arr, n, k); printf("\n"); } free(temp); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000, q = 10) ===\n"); int n = 100000; int q = 10; int *arr = malloc(n * sizeof(int)); int *temp = malloc(2 * n * sizeof(int)); if (arr == NULL || temp == NULL) { fprintf(stderr, "Memory allocation failed.\n"); free(arr); free(temp); return; } for (int i = 0; i < n; i++) arr[i] = i; preprocessDoubled(arr, n, temp); clock_t start, end; double timeTaken; /* Naive */ start = clock(); for (int i = 0; i < q; i++) { int *copy = malloc(n * sizeof(int)); memcpy(copy, arr, n * sizeof(int)); leftRotateInPlace(copy, n, i * 100); free(copy); } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (rotate copy): %.3f ms\n", timeTaken); /* Doubled */ start = clock(); for (int i = 0; i < q; i++) { int k = i * 100; int startIdx = k % n; volatile long sum = 0; for (int j = startIdx; j < startIdx + n; j++) { sum += temp[j]; } (void)sum; } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Doubled array: %.3f ms\n", timeTaken); /* Modular */ start = clock(); for (int i = 0; i < q; i++) { int k = i * 100; volatile long sum = 0; for (int j = k; j < k + n; j++) { sum += arr[j % n]; } (void)sum; } end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Modular indexing: %.3f ms\n", timeTaken); free(arr); free(temp); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int arr[] = {1, 3, 5, 7, 9}; int n = sizeof(arr) / sizeof(arr[0]); /* Case 1: k = 0 */ printf("Case 1: k = 0 (no rotation)\n"); printRotatedModular(arr, n, 0); /* Case 2: k = n */ printf("\nCase 2: k = n (full rotation, no change)\n"); printRotatedModular(arr, n, n); /* Case 3: k > n */ printf("\nCase 3: k = 14 > n = 5 (k %% n = 4)\n"); printRotatedModular(arr, n, 14); /* Case 4: Single element */ int arr2[] = {42}; printf("\nCase 4: Single element\n"); printRotatedModular(arr2, 1, 3); /* Case 5: k = n - 1 */ printf("\nCase 5: k = n - 1 = 4\n"); printRotatedModular(arr, n, 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: Copies and rotates for each query; O(n) per query.
  2. Doubled: Preprocesses 2n array; O(n) per query for printing.
  3. Modular: No preprocessing; O(n) per query for printing with O(1) space.

Expected Output (sample run)

text

=== Multiple Left Rotations Demo === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array with Multiple Queries 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === All Approaches === Original: [1, 3, 5, 7, 9] k = 2 Naive: [5, 7, 9, 1, 3] Doubled: [5, 7, 9, 1, 3] Modular: [5, 7, 9, 1, 3] k = 3 Naive: [7, 9, 1, 3, 5] Doubled: [7, 9, 1, 3, 5] Modular: [7, 9, 1, 3, 5] k = 4 Naive: [9, 1, 3, 5, 7] Doubled: [9, 1, 3, 5, 7] Modular: [9, 1, 3, 5, 7] k = 6 Naive: [3, 5, 7, 9, 1] Doubled: [3, 5, 7, 9, 1] Modular: [3, 5, 7, 9, 1] k = 14 Naive: [9, 1, 3, 5, 7] Doubled: [9, 1, 3, 5, 7] Modular: [9, 1, 3, 5, 7] --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: k = 0 (no rotation) [1, 3, 5, 7, 9] Case 2: k = n (full rotation, no change) [1, 3, 5, 7, 9] Case 3: k = 14 > n = 5 (k % n = 4) [9, 1, 3, 5, 7] Case 4: Single element [42] Case 5: k = n - 1 = 4 [9, 1, 3, 5, 7]

Common Pitfalls, Edge Cases & Memory Considerations

1. Not Handling k > n

Forgetting k %= n causes out-of-bounds access.

c

int start = k; /* BUG if k > n */ int start = k % n; /* Correct */

Solution: Always apply k %= n.

2. Negative k

Negative k causes implementation-defined modulo results.

c

k = -1; k %= n; /* Implementation-defined */

Solution: Normalize: k = ((k % n) + n) % n;

3. Empty Array

Accessing arr[0] on empty array causes issues.

c

if (n <= 0) return; /* Required */

4. Single Element

A single-element array remains unchanged for any k.

c

int arr[] = {42}; /* Rotated by any k: [42] */

5. Memory Leaks with Naive Approach

Forgetting to free the copy array.

c

int *copy = malloc(n * sizeof(int)); /* ... */ /* Missing free(copy); */

Solution: Always free dynamically allocated memory.

6. Overflow in i + n

For doubled array, ensure index stays within 2n.

c

for (int i = start; i < start + n; i++) /* start + n <= 2n */

7. Modular Indexing Correctness

arr[i % n] works correctly for non-negative i.

Solution: Ensure loop variable starts at k (non-negative after modulo).

8. Printing Format

Consistent formatting for readability.

c

printf("["); /* ... */ printf("]\n");

9. Multiple Queries with Same k

Cache results if same k repeats.

Solution: Use a hash map or sort queries for optimization.

10. Memory Considerations

  • Naive: O(n) extra space per query.
  • Doubled: O(n) extra space once.
  • Modular: O(1) extra space.
  • Modular approach is preferred for memory-constrained environments.

Summary & Best Practices

Quickly finding multiple left rotations of an array involves preprocessing or using modular arithmetic to answer each query efficiently. The modular indexing approach is the most space-efficient.

Key Takeaways

  • After k left rotations, element i becomes arr[(i + k) % n].
  • Naive: O(n) per query, O(n) space.
  • Doubled array: O(n) preprocessing, O(n) per query, O(n) space.
  • Modular: No preprocessing, O(n) per query, O(1) space.
  • Always apply k %= n to handle large k.

Best Practices

  1. Use modular indexing for space efficiency.
  2. Use doubled array when multiple queries benefit from preprocessing.
  3. Apply k %= n to normalize rotation count.
  4. Handle edge cases: empty array, single element, k = 0, k = n.
  5. Free dynamically allocated memory.
  6. Use size_t for array sizes where appropriate.
  7. Validate input (array size, k range).
  8. Consistent output formatting for readability.
  9. Test with k > n and negative values.
  10. Profile for very large arrays and many queries.

By mastering this problem, you’ll efficiently handle multiple rotation queries and understand the trade-offs between preprocessing and on-the-fly computation.