CoreTechED

Loading

Learn to find missing and repeating numbers in an array in C: visited, marking, math equations, and XOR approaches. Master O(n) time with O(1) space.

Missing and Repeating Number in Array: C Guide

Missing and Repeating in an Array in C: A Comprehensive Tutorial

Given an unsorted array of size n containing elements from the range 1 to n, exactly one number is missing and another appears twice. The task is to find both the duplicate (repeating) and the missing number. This is a classic algorithmic problem that tests your understanding of array manipulation, mathematical equations, bitwise operations, and time-space trade-offs.

This tutorial provides an in-depth exploration of finding both the missing and repeating numbers in C: visited array approach, array marking, two math equations, XOR approach, practical implementation, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] of size n with elements from the range [1, n]:

  • Exactly one number appears twice (repeating).
  • Exactly one number is missing.
  • All other numbers appear exactly once.

Find both the repeating and missing numbers.

Examples:

text

Input: [3, 1, 3] Output: [3, 2] (3 repeats, 2 is missing) Input: [4, 3, 6, 2, 1, 1] Output: [1, 5] (1 repeats, 5 is missing)

Why This Problem Matters

  • Multiple approaches: Demonstrates trade-offs between time and space.
  • Mathematical reasoning: Sum and sum-of-squares equations.
  • Bitwise operations: XOR-based grouping technique.
  • Interview preparation: Very common coding interview question.
  • Real-world applications: Data integrity, duplicate detection, sequence validation.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Visited arrayO(n)O(n)
Array marking (negation)O(n)O(1)
Two math equationsO(n)O(1)
XORO(n)O(1)

Syntax & Core Mechanics

Approach 1: Visited Array (Frequency Count)

Use a frequency array to count occurrences.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int *freq = calloc(n + 1, sizeof(int)); if (freq == NULL) { fprintf(stderr, "Memory allocation failed.\n"); *repeating = -1; *missing = -1; return; } for (int i = 0; i < n; i++) { freq[arr[i]]++; } *repeating = -1; *missing = -1; for (int i = 1; i <= n; i++) { if (freq[i] == 0) *missing = i; else if (freq[i] == 2) *repeating = i; } free(freq); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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

Approach 2: Array Marking (Negation)

Use the input array itself to track visited elements by negating values.

c

#include <stdio.h> #include <stdlib.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int *copy = malloc(n * sizeof(int)); if (copy == NULL) { *repeating = -1; *missing = -1; return; } memcpy(copy, arr, n * sizeof(int)); *repeating = -1; /* Mark visited by negating */ for (int i = 0; i < n; i++) { int val = abs(copy[i]); if (copy[val - 1] > 0) { copy[val - 1] = -copy[val - 1]; } else { *repeating = val; /* Already visited */ } } *missing = -1; for (int i = 0; i < n; i++) { if (copy[i] > 0) { *missing = i + 1; break; } } free(copy); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

Time: O(n), Space: O(n) for copy (O(1) if input can be modified)

Approach 3: Two Math Equations

Use sum and sum-of-squares formulas.

Derivation:

text

Let x = missing, y = repeating. Sum equation: x - y = S_expected - S_actual (call it s) Squares equation: x² - y² = SS_expected - SS_actual (call it ssq) From squares: (x - y)(x + y) = ssq → (x + y) = ssq / s Solving: x = (s + ssq/s) / 2 y = x - s

c

#include <stdio.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { long long s = (long long)n * (n + 1) / 2; long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; for (int i = 0; i < n; i++) { s -= arr[i]; ssq -= (long long)arr[i] * arr[i]; } *missing = (int)((s + ssq / s) / 2); *repeating = (int)(*missing - s); } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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

Approach 4: XOR

XOR all array elements and numbers 1..n to get x ^ y. Use a set bit to divide into groups.

c

#include <stdio.h> void findTwoElement(const int arr[], int n, int *repeating, int *missing) { int xorVal = 0; for (int i = 0; i < n; i++) { xorVal ^= arr[i]; xorVal ^= (i + 1); } int setBit = xorVal & ~(xorVal - 1); int x = 0, y = 0; for (int i = 0; i < n; i++) { if (arr[i] & setBit) x ^= arr[i]; else y ^= arr[i]; if ((i + 1) & setBit) x ^= (i + 1); else y ^= (i + 1); } /* Determine which is which */ int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { *missing = x; *repeating = y; } else { *missing = y; *repeating = x; } } int main(void) { int arr[] = {3, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int repeating, missing; findTwoElement(arr, n, &repeating, &missing); printf("Repeating: %d, Missing: %d\n", repeating, missing); return 0; }

Expected output:

text

Repeating: 3, Missing: 2

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


Full Practical Code Example with Explanation

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

Program: Missing and Repeating Finder

c

/** * missing_repeating.c * Finds both missing and repeating numbers in an array. * Compares visited array, marking, math, and XOR approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ void findVisited(const int arr[], int n, int *repeating, int *missing); void findMarking(const int arr[], int n, int *repeating, int *missing); void findMath(const int arr[], int n, int *repeating, int *missing); void findXOR(const int arr[], int n, int *repeating, int *missing); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demonstrateApproaches(void); void performanceComparison(void); void demonstrateEdgeCases(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Missing and Repeating Number Finder ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Custom Array\n"); printf("3. Performance Comparison\n"); printf("4. Edge Cases\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateApproaches(); break; case 2: { int arr[MAX_SIZE]; int n; if (readInteger("Enter array size (1-100000): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 1 || n > MAX_SIZE) { printf("Size must be 1-%d.\n", MAX_SIZE); break; } printf("Enter %d elements (1..%d, one missing, one repeated):\n", n, n); for (int i = 0; i < n; i++) { char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &arr[i]) != 0) { fprintf(stderr, "Invalid input.\n"); n = i; break; } } printf("\nArray: "); printArray(arr, n); int rep, mis; findVisited(arr, n, &rep, &mis); printf("Visited: Repeating = %d, Missing = %d\n", rep, mis); findMarking(arr, n, &rep, &mis); printf("Marking: Repeating = %d, Missing = %d\n", rep, mis); findMath(arr, n, &rep, &mis); printf("Math: Repeating = %d, Missing = %d\n", rep, mis); findXOR(arr, n, &rep, &mis); printf("XOR: Repeating = %d, Missing = %d\n", rep, mis); break; } case 3: performanceComparison(); break; case 4: demonstrateEdgeCases(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Approach 1: Visited array */ void findVisited(const int arr[], int n, int *repeating, int *missing) { int *freq = calloc(n + 1, sizeof(int)); if (freq == NULL) { *repeating = -1; *missing = -1; return; } for (int i = 0; i < n; i++) { if (arr[i] >= 1 && arr[i] <= n) { freq[arr[i]]++; } } *repeating = -1; *missing = -1; for (int i = 1; i <= n; i++) { if (freq[i] == 0) *missing = i; else if (freq[i] == 2) *repeating = i; } free(freq); } /* Approach 2: Array marking (negation) */ void findMarking(const int arr[], int n, int *repeating, int *missing) { int *copy = malloc(n * sizeof(int)); if (copy == NULL) { *repeating = -1; *missing = -1; return; } memcpy(copy, arr, n * sizeof(int)); *repeating = -1; for (int i = 0; i < n; i++) { int val = abs(copy[i]); if (val >= 1 && val <= n) { if (copy[val - 1] > 0) { copy[val - 1] = -copy[val - 1]; } else { *repeating = val; } } } *missing = -1; for (int i = 0; i < n; i++) { if (copy[i] > 0) { *missing = i + 1; break; } } free(copy); } /* Approach 3: Math equations */ void findMath(const int arr[], int n, int *repeating, int *missing) { long long s = (long long)n * (n + 1) / 2; long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; for (int i = 0; i < n; i++) { s -= arr[i]; ssq -= (long long)arr[i] * arr[i]; } if (s == 0) { *repeating = -1; *missing = -1; return; } *missing = (int)((s + ssq / s) / 2); *repeating = (int)(*missing - s); } /* Approach 4: XOR */ void findXOR(const int arr[], int n, int *repeating, int *missing) { int xorVal = 0; for (int i = 0; i < n; i++) { xorVal ^= arr[i]; xorVal ^= (i + 1); } int setBit = xorVal & ~(xorVal - 1); int x = 0, y = 0; for (int i = 0; i < n; i++) { if (arr[i] & setBit) x ^= arr[i]; else y ^= arr[i]; if ((i + 1) & setBit) x ^= (i + 1); else y ^= (i + 1); } int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { *missing = x; *repeating = y; } else { *missing = y; *repeating = x; } } /* Prints array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demonstrates all approaches */ void demonstrateApproaches(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {3, 1, 3}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int rep, mis; printf("\nArray 1: "); printArray(arr1, n1); findVisited(arr1, n1, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMarking(arr1, n1, &rep, &mis); printf(" Marking: Rep=%d, Mis=%d\n", rep, mis); findMath(arr1, n1, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr1, n1, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); int arr2[] = {4, 3, 6, 2, 1, 1}; int n2 = sizeof(arr2) / sizeof(arr2[0]); printf("\nArray 2: "); printArray(arr2, n2); findVisited(arr2, n2, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMarking(arr2, n2, &rep, &mis); printf(" Marking: Rep=%d, Mis=%d\n", rep, mis); findMath(arr2, n2, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr2, n2, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); } /* Performance comparison */ void performanceComparison(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return; } /* Fill 1..n, replace one with duplicate of another */ int missing = n / 2; int repeat = n / 3; for (int i = 0; i < n; i++) { arr[i] = i + 1; } /* Replace missing with repeat */ for (int i = 0; i < n; i++) { if (arr[i] == missing) { arr[i] = repeat; break; } } clock_t start, end; double timeTaken; int rep, mis; start = clock(); findVisited(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Visited: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findMarking(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Marking: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findMath(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Math: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); start = clock(); findXOR(arr, n, &rep, &mis); end = clock(); timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("XOR: Rep=%d, Mis=%d, Time = %.3f ms\n", rep, mis, timeTaken); free(arr); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); int rep, mis; /* Case 1: n = 2 */ int arr1[] = {1, 1}; printf("Case 1: n=2, [1, 1]\n"); findVisited(arr1, 2, &rep, &mis); printf(" Visited: Rep=%d, Mis=%d\n", rep, mis); findMath(arr1, 2, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr1, 2, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 2: Repeat at start */ int arr2[] = {1, 1, 2, 3}; printf("\nCase 2: Repeat at start\n"); printf(" Array: "); printArray(arr2, 4); findMath(arr2, 4, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr2, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 3: Repeat at end */ int arr3[] = {1, 2, 3, 3}; printf("\nCase 3: Repeat at end\n"); printf(" Array: "); printArray(arr3, 4); findMath(arr3, 4, &rep, &mis); printf(" Math: Rep=%d, Mis=%d\n", rep, mis); findXOR(arr3, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 4: Missing 1 */ int arr4[] = {2, 2, 3, 4}; printf("\nCase 4: Missing 1\n"); printf(" Array: "); printArray(arr4, 4); findXOR(arr4, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); /* Case 5: Missing n */ int arr5[] = {1, 1, 2, 3}; printf("\nCase 5: Missing n (4)\n"); printf(" Array: "); printArray(arr5, 4); findXOR(arr5, 4, &rep, &mis); printf(" XOR: Rep=%d, Mis=%d\n", rep, mis); } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Approaches

  1. Visited: Uses a frequency array; O(n) time, O(n) space.
  2. Marking: Negates visited indices in a copy; O(n) time, O(n) space (or O(1) if input is mutable).
  3. Math: Uses sum and sum-of-squares equations; O(n) time, O(1) space.
  4. XOR: Divides elements into groups by a set bit; O(n) time, O(1) space.

Expected Output (sample run)

text

=== Missing and Repeating Number Finder === --- Menu --- 1. Demonstrate All Approaches 2. Custom Array 3. Performance Comparison 4. Edge Cases 5. Exit Enter choice: 1 === Sample Arrays === Array 1: [3, 1, 3] Visited: Rep=3, Mis=2 Marking: Rep=3, Mis=2 Math: Rep=3, Mis=2 XOR: Rep=3, Mis=2 Array 2: [4, 3, 6, 2, 1, 1] Visited: Rep=1, Mis=5 Marking: Rep=1, Mis=5 Math: Rep=1, Mis=5 XOR: Rep=1, Mis=5 --- Menu --- ... Enter choice: 4 === Edge Cases === Case 1: n=2, [1, 1] Visited: Rep=1, Mis=2 Math: Rep=1, Mis=2 XOR: Rep=1, Mis=2 Case 2: Repeat at start Array: [1, 1, 2, 3] Math: Rep=1, Mis=4 XOR: Rep=1, Mis=4 Case 3: Repeat at end Array: [1, 2, 3, 3] Math: Rep=3, Mis=4 XOR: Rep=3, Mis=4 Case 4: Missing 1 Array: [2, 2, 3, 4] XOR: Rep=2, Mis=1 Case 5: Missing n (4) Array: [1, 1, 2, 3] XOR: Rep=1, Mis=4

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Overflow in Math Approach

n × (n+1) × (2n+1) / 6 overflows quickly for large n.

c

long long ssq = (long long)n * (n + 1) * (2 * n + 1) / 6; /* Cast */

Solution: Use long long and cast before multiplication.

2. Division by Zero in Math Approach

If s == 0 (no missing/repeating, invalid input), division fails.

c

if (s == 0) { /* handle invalid input */ }

3. Array Marking Modifies Input

The marking approach requires modifying the array (or a copy).

Solution: Copy the array first if preservation is needed.

4. XOR Determination of Missing vs Repeating

XOR gives two values; must determine which is which by counting occurrences.

c

int xCount = 0; for (int i = 0; i < n; i++) { if (arr[i] == x) xCount++; } if (xCount == 0) { /* x is missing, y is repeating */ }

5. Zero Values in Array

Problem specifies values in [1, n]; zero values are invalid input.

6. n = 1 Edge Case

If n = 1, the array has one element, and that element must be the repeating one (missing 1).

c

int arr[] = {1}; /* Repeating = 1, Missing = 2 ... but n=1 means values in [1,1] */ /* Actually the problem requires n >= 2 */

Solution: Handle n == 1 explicitly if needed.

7. Duplicate Values Beyond Two

Problem guarantees exactly one repeat; other inputs break the algorithm.

8. Hash Array Sizing

Ensure frequency array size is n + 1 (indices 0..n).

9. Memory Leaks

Free dynamically allocated frequency arrays.

10. Memory Considerations

  • Visited: O(n) extra space.
  • Marking: O(n) extra space (or O(1) if mutating input).
  • Math: O(1) extra space.
  • XOR: O(1) extra space — preferred for memory.
  • Use long long for math approach.

Summary & Best Practices

Finding the missing and repeating numbers in an array is solved elegantly using XOR (O(n) time, O(1) space) or math equations (O(n) time, O(1) space). Both approaches avoid extra memory.

Key Takeaways

  • Array has n elements from range 1..n.
  • One element repeats; one is missing.
  • Visited: O(n) time, O(n) space.
  • Marking: O(n) time, O(1)-O(n) space.
  • Math: O(n) time, O(1) space (watch for overflow).
  • XOR: O(n) time, O(1) space — preferred.

Best Practices

  1. Use XOR for O(n) time and O(1) space without overflow.
  2. Use math for simplicity when n is small.
  3. Use long long for math to prevent overflow.
  4. Use visited array for clarity when memory permits.
  5. Verify with multiple approaches during testing.
  6. Handle edge cases: n=2, missing/repeat at boundaries.
  7. Free dynamically allocated memory.
  8. Validate input (values in [1, n], exactly one repeat).
  9. Test with n=2 and larger values.
  10. Profile for large arrays.

By mastering this problem, you’ll understand multiple algorithmic techniques and their trade-offs, applicable to many array and bitwise problems.

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