CoreTechED

Loading

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 find maximum sum of i*arr[i] across all array rotations in C. Master O(n) mathematical recurrence and pivot approaches with examples.

Maximum Sum of i*arr[i] Among Rotations in C

Maximum Sum of i*arr[i] Among All Rotations of an Array in C

Given an integer array, the task is to find the maximum value of the expression i * arr[i] (for all i from 0 to n-1) after rotating the array any number of times. This problem tests your understanding of array manipulation, mathematical optimization, and efficient algorithm design.

This tutorial provides an in-depth exploration of finding the maximum weighted sum across all rotations: naive approach, mathematical optimization, pivot-based approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

For an array arr[] of size n, we compute the weighted sum S = Σ (i * arr[i]) for each possible rotation. We need to find the maximum S across all rotations.

Example:

text

Array: [8, 3, 1, 2] Rotation 0: [8, 3, 1, 2] → S = 0*8 + 1*3 + 2*1 + 3*2 = 11 Rotation 1: [3, 1, 2, 8] → S = 0*3 + 1*1 + 2*2 + 3*8 = 29 ← Maximum Rotation 2: [1, 2, 8, 3] → S = 0*1 + 1*2 + 2*8 + 3*3 = 27 Rotation 3: [2, 8, 3, 1] → S = 0*2 + 1*8 + 2*3 + 3*1 = 17 Maximum S = 29

Why This Problem Matters

  • Algorithm optimization: Teaches O(n²) to O(n) transformation.
  • Mathematical insight: Deriving recurrence relations for rotations.
  • Coding interviews: Classic optimization problem.
  • Signal processing: Weighted sum computations.
  • Data analysis: Position-weighted aggregation.

Approaches Overview

ApproachTime ComplexitySpace ComplexityNotes
Naive nested loopsO(n²)O(1)Simple but slow
Mathematical recurrenceO(n)O(1)Optimal, general
Pivot-basedO(n)O(1)Only for sorted/rotated sorted arrays

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

For each rotation, compute the weighted sum from scratch.

c

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

Expected output:

text

Maximum sum: 29

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

Approach 2: Mathematical Recurrence (Optimal)

Instead of recomputing from scratch, derive the next rotation’s sum from the current one.

Key formula:

text

nextVal = currVal - (curSum - arr[i-1]) + arr[i-1] * (n - 1)

Derivation:

  • Shifting right by 1: each element’s coefficient decreases by 1, except the last element (which moves to index 0 with coefficient n-1).
  • currVal loses curSum - arr[i-1] (all elements except last move down by 1).
  • The last element arr[i-1] gains (n-1) instead of 0.

c

#include <stdio.h> #include <limits.h> int maxSumRecurrence(const int arr[], int n) { if (n <= 0) return 0; /* Compute total sum */ int curSum = 0; for (int i = 0; i < n; i++) { curSum += arr[i]; } /* Compute initial weighted sum */ int currVal = 0; for (int i = 0; i < n; i++) { currVal += i * arr[i]; } int maxVal = currVal; /* Compute for subsequent rotations */ for (int i = 1; i < n; i++) { int nextVal = currVal - (curSum - arr[i - 1]) + arr[i - 1] * (n - 1); currVal = nextVal; if (nextVal > maxVal) { maxVal = nextVal; } } return maxVal; } int main(void) { int arr[] = {8, 3, 1, 2}; int n = sizeof(arr) / sizeof(arr[0]); printf("Maximum sum: %d\n", maxSumRecurrence(arr, n)); return 0; }

Expected output:

text

Maximum sum: 29

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

Approach 3: Pivot-Based (Sorted/Rotated Sorted Arrays Only)

For sorted or rotated sorted arrays, the maximum sum occurs when the array is sorted in ascending order.

Steps:

  1. Find the pivot (where arr[i] > arr[i+1]).
  2. Calculate the offset diff = n - 1 - pivot.
  3. Compute the sum with adjusted indices.

c

#include <stdio.h> int findPivot(const int arr[], int n) { for (int i = 0; i < n; i++) { if (arr[i] > arr[(i + 1) % n]) { return i; } } return 0; } int maxSumPivot(const int arr[], int n) { int pivot = findPivot(arr, n); int diff = n - 1 - pivot; int result = 0; for (int i = 0; i < n; i++) { result += ((i + diff) % n) * arr[i]; } return result; } int main(void) { int arr[] = {8, 3, 1, 2}; int n = sizeof(arr) / sizeof(arr[0]); printf("Maximum sum: %d\n", maxSumPivot(arr, n)); return 0; }

Expected output:

text

Maximum sum: 29

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

Note: This approach only works for sorted or rotated sorted arrays.


Full Practical Code Example with Explanation

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

Program: Maximum Weighted Sum Across Rotations

c

/** * max_weighted_sum_rotations.c * Finds the maximum value of sum(i * arr[i]) across all rotations. * Compares naive, mathematical recurrence, and pivot-based 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 maxSumNaive(const int arr[], int n); int maxSumRecurrence(const int arr[], int n); int maxSumPivot(const int arr[], int n); int findPivot(const int arr[], int n); void rotateArray(int arr[], int n, int d); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); int isSortedOrRotatedSorted(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("=== Maximum i*arr[i] Across Rotations ===\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 resultNaive = maxSumNaive(arr, n); int resultRecur = maxSumRecurrence(arr, n); printf("Max sum (naive): %d\n", resultNaive); printf("Max sum (recurrence): %d\n", resultRecur); printf("Verification: %s\n", (resultNaive == resultRecur) ? "PASS" : "FAIL"); if (isSortedOrRotatedSorted(arr, n)) { int resultPivot = maxSumPivot(arr, n); printf("Max sum (pivot): %d\n", resultPivot); printf("Pivot verification: %s\n", (resultPivot == resultNaive) ? "PASS" : "FAIL"); } else { printf("(Pivot approach not applicable — array not sorted/rotated sorted)\n"); } break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Naive approach: O(n^2) */ int maxSumNaive(const int arr[], int n) { if (n <= 0) return 0; int maxSum = INT_MIN; for (int i = 0; i < n; i++) { int sum = 0; for (int j = 0; j < n; j++) { int index = (i + j) % n; sum += j * arr[index]; } if (sum > maxSum) { maxSum = sum; } } return maxSum; } /* Mathematical recurrence: O(n) */ int maxSumRecurrence(const int arr[], int n) { if (n <= 0) return 0; /* Compute total sum */ int curSum = 0; for (int i = 0; i < n; i++) { curSum += arr[i]; } /* Compute initial weighted sum */ int currVal = 0; for (int i = 0; i < n; i++) { currVal += i * arr[i]; } int maxVal = currVal; /* Compute for subsequent rotations */ for (int i = 1; i < n; i++) { int nextVal = currVal - (curSum - arr[i - 1]) + arr[i - 1] * (n - 1); currVal = nextVal; if (nextVal > maxVal) { maxVal = nextVal; } } return maxVal; } /* Find pivot for rotated sorted array */ int findPivot(const int arr[], int n) { for (int i = 0; i < n; i++) { if (arr[i] > arr[(i + 1) % n]) { return i; } } return 0; } /* Pivot-based approach: O(n), only for sorted/rotated sorted arrays */ int maxSumPivot(const int arr[], int n) { if (n <= 0) return 0; int pivot = findPivot(arr, n); int diff = n - 1 - pivot; int result = 0; for (int i = 0; i < n; i++) { result += ((i + diff) % n) * arr[i]; } return result; } /* Checks if array is sorted or rotated sorted */ int isSortedOrRotatedSorted(const int arr[], int n) { int descents = 0; for (int i = 0; i < n; i++) { if (arr[i] > arr[(i + 1) % n]) { descents++; } } return descents <= 1; } /* Rotates array right by d positions */ void rotateArray(int arr[], int n, int d) { if (n <= 1) return; d %= n; if (d == 0) return; int *temp = malloc(n * sizeof(int)); if (temp == NULL) return; for (int i = 0; i < n; i++) { temp[(i + d) % n] = arr[i]; } for (int i = 0; i < n; i++) { arr[i] = temp[i]; } free(temp); } /* 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, 3, 1, 2}; int n1 = sizeof(arr1) / sizeof(arr1[0]); printf("\nArray 1: "); printArray(arr1, n1); printf("Naive: %d\n", maxSumNaive(arr1, n1)); printf("Recurrence: %d\n", maxSumRecurrence(arr1, n1)); printf("Pivot: %d\n", maxSumPivot(arr1, n1)); int arr2[] = {1, 2, 3}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); printf("Naive: %d\n", maxSumNaive(arr2, n2)); printf("Recurrence: %d\n", maxSumRecurrence(arr2, n2)); printf("Pivot: %d\n", maxSumPivot(arr2, n2)); int arr3[] = {10, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int n3 = sizeof(arr3) / sizeof(arr3[0]); printf("\nArray 3: "); printArray(arr3, n3); printf("Naive: %d\n", maxSumNaive(arr3, n3)); printf("Recurrence: %d\n", maxSumRecurrence(arr3, n3)); printf("Pivot: %d\n", maxSumPivot(arr3, n3)); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 10000) ===\n"); int size = 10000; int *arr = malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Create a random-ish array */ srand(42); for (int i = 0; i < size; i++) { arr[i] = rand() % 1000; } clock_t start, end; double timeTaken; /* Naive approach */ start = clock(); int naiveResult = maxSumNaive(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (O(n^2)): Result = %d, Time = %.3f ms\n", naiveResult, timeTaken); /* Recurrence approach */ start = clock(); int recurResult = maxSumRecurrence(arr, size); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Recurrence (O(n)): Result = %d, Time = %.3f ms\n", recurResult, timeTaken); printf("\nVerification: %s\n", (naiveResult == recurResult) ? "PASS" : "FAIL"); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Single element */ int arr1[] = {5}; printf("Case 1: Single element\n"); printf(" Array: "); printArray(arr1, 1); printf(" Max sum: %d\n", maxSumRecurrence(arr1, 1)); /* Case 2: Two elements */ int arr2[] = {10, 20}; printf("\nCase 2: Two elements\n"); printf(" Array: "); printArray(arr2, 2); printf(" Naive: %d\n", maxSumNaive(arr2, 2)); printf(" Recurrence: %d\n", maxSumRecurrence(arr2, 2)); /* Case 3: All equal elements */ int arr3[] = {7, 7, 7, 7}; printf("\nCase 3: All equal elements\n"); printf(" Array: "); printArray(arr3, 4); printf(" Naive: %d\n", maxSumNaive(arr3, 4)); printf(" Recurrence: %d\n", maxSumRecurrence(arr3, 4)); /* Case 4: Already sorted */ int arr4[] = {1, 2, 3, 4, 5}; printf("\nCase 4: Already sorted (best rotation is 0)\n"); printf(" Array: "); printArray(arr4, 5); printf(" Naive: %d\n", maxSumNaive(arr4, 5)); printf(" Recurrence: %d\n", maxSumRecurrence(arr4, 5)); /* Case 5: Reverse sorted */ int arr5[] = {5, 4, 3, 2, 1}; printf("\nCase 5: Reverse sorted\n"); printf(" Array: "); printArray(arr5, 5); printf(" Naive: %d\n", maxSumNaive(arr5, 5)); printf(" Recurrence: %d\n", maxSumRecurrence(arr5, 5)); /* Case 6: Large values (overflow check) */ int arr6[] = {100000, 200000, 300000}; printf("\nCase 6: Large values\n"); printf(" Array: "); printArray(arr6, 3); printf(" Recurrence: %d (watch for overflow)\n", maxSumRecurrence(arr6, 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: Computes weighted sum for each rotation from scratch. O(n²).
  2. Recurrence: Derives next rotation’s sum from current using a formula. O(n).
  3. Pivot: Only for sorted/rotated sorted arrays; finds pivot and computes optimal sum. O(n).

Expected Output (sample run)

text

=== Maximum i*arr[i] Across Rotations === --- 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: [8, 3, 1, 2] Naive: 29 Recurrence: 29 Pivot: 29 Array 2: [1, 2, 3] Naive: 8 Recurrence: 8 Pivot: 8 Array 3: [10, 1, 2, 3, 4, 5, 6, 7, 8, 9] Naive: 330 Recurrence: 330 Pivot: 330 --- Menu --- ... Enter choice: 3 === Performance Comparison (n = 10000) === Naive (O(n^2)): Result = 299850000, Time = 452.341 ms Recurrence (O(n)): Result = 299850000, Time = 0.087 ms Verification: PASS --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: Single element Array: [5] Max sum: 0 Case 2: Two elements Array: [10, 20] Naive: 20 Recurrence: 20 Case 3: All equal elements Array: [7, 7, 7, 7] Naive: 42 Recurrence: 42 Case 4: Already sorted (best rotation is 0) Array: [1, 2, 3, 4, 5] Naive: 40 Recurrence: 40 Case 5: Reverse sorted Array: [5, 4, 3, 2, 1] Naive: 30 Recurrence: 30 Case 6: Large values Array: [100000, 200000, 300000] Recurrence: 800000 (watch for overflow)

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow

Weighted sums can grow quickly.

c

int arr[] = {100000, 200000, 300000}; /* Sum may overflow int */

Solution: Use long long for sum calculations.

2. Empty Array

Return 0 for empty arrays.

c

if (n <= 0) return 0;

3. Single Element

A single-element array has only one rotation with sum 0.

c

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

4. Negative Values

Negative values work correctly with the recurrence formula.

c

int arr[] = {-8, 3, -1, 2}; /* Recurrence still valid */

5. Pivot Approach Misuse

Pivot approach only works for sorted or rotated sorted arrays.

c

int arr[] = {3, 1, 4, 1, 5}; // Not sorted /* Pivot approach gives wrong answer */

Solution: Only use pivot approach when applicable.

6. Incorrect Recurrence Formula

Wrong formula produces incorrect results.

c

/* Correct: nextVal = currVal - (curSum - arr[i-1]) + arr[i-1] * (n-1) */

Solution: Verify formula with small examples.

7. Off-by-One in Pivot Calculation

Incorrect pivot index causes wrong offset.

c

int diff = n - 1 - pivot; /* Correct formula */

8. Modulo Operator with Negative Values

(i + diff) % n is safe when i + diff >= 0.

Solution: Ensure indices stay non-negative.

9. Memory Leaks

Free dynamically allocated memory.

c

free(temp); /* Always free */

10. Memory Considerations

  • Both recurrence and pivot approaches use O(1) extra space.
  • Naive approach uses O(1) extra space.
  • No dynamic allocation required for core algorithms.
  • Use long long for large weighted sums.

Summary & Best Practices

Finding the maximum of i * arr[i] across all rotations is a classic optimization problem. The mathematical recurrence reduces time from O(n²) to O(n), making it suitable for large arrays.

Key Takeaways

  • Weighted sum S = Σ (i * arr[i]) changes with rotation.
  • Naive approach: O(n²) time.
  • Mathematical recurrence: O(n) time, O(1) space.
  • Pivot approach: O(n) time for sorted/rotated sorted arrays.
  • Recurrence formula: nextVal = currVal - (curSum - arr[i-1]) + arr[i-1] * (n-1).

Best Practices

  1. Use the recurrence approach for general arrays (O(n)).
  2. Use the pivot approach only for sorted/rotated sorted arrays.
  3. Use long long for sum calculations to prevent overflow.
  4. Handle empty arrays and single-element arrays.
  5. Verify with naive approach during testing.
  6. Document the recurrence formula in comments.
  7. Test with negative values and edge cases.
  8. Validate input (array size, values).
  9. Free dynamically allocated memory.
  10. Profile performance for large arrays.

By mastering this problem, you’ll understand the power of mathematical optimization and be prepared for similar rotation-based problems.

Learn to find the first repeating element in an array using brute force, hash table, and index marking. Master O(n) algorithms with edge cases

First Repeating Element in Array: C Complete Guide

First Repeating Element in an Array: A Comprehensive Tutorial

Finding the first repeating element in an array is a classic algorithmic problem that tests understanding of arrays, hash tables, and time-space trade-offs. The goal is to identify the element that appears more than once and whose first occurrence (or second occurrence) comes earliest in the array.

This tutorial provides an in-depth exploration of finding the first repeating element in C: problem understanding, multiple approaches with different time/space complexities, implementation, edge cases, and best practices.


Conceptual Introduction & Fundamentals

What is a Repeating Element?

A repeating element is a value that appears more than once in an array. The “first repeating element” can be defined in two ways:

  1. First by first occurrence: The element whose first occurrence has the smallest index among all repeating elements.
  2. First by second occurrence: The element whose second occurrence has the smallest index (the first element to repeat as we scan left to right).

Example:

text

Array: [10, 5, 3, 4, 3, 5, 6] Repeating elements: 5 (appears twice), 3 (appears twice) First repeating element (by first occurrence): 5 (first occurs at index 1) First repeating element (by second occurrence): 3 (second occurrence at index 4, earlier than 5's second occurrence at index 5)

Problem Statement

Given an array of integers, find the first repeating element. Return the element value and/or its index. If no element repeats, indicate that.

Why This Problem Matters

  • Algorithm design: Demonstrates brute force vs. optimized approaches.
  • Hash table usage: Teaches efficient frequency counting.
  • Time-space trade-offs: O(n²) time vs. O(n) time with O(n) space.
  • Interview preparation: Common coding interview question.
  • Data analysis: Finding duplicates in datasets.

Approaches Overview

ApproachTime ComplexitySpace ComplexityNotes
Brute force (nested loops)O(n²)O(1)Simple but slow
SortingO(n log n)O(1) or O(n)Changes original order
Hash table (frequency count)O(n)O(n)Fast, uses extra space
Index markingO(n)O(1)Only works for specific constraints

Syntax & Core Mechanics

Approach 1: Brute Force (Nested Loops)

Check each element against all following elements to find duplicates.

c

#include <stdio.h> #include <stdbool.h> int findFirstRepeatingBrute(const int arr[], int n, int *element, int *index) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] == arr[j]) { *element = arr[i]; *index = i; return 0; /* Found */ } } } return -1; /* Not found */ } int main(void) { int arr[] = {10, 5, 3, 4, 3, 5, 6}; int n = sizeof(arr) / sizeof(arr[0]); int element, index; if (findFirstRepeatingBrute(arr, n, &element, &index) == 0) { printf("First repeating element: %d at index %d\n", element, index); } else { printf("No repeating element found.\n"); } return 0; }

Expected output:

text

First repeating element: 5 at index 1

Time complexity: O(n²)

Approach 2: Hash Table (Frequency Count)

Use a hash table or array to count frequencies, then find the first element with count > 1.

c

#include <stdio.h> #include <stdlib.h> #include <limits.h> /* Hash table entry */ typedef struct { int key; int count; int firstIndex; } HashEntry; /* Simple hash table */ typedef struct { HashEntry *entries; int size; int capacity; } HashTable; /* Hash function */ int hashFunction(int key, int capacity) { return (key % capacity + capacity) % capacity; /* Handle negative keys */ } /* Initialize hash table */ HashTable *createHashTable(int capacity) { HashTable *ht = malloc(sizeof(HashTable)); if (ht == NULL) return NULL; ht->entries = malloc(capacity * sizeof(HashEntry)); if (ht->entries == NULL) { free(ht); return NULL; } ht->size = 0; ht->capacity = capacity; /* Initialize entries */ for (int i = 0; i < capacity; i++) { ht->entries[i].key = INT_MIN; /* Sentinel for empty */ ht->entries[i].count = 0; ht->entries[i].firstIndex = -1; } return ht; } /* Insert or update key in hash table */ void hashTableInsert(HashTable *ht, int key, int index) { int hash = hashFunction(key, ht->capacity); int i = hash; /* Linear probing */ while (ht->entries[i].key != INT_MIN && ht->entries[i].key != key) { i = (i + 1) % ht->capacity; if (i == hash) return; /* Table full */ } if (ht->entries[i].key == INT_MIN) { ht->entries[i].key = key; ht->entries[i].count = 1; ht->entries[i].firstIndex = index; ht->size++; } else { ht->entries[i].count++; } } /* Get count for a key */ int hashTableGetCount(const HashTable *ht, int key) { int hash = hashFunction(key, ht->capacity); int i = hash; while (ht->entries[i].key != INT_MIN) { if (ht->entries[i].key == key) { return ht->entries[i].count; } i = (i + 1) % ht->capacity; if (i == hash) break; } return 0; } /* Get first index for a key */ int hashTableGetFirstIndex(const HashTable *ht, int key) { int hash = hashFunction(key, ht->capacity); int i = hash; while (ht->entries[i].key != INT_MIN) { if (ht->entries[i].key == key) { return ht->entries[i].firstIndex; } i = (i + 1) % ht->capacity; if (i == hash) break; } return -1; } /* Free hash table */ void freeHashTable(HashTable *ht) { if (ht != NULL) { free(ht->entries); free(ht); } } /* Find first repeating element using hash table */ int findFirstRepeatingHash(const int arr[], int n, int *element, int *index) { HashTable *ht = createHashTable(n * 2); if (ht == NULL) return -1; /* Insert all elements */ for (int i = 0; i < n; i++) { hashTableInsert(ht, arr[i], i); } /* Find first element with count > 1 */ for (int i = 0; i < n; i++) { if (hashTableGetCount(ht, arr[i]) > 1) { *element = arr[i]; *index = i; freeHashTable(ht); return 0; } } freeHashTable(ht); return -1; } int main(void) { int arr[] = {10, 5, 3, 4, 3, 5, 6}; int n = sizeof(arr) / sizeof(arr[0]); int element, index; if (findFirstRepeatingHash(arr, n, &element, &index) == 0) { printf("First repeating element: %d at index %d\n", element, index); } else { printf("No repeating element found.\n"); } return 0; }

Expected output:

text

First repeating element: 5 at index 1

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

Approach 3: Index Marking (For Constrained Arrays)

If array elements are between 0 and n-1, use index marking to find duplicates in O(n) time and O(1) space.

c

#include <stdio.h> #include <stdlib.h> /* Works only if all elements are in range [0, n-1] */ int findFirstRepeatingIndexMarking(int arr[], int n, int *element) { for (int i = 0; i < n; i++) { int absVal = abs(arr[i]); if (arr[absVal] < 0) { /* Already marked: this is a repeating element */ *element = absVal; return 0; } /* Mark as negative */ arr[absVal] = -arr[absVal]; } return -1; } int main(void) { int arr[] = {1, 2, 3, 1, 4, 2}; int n = sizeof(arr) / sizeof(arr[0]); int element; if (findFirstRepeatingIndexMarking(arr, n, &element) == 0) { printf("First repeating element: %d\n", element); } else { printf("No repeating element found.\n"); } return 0; }

Expected output:

text

First repeating element: 1

Note: This modifies the original array and only works for non-negative elements in range [0, n-1].


Full Practical Code Example with Explanation

This program demonstrates multiple approaches to find the first repeating element in an array, with performance comparison.

Program: First Repeating Element Finder

c

/** * first_repeating_element.c * Finds the first repeating element in an array using multiple approaches. * Compares brute force, hash table, and index marking methods. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <limits.h> #define MAX_LINE_LEN 100 #define MAX_ARRAY_SIZE 1000 /* Hash table for efficient frequency counting */ typedef struct { int key; int count; int firstIndex; int isOccupied; } HashSlot; typedef struct { HashSlot *slots; int capacity; } SimpleHashTable; /* Function prototypes */ int findFirstRepeatingBrute(const int arr[], int n, int *element, int *index); int findFirstRepeatingHash(const int arr[], int n, int *element, int *index); int findFirstRepeatingIndexMarking(int arr[], int n, int *element); SimpleHashTable *createHashTable(int capacity); void hashInsert(SimpleHashTable *ht, int key, int index); int hashGetCount(SimpleHashTable *ht, int key); void freeHashTable(SimpleHashTable *ht); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproach(const char *name, int result, clock_t start, clock_t end); int main(void) { int choice; char buffer[MAX_LINE_LEN]; /* Test arrays */ int arr1[] = {10, 5, 3, 4, 3, 5, 6}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int arr2[] = {1, 2, 3, 4, 5}; int n2 = sizeof(arr2) / sizeof(arr2[0]); int arr3[] = {1, 1, 1, 1}; int n3 = sizeof(arr3) / sizeof(arr3[0]); int arr4[] = {}; int n4 = 0; int arr5[] = {5}; int n5 = sizeof(arr5) / sizeof(arr5[0]); printf("=== First Repeating Element Finder ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate with Sample Arrays\n"); printf("2. Enter Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. 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: { printf("\n=== Sample Arrays ===\n"); printf("\nArray 1: "); printArray(arr1, n1); int element, index; if (findFirstRepeatingHash(arr1, n1, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } printf("\nArray 2 (no repeats): "); printArray(arr2, n2); if (findFirstRepeatingHash(arr2, n2, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } printf("\nArray 3 (all same): "); printArray(arr3, n3); if (findFirstRepeatingHash(arr3, n3, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } printf("\nArray 4 (empty): "); printArray(arr4, n4); if (findFirstRepeatingHash(arr4, n4, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } printf("\nArray 5 (single): "); printArray(arr5, n5); if (findFirstRepeatingHash(arr5, n5, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } break; } case 2: { int n; if (readInteger("Enter array size (0-1000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 0 || n > MAX_ARRAY_SIZE) { printf("Size must be 0-%d.\n", MAX_ARRAY_SIZE); break; } int *customArr = malloc(n * sizeof(int)); if (customArr == NULL && n > 0) { fprintf(stderr, "Memory allocation failed.\n"); 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, &customArr[i]) != 0) { fprintf(stderr, "Invalid element.\n"); free(customArr); break; } } printf("\nCustom array: "); printArray(customArr, n); int element, index; if (findFirstRepeatingHash(customArr, n, &element, &index) == 0) { printf("First repeating: %d at index %d\n", element, index); } else { printf("No repeating element.\n"); } free(customArr); break; } case 3: { printf("\n=== Performance Comparison ===\n"); int largeSize = 5000; int *largeArr = malloc(largeSize * sizeof(int)); if (largeArr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); break; } /* Fill with values, ensure some repeats */ srand(42); /* Fixed seed for reproducibility */ for (int i = 0; i < largeSize; i++) { largeArr[i] = rand() % 100; /* Values 0-99, many repeats */ } clock_t start, end; int element, index; /* Brute force */ start = clock(); int bruteResult = findFirstRepeatingBrute(largeArr, largeSize, &element, &index); end = clock(); demonstrateApproach("Brute force", bruteResult, start, end); /* Hash table */ start = clock(); int hashResult = findFirstRepeatingHash(largeArr, largeSize, &element, &index); end = clock(); demonstrateApproach("Hash table", hashResult, start, end); free(largeArr); break; } case 4: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Brute force: O(n^2) */ int findFirstRepeatingBrute(const int arr[], int n, int *element, int *index) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] == arr[j]) { *element = arr[i]; *index = i; return 0; } } } return -1; } /* Creates a hash table for frequency counting */ SimpleHashTable *createHashTable(int capacity) { SimpleHashTable *ht = malloc(sizeof(SimpleHashTable)); if (ht == NULL) return NULL; ht->slots = malloc(capacity * sizeof(HashSlot)); if (ht->slots == NULL) { free(ht); return NULL; } ht->capacity = capacity; for (int i = 0; i < capacity; i++) { ht->slots[i].key = 0; ht->slots[i].count = 0; ht->slots[i].firstIndex = -1; ht->slots[i].isOccupied = 0; } return ht; } /* Hash function with linear probing */ void hashInsert(SimpleHashTable *ht, int key, int index) { int hash = (key % ht->capacity + ht->capacity) % ht->capacity; int i = hash; while (ht->slots[i].isOccupied && ht->slots[i].key != key) { i = (i + 1) % ht->capacity; if (i == hash) return; /* Table full */ } if (!ht->slots[i].isOccupied) { ht->slots[i].key = key; ht->slots[i].count = 1; ht->slots[i].firstIndex = index; ht->slots[i].isOccupied = 1; } else { ht->slots[i].count++; } } /* Gets count for a key */ int hashGetCount(SimpleHashTable *ht, int key) { int hash = (key % ht->capacity + ht->capacity) % ht->capacity; int i = hash; while (ht->slots[i].isOccupied) { if (ht->slots[i].key == key) { return ht->slots[i].count; } i = (i + 1) % ht->capacity; if (i == hash) break; } return 0; } /* Frees hash table */ void freeHashTable(SimpleHashTable *ht) { if (ht != NULL) { free(ht->slots); free(ht); } } /* Hash table approach: O(n) time, O(n) space */ int findFirstRepeatingHash(const int arr[], int n, int *element, int *index) { SimpleHashTable *ht = createHashTable(n * 2 + 10); if (ht == NULL) return -1; for (int i = 0; i < n; i++) { hashInsert(ht, arr[i], i); } for (int i = 0; i < n; i++) { if (hashGetCount(ht, arr[i]) > 1) { *element = arr[i]; *index = i; freeHashTable(ht); return 0; } } freeHashTable(ht); return -1; } /* Index marking: O(n) time, O(1) space, modifies array */ int findFirstRepeatingIndexMarking(int arr[], int n, int *element) { for (int i = 0; i < n; i++) { int absVal = abs(arr[i]); if (arr[absVal] < 0) { *element = absVal; return 0; } arr[absVal] = -arr[absVal]; } return -1; } /* Displays approach result and time */ void demonstrateApproach(const char *name, int result, clock_t start, clock_t end) { double timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("%-20s: %s, Time = %.3f ms\n", name, result == 0 ? "Found" : "Not found", timeTaken); } /* Prints an array */ void printArray(const int arr[], int n) { if (n == 0) { printf("[]"); return; } printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]"); } /* 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. Brute Force: Nested loops compare each element with all subsequent elements. O(n²).
  2. Hash Table: Counts frequencies, then finds first element with count > 1. O(n) time, O(n) space.
  3. Index Marking: Marks visited indices as negative; detects repeats. O(n) time, O(1) space, but modifies array.

Expected Output (sample run)

text

=== First Repeating Element Finder === --- Menu --- 1. Demonstrate with Sample Arrays 2. Enter Custom Array 3. Performance Comparison 4. Exit Enter choice: 1 === Sample Arrays === Array 1: [10, 5, 3, 4, 3, 5, 6] First repeating: 5 at index 1 Array 2 (no repeats): [1, 2, 3, 4, 5] No repeating element. Array 3 (all same): [1, 1, 1, 1] First repeating: 1 at index 0 Array 4 (empty): [] No repeating element. Array 5 (single): [5] No repeating element. --- Menu --- ... Enter choice: 3 === Performance Comparison === Brute force : Found, Time = 45.123 ms Hash table : Found, Time = 0.234 ms

Common Pitfalls, Edge Cases & Memory Considerations

1. Empty Array

Accessing arr[0] on an empty array is undefined behavior.

c

if (arr[0] == arr[1]) { // BUG: empty array }

Solution: Check n == 0 first.

2. Single Element Array

A single element cannot repeat. The function should return “not found.”

c

int arr[] = {5}; findFirstRepeating(arr, 1); // Should return -1

Solution: Ensure loop bounds handle n < 2 correctly.

3. Negative Numbers

Index marking approach fails with negative numbers because arr[absVal] may index out of bounds.

c

int arr[] = {-1, 2, -1}; // abs(-1) = 1, arr[1] is valid, but arr[-1] would be invalid

Solution: Use hash table approach for negative numbers, or offset values.

4. Elements Out of Range for Index Marking

Index marking requires all elements in [0, n-1]. Elements outside this range cause out-of-bounds access.

c

int arr[] = {100, 200, 100}; // arr[100] is out of bounds

Solution: Validate range before using index marking, or use hash table.

5. Large Element Values

Hash table with fixed-size array may not handle large element values without a proper hash function.

Solution: Use a proper hash table with collision handling, or use dynamic allocation.

6. All Elements Distinct

If no elements repeat, the function must correctly return “not found.”

c

int arr[] = {1, 2, 3, 4, 5}; findFirstRepeating(arr, 5); // Should return -1

Solution: Initialize result to -1 and only update when a repeat is found.

7. Memory Leaks in Hash Table

Forgetting to free the hash table causes memory leaks.

c

SimpleHashTable *ht = createHashTable(100); /* ... use ht ... */ /* Missing freeHashTable(ht); */

Solution: Always free dynamically allocated memory.

8. Integer Overflow in Hash Function

Large keys can overflow when computing hash.

c

int hash = key % capacity; // Overflow for very large key?

Solution: Use (key % capacity + capacity) % capacity to handle negatives safely.

9. Index Marking Modifies Original Array

Index marking changes the array by negating elements, making it unsuitable for read-only arrays.

Solution: Document that the function modifies the array, or create a copy.

10. Time-Space Trade-off

Brute force uses O(1) space but O(n²) time; hash table uses O(n) space but O(n) time. Choose based on constraints.

Solution: For small arrays, brute force is fine. For large arrays, use hash table.


Summary & Best Practices

Finding the first repeating element in an array is a classic problem that demonstrates the trade-off between time and space complexity. The hash table approach provides O(n) time, making it the preferred solution for large arrays.

Key Takeaways

  • First repeating element can be defined by first occurrence or second occurrence.
  • Brute force: O(n²) time, O(1) space.
  • Hash table: O(n) time, O(n) space.
  • Index marking: O(n) time, O(1) space (with constraints).
  • Handle edge cases: empty array, single element, no repeats, all same.
  • Choose approach based on array size and element range.

Best Practices

  1. Use hash table approach for general cases with O(n) time.
  2. Use brute force for very small arrays (n < 100) for simplicity.
  3. Use index marking only when elements are in [0, n-1] and modification is acceptable.
  4. Always check for empty arrays before processing.
  5. Return a clear sentinel (e.g., -1) when no repeating element exists.
  6. Free dynamically allocated memory in hash table approaches.
  7. Document the approach’s constraints (e.g., modifies array, element range).
  8. Test with diverse inputs: negatives, large values, duplicates at different positions.
  9. Consider using built-in sorting if order doesn’t matter.
  10. Profile performance for large arrays to choose the right algorithm.

By mastering this problem, you’ll understand fundamental algorithm design principles and be prepared for variations like finding all duplicates, first non-repeating element, and frequency counting.