CoreTechED

Loading

Learn to find minimum subsets with distinct elements in C: the answer equals the max frequency. Compare naive, sorted, and hash O(n) approaches.

Minimum Subsets with Distinct Elements in C

Minimum Subsets with Distinct Elements in C: A Comprehensive Tutorial

Given an array of integers, partition its elements into the minimum number of subsets so that no subset contains duplicates. At first glance, this looks like a combinatorial optimization problem—but there’s an elegant insight that collapses it into a single statistic: the maximum frequency of any element.

This tutorial explores the reasoning, three implementation approaches (naive O(n²), sort O(n log n), hash O(n)), and everything you need to write robust C code that solves it.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[], distribute its elements into the fewest possible subsets, where each subset must contain distinct elements (no duplicates inside a subset).

Examples:

text

Input: arr = [1, 2, 3, 4] Output: 1 (all distinct → one subset) Input: arr = [1, 2, 3, 3] Output: 2 ({1,2,3} and {3})

Why This Problem Matters

  • Pattern recognition: Reduces a seemingly complex problem to one statistic.
  • Frequency counting: Foundation for many counting and partition problems.
  • Combinatorial reasoning: Pigeonhole principle in action.
  • Interview preparation: Common coding interview question.
  • Real-world applications: Task scheduling, resource allocation, exam seating.

The Key Insight

Every subset can contain at most one copy of any value. If a value v appears f(v) times, we need at least f(v) subsets to place all its copies. The most frequent value determines the minimum:

text

answer = max over all values of frequency

Why the Answer is Sufficient

Suppose the maximum frequency is m for value x. Create m empty subsets. Distribute the elements round-robin (each value goes to a different subset each time it appears). Since every value appears at most m times, each value fits into distinct subsets, so no subset gets a duplicate.

Approaches Overview

ApproachTimeSpace
Naive nested loopsO(n²)O(1)
SortingO(n log n)O(1) or O(n)
Hash mapO(n) averageO(n)

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

For each element, count its occurrences by scanning the array.

c

#include <stdio.h> int minSubsetsNaive(const int arr[], int n) { if (n <= 0) return 0; int maxCount = 0; for (int i = 0; i < n; i++) { int count = 0; for (int j = 0; j < n; j++) { if (arr[i] == arr[j]) count++; } if (count > maxCount) maxCount = count; } return maxCount; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", minSubsetsNaive(arr, n)); return 0; }

Expected output:

text

3

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

Approach 2: Sort + Linear Scan

After sorting, equal elements are adjacent. Track the run length.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } int minSubsetsSorted(const int arr_in[], int n) { if (n <= 0) return 0; if (n == 1) return 1; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return -1; memcpy(arr, arr_in, (size_t)n * sizeof(int)); qsort(arr, n, sizeof(int), compareInts); int maxCount = 1; int currCount = 1; for (int i = 1; i < n; i++) { if (arr[i] == arr[i - 1]) currCount++; else currCount = 1; if (currCount > maxCount) maxCount = currCount; } free(arr); return maxCount; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", minSubsetsSorted(arr, n)); return 0; }

Expected output:

text

3

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

Approach 3: Hash Map (Optimal)

Insert into a hash map (key = value, value = frequency). Return the maximum frequency.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 4096 typedef struct { int *keys; int *counts; int *occupied; int capacity; } FreqMap; static unsigned int hashFunc(int key, int cap) { return ((unsigned int)key) % (unsigned int)cap; } FreqMap *createMap(int cap) { FreqMap *m = malloc(sizeof(FreqMap)); if (m == NULL) return NULL; m->keys = malloc((size_t)cap * sizeof(int)); m->counts = calloc((size_t)cap, sizeof(int)); m->occupied = calloc((size_t)cap, sizeof(int)); if (m->keys == NULL || m->counts == NULL || m->occupied == NULL) { free(m->keys); free(m->counts); free(m->occupied); free(m); return NULL; } m->capacity = cap; return m; } void freeMap(FreqMap *m) { if (m) { free(m->keys); free(m->counts); free(m->occupied); free(m); } } void increment(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) { m->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) return; } m->keys[idx] = key; m->counts[idx] = 1; m->occupied[idx] = 1; } int minSubsetsHash(const int arr[], int n) { if (n <= 0) return 0; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return -1; for (int i = 0; i < n; i++) increment(m, arr[i]); int maxCount = 0; for (int i = 0; i < m->capacity; i++) { if (m->occupied[i] && m->counts[i] > maxCount) { maxCount = m->counts[i]; } } freeMap(m); return maxCount; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", minSubsetsHash(arr, n)); return 0; }

Expected output:

text

3

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

Round-Robin Distribution Proof

If the maximum frequency is m for value x:

  1. Create m empty subsets.
  2. For each distinct value v appearing f(v) ≤ m times, assign its f(v) copies to f(v) distinct subsets.
  3. Each subset gets at most one copy of v.

This yields a valid partition with exactly m subsets, and no smaller count can work (pigeonhole).


Full Practical Code Example with Explanation

This program implements all three approaches and verifies they agree on multiple datasets.

c

/** * min_subsets.c * Minimum number of subsets with distinct elements. * Compares naive, sorted, and hash map approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define TABLE_SIZE 262144 /* ============================================================ * FreqMap * ============================================================ */ typedef struct { int *keys; int *counts; int *occupied; int capacity; } FreqMap; static unsigned int hashFunc(int key, int cap) { return ((unsigned int)key) % (unsigned int)cap; } FreqMap *createMap(int cap) { FreqMap *m = malloc(sizeof(FreqMap)); if (m == NULL) return NULL; m->keys = malloc((size_t)cap * sizeof(int)); m->counts = calloc((size_t)cap, sizeof(int)); m->occupied = calloc((size_t)cap, sizeof(int)); if (m->keys == NULL || m->counts == NULL || m->occupied == NULL) { free(m->keys); free(m->counts); free(m->occupied); free(m); return NULL; } m->capacity = cap; return m; } void freeMap(FreqMap *m) { if (m) { free(m->keys); free(m->counts); free(m->occupied); free(m); } } void increment(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) { m->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) return; } m->keys[idx] = key; m->counts[idx] = 1; m->occupied[idx] = 1; } /* ============================================================ * Approaches * ============================================================ */ int minSubsetsNaive(const int arr[], int n) { if (n <= 0) return 0; int maxCount = 0; for (int i = 0; i < n; i++) { int cnt = 0; for (int j = 0; j < n; j++) if (arr[i] == arr[j]) cnt++; if (cnt > maxCount) maxCount = cnt; } return maxCount; } static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } int minSubsetsSorted(const int arr_in[], int n) { if (n <= 0) return 0; if (n == 1) return 1; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return -1; memcpy(arr, arr_in, (size_t)n * sizeof(int)); qsort(arr, n, sizeof(int), compareInts); int maxCount = 1, currCount = 1; for (int i = 1; i < n; i++) { if (arr[i] == arr[i - 1]) currCount++; else currCount = 1; if (currCount > maxCount) maxCount = currCount; } free(arr); return maxCount; } int minSubsetsHash(const int arr[], int n) { if (n <= 0) return 0; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return -1; for (int i = 0; i < n; i++) increment(m, arr[i]); int maxCount = 0; for (int i = 0; i < m->capacity; i++) { if (m->occupied[i] && m->counts[i] > maxCount) { maxCount = m->counts[i]; } } freeMap(m); return maxCount; } /* ============================================================ * Utility * ============================================================ */ 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("]"); } static void demoBasic(void) { printf("\n=== Sample Arrays ===\n"); int a1[] = {1, 2, 3, 4}; int a2[] = {1, 2, 3, 3}; int a3[] = {40, 50, 30, 40, 50, 30, 30}; int a4[] = {1, 2, 1, 2, 3, 3, 2, 2}; struct { int *arr; int n; const char *label; } tests[] = { {a1, 4, "[1,2,3,4]"}, {a2, 4, "[1,2,3,3]"}, {a3, 7, "[40,50,30,40,50,30,30]"}, {a4, 8, "[1,2,1,2,3,3,2,2]"} }; int nt = sizeof(tests) / sizeof(tests[0]); for (int t = 0; t < nt; t++) { printf("\nTest %d: %s\n", t + 1, tests[t].label); printf(" Array: "); printArray(tests[t].arr, tests[t].n); printf("\n"); int r1 = minSubsetsNaive(tests[t].arr, tests[t].n); int r2 = minSubsetsSorted(tests[t].arr, tests[t].n); int r3 = minSubsetsHash(tests[t].arr, tests[t].n); printf(" Naive: %d\n Sorted: %d\n Hash: %d\n", r1, r2, r3); printf(" Verify: %s\n", (r1 == r2 && r2 == r3) ? "PASS" : "FAIL"); } printf("\n"); } static void demoEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Empty */ printf("Empty array: naive=%d, sorted=%d, hash=%d\n", minSubsetsNaive(NULL, 0), minSubsetsSorted(NULL, 0), minSubsetsHash(NULL, 0)); /* Single */ int a1[] = {7}; printf("Single element: %d / %d / %d\n", minSubsetsNaive(a1, 1), minSubsetsSorted(a1, 1), minSubsetsHash(a1, 1)); /* All distinct */ int a2[] = {1, 2, 3, 4, 5}; printf("All distinct: %d / %d / %d (expect 1)\n", minSubsetsNaive(a2, 5), minSubsetsSorted(a2, 5), minSubsetsHash(a2, 5)); /* All same */ int a3[] = {9, 9, 9, 9}; printf("All same: %d / %d / %d (expect 4)\n", minSubsetsNaive(a3, 4), minSubsetsSorted(a3, 4), minSubsetsHash(a3, 4)); /* One dominant value */ int a4[] = {1, 1, 1, 1, 2, 3}; printf("One dominant: %d / %d / %d (expect 4)\n", minSubsetsNaive(a4, 6), minSubsetsSorted(a4, 6), minSubsetsHash(a4, 6)); /* Negatives */ int a5[] = {-1, -1, -2, -2, -3}; printf("Negatives: %d / %d / %d (expect 2)\n", minSubsetsNaive(a5, 5), minSubsetsSorted(a5, 5), minSubsetsHash(a5, 5)); /* Two values alternating */ int a6[] = {5, 6, 5, 6, 5, 6}; printf("Alternating: %d / %d / %d (expect 3)\n", minSubsetsNaive(a6, 6), minSubsetsSorted(a6, 6), minSubsetsHash(a6, 6)); /* Zeros */ int a7[] = {0, 0, 0, 1, 1}; printf("Zeros: %d / %d / %d (expect 3)\n", minSubsetsNaive(a7, 5), minSubsetsSorted(a7, 5), minSubsetsHash(a7, 5)); } static void performanceTest(void) { printf("\n=== Performance Comparison ===\n"); /* Naive small */ int smallN = 3000; int *sa = malloc((size_t)smallN * sizeof(int)); if (sa == NULL) return; srand(42); for (int i = 0; i < smallN; i++) sa[i] = rand() % 500; clock_t start, end; double t; start = clock(); int r1 = minSubsetsNaive(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): result=%d, time=%.3f ms\n", smallN, r1, t); start = clock(); int r2 = minSubsetsSorted(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted (n=%d): result=%d, time=%.3f ms\n", smallN, r2, t); start = clock(); int r3 = minSubsetsHash(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", smallN, r3, t); printf("Verify: %s\n", (r1 == r2 && r2 == r3) ? "PASS" : "FAIL"); free(sa); /* Large hash */ int largeN = 5000000; int *la = malloc((size_t)largeN * sizeof(int)); if (la == NULL) return; for (int i = 0; i < largeN; i++) la[i] = rand() % 100000; start = clock(); int r4 = minSubsetsHash(la, largeN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", largeN, r4, t); free(la); } static 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; } static void demoInteractive(void) { int n; if (readInteger("Enter array size (0-10000): ", &n) != 0 || n < 0 || n > 10000) { printf("Invalid size.\n"); return; } int *arr = NULL; if (n > 0) { arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return; for (int i = 0; i < n; i++) { char p[40]; snprintf(p, sizeof(p), " arr[%d]: ", i); if (readInteger(p, &arr[i]) != 0) { printf("Invalid value.\n"); free(arr); return; } } } printf("\nMinimum subsets: %d\n", minSubsetsHash(arr, n)); free(arr); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Minimum Subsets with Distinct Elements Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Edge Cases\n"); printf("3. Performance Comparison\n"); printf("4. Interactive\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: demoBasic(); break; case 2: demoEdgeCases(); break; case 3: performanceTest(); break; case 4: demoInteractive(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice.\n"); break; } } return EXIT_SUCCESS; }

Explanation of the Approaches

  1. Naive: For each element, count its frequency with a nested loop; track the maximum. O(n²).
  2. Sorted: Sort; scan runs of equal elements. O(n log n).
  3. Hash: Increment frequency in a hash map; find the maximum. O(n) average.

Expected Output (sample run)

text

=== Sample Arrays === Test 1: [1,2,3,4] Array: [1, 2, 3, 4] Naive: 1 Sorted: 1 Hash: 1 Verify: PASS Test 2: [1,2,3,3] Array: [1, 2, 3, 3] Naive: 2 Sorted: 2 Hash: 2 Verify: PASS Test 3: [40,50,30,40,50,30,30] Array: [40, 50, 30, 40, 50, 30, 30] Naive: 3 Sorted: 3 Hash: 3 Verify: PASS Test 4: [1,2,1,2,3,3,2,2] Array: [1, 2, 1, 2, 3, 3, 2, 2] Naive: 4 Sorted: 4 Hash: 4 Verify: PASS === Edge Cases === Empty array: naive=0, sorted=0, hash=0 Single element: 1 / 1 / 1 All distinct: 1 / 1 / 1 (expect 1) All same: 4 / 4 / 4 (expect 4) One dominant: 4 / 4 / 4 (expect 4) Negatives: 2 / 2 / 2 (expect 2) Alternating: 3 / 3 / 3 (expect 3) Zeros: 3 / 3 / 3 (expect 3)

Common Pitfalls, Edge Cases & Memory Considerations

1. Confusing “Distinct” with “Unique Values”

Each subset must have distinct elements within itself; elements may repeat across subsets.

2. Empty Array

Return 0 (no subsets needed). Guard explicitly.

3. Single Element

Exactly 1 subset is required.

4. All Same Values

The answer equals n (each subset holds one copy).

5. All Distinct

The answer is 1 (one subset holds everything).

6. Large Frequencies

The count fits in int for arrays up to INT_MAX elements; use size_t for very large arrays.

7. Hash Table Sizing

Undersized table → probe failures.

Solution: Size ≥ 2× number of distinct values (or ≥ 2× n).

8. Negative Keys

Cast to unsigned int in the hash function.

9. Sorting Mutates Input

Copy before sorting if preservation needed.

10. Memory Considerations

  • Naive: O(1) extra space.
  • Sorted: O(n) for the copy.
  • Hash: O(n) for the frequency map.
  • Free all dynamic allocations.

Summary & Best Practices

The minimum-subsets problem is a beautiful case of “reduce complexity to one statistic.” The answer is simply the maximum frequency of any value in the array.

Key Takeaways

  • Answer = maximum frequency of any element.
  • All distinct → 1 subset.
  • All same → n subsets.
  • Naive: O(n²) — educational.
  • Sorted: O(n log n) — memory-friendly.
  • Hash: O(n) average — optimal.

Best Practices

  1. Use the hash map for O(n) average.
  2. Use sorting when memory is tight.
  3. Track the maximum frequency during the scan.
  4. Handle empty arrays (0) and single elements (1).
  5. Size hash tables ≥ 2× distinct values.
  6. Cast negative keys to unsigned int.
  7. Copy inputs before sorting if preservation matters.
  8. Free allocated memory.
  9. Test edge cases: empty, single, all-same, all-distinct, negatives.
  10. Profile for large inputs.

By mastering this problem, you’ll recognize reductions from combinatorial problems to single statistics—a powerful pattern across algorithms.

Learn to find the most frequent element in a C array with largest-value tie-break: naive, sorted, and hash map O(n) approaches with code examples.

Most Frequent Element in Array in C: Complete Guide

Most Frequent Element in an Array in C: A Comprehensive Tutorial

Finding the most frequent element in an array is a foundational problem that tests your understanding of frequency counting, tie-breaking rules, and time–space trade-offs. With the requirement to return the largest value in case of ties, this problem also reinforces comparison logic and careful edge-case handling.

This tutorial provides an in-depth exploration of three approaches in C: naive nested loops, sort-and-scan, and optimal hash-map counting. Complete, portable C99/C11 code examples are provided for each.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an integer array arr[], find the element that appears most frequently. If multiple elements share the highest frequency, return the largest value among them.

Examples:

text

Input: [1, 3, 2, 1, 4, 1] Output: 1 (1 appears three times) Input: [10, 20, 10, 20, 30, 20, 20] Output: 20 (20 appears four times) Input: [1, 2, 2, 4, 1] Output: 2 (1 and 2 tie at 2; return larger)

Why This Problem Matters

  • Frequency counting: Core of histograms, mode calculation, and voting systems.
  • Tie-breaking logic: Reinforces comparison and update rules.
  • Algorithm comparison: O(n²), O(n log n), O(n) trade-offs.
  • Interview preparation: Common coding interview question.
  • Real-world use: Analytics, log analysis, election results.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Naive nested loopsO(n²)O(1)
SortingO(n log n)O(1) or O(n)
Hash mapO(n) averageO(n)

The Tie-Breaking Rule

Whenever frequencies are equal, keep the larger value:

c

if (cnt > maxCnt || (cnt == maxCnt && val > res)) { res = val; maxCnt = cnt; }

Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Count occurrences of each element by scanning the entire array.

c

#include <stdio.h> #include <limits.h> int mostFreqNaive(const int arr[], int n) { if (n <= 0) return INT_MIN; int maxCnt = 0; int res = arr[0]; for (int i = 0; i < n; i++) { int cnt = 0; for (int j = 0; j < n; j++) { if (arr[i] == arr[j]) cnt++; } if (cnt > maxCnt || (cnt == maxCnt && arr[i] > res)) { maxCnt = cnt; res = arr[i]; } } return res; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", mostFreqNaive(arr, n)); return 0; }

Expected output:

text

30

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

Approach 2: Sorting + Linear Scan

After sorting, equal elements are adjacent. Track run lengths and apply tie-break.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h> static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } int mostFreqSorted(const int arr_in[], int n) { if (n <= 0) return INT_MIN; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return INT_MIN; memcpy(arr, arr_in, (size_t)n * sizeof(int)); qsort(arr, n, sizeof(int), compareInts); int maxCnt = 1; int res = arr[0]; int currCnt = 1; for (int i = 1; i < n; i++) { if (arr[i] == arr[i - 1]) currCnt++; else currCnt = 1; if (currCnt > maxCnt || (currCnt == maxCnt && arr[i] > res)) { maxCnt = currCnt; res = arr[i]; } } free(arr); return res; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", mostFreqSorted(arr, n)); return 0; }

Expected output:

text

30

Time: O(n log n), Space: O(n) if copied; O(1) if in-place mutation is acceptable

Approach 3: Hash Map (Optimal)

Count frequencies in one pass, then find the maximum with tie-break.

c

#include <stdio.h> #include <stdlib.h> #include <limits.h> #define TABLE_SIZE 16384 typedef struct { int *keys; int *counts; int *occupied; int capacity; int size; } FreqMap; static unsigned int hashFunc(int key, int cap) { return ((unsigned int)key) % (unsigned int)cap; } FreqMap *createMap(int cap) { FreqMap *m = malloc(sizeof(FreqMap)); if (m == NULL) return NULL; m->keys = malloc((size_t)cap * sizeof(int)); m->counts = calloc((size_t)cap, sizeof(int)); m->occupied = calloc((size_t)cap, sizeof(int)); if (m->keys == NULL || m->counts == NULL || m->occupied == NULL) { free(m->keys); free(m->counts); free(m->occupied); free(m); return NULL; } m->capacity = cap; m->size = 0; return m; } void freeMap(FreqMap *m) { if (m) { free(m->keys); free(m->counts); free(m->occupied); free(m); } } void increment(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) { m->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) return; /* Table full */ } m->keys[idx] = key; m->counts[idx] = 1; m->occupied[idx] = 1; m->size++; } int mostFreqHash(const int arr[], int n) { if (n <= 0) return INT_MIN; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return INT_MIN; for (int i = 0; i < n; i++) increment(m, arr[i]); int maxCnt = 0; int res = INT_MIN; for (int i = 0; i < m->capacity; i++) { if (!m->occupied[i]) continue; int val = m->keys[i]; int cnt = m->counts[i]; if (cnt > maxCnt || (cnt == maxCnt && val > res)) { maxCnt = cnt; res = val; } } freeMap(m); return res; } int main(void) { int arr[] = {40, 50, 30, 40, 50, 30, 30}; int n = sizeof(arr) / sizeof(arr[0]); printf("%d\n", mostFreqHash(arr, n)); return 0; }

Expected output:

text

30

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


Full Practical Code Example with Explanation

This program compares all three approaches, verifies their equality, and exercises edge cases.

c

/** * most_frequent.c * Finds the most frequent element in an array; ties broken by larger value. * Compares naive, sorting, and hash map 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 TABLE_SIZE 262144 /* 2^18 */ /* ============================================================ * Approach 1: Naive nested loops * ============================================================ */ int mostFreqNaive(const int arr[], int n) { if (n <= 0) return INT_MIN; int maxCnt = 0; int res = arr[0]; for (int i = 0; i < n; i++) { int cnt = 0; for (int j = 0; j < n; j++) { if (arr[i] == arr[j]) cnt++; } if (cnt > maxCnt || (cnt == maxCnt && arr[i] > res)) { maxCnt = cnt; res = arr[i]; } } return res; } /* ============================================================ * Approach 2: Sorting * ============================================================ */ static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } int mostFreqSorted(const int arr_in[], int n) { if (n <= 0) return INT_MIN; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return INT_MIN; memcpy(arr, arr_in, (size_t)n * sizeof(int)); qsort(arr, n, sizeof(int), compareInts); int maxCnt = 1; int res = arr[0]; int currCnt = 1; for (int i = 1; i < n; i++) { if (arr[i] == arr[i - 1]) currCnt++; else currCnt = 1; if (currCnt > maxCnt || (currCnt == maxCnt && arr[i] > res)) { maxCnt = currCnt; res = arr[i]; } } free(arr); return res; } /* ============================================================ * Approach 3: Hash map * ============================================================ */ typedef struct { int *keys; int *counts; int *occupied; int capacity; int size; } FreqMap; static unsigned int hashFunc(int key, int cap) { return ((unsigned int)key) % (unsigned int)cap; } FreqMap *createMap(int cap) { FreqMap *m = malloc(sizeof(FreqMap)); if (m == NULL) return NULL; m->keys = malloc((size_t)cap * sizeof(int)); m->counts = calloc((size_t)cap, sizeof(int)); m->occupied = calloc((size_t)cap, sizeof(int)); if (m->keys == NULL || m->counts == NULL || m->occupied == NULL) { free(m->keys); free(m->counts); free(m->occupied); free(m); return NULL; } m->capacity = cap; m->size = 0; return m; } void freeMap(FreqMap *m) { if (m) { free(m->keys); free(m->counts); free(m->occupied); free(m); } } void increment(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) { m->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) return; } m->keys[idx] = key; m->counts[idx] = 1; m->occupied[idx] = 1; m->size++; } int mostFreqHash(const int arr[], int n) { if (n <= 0) return INT_MIN; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return INT_MIN; for (int i = 0; i < n; i++) increment(m, arr[i]); int maxCnt = 0; int res = INT_MIN; for (int i = 0; i < m->capacity; i++) { if (!m->occupied[i]) continue; int val = m->keys[i]; int cnt = m->counts[i]; if (cnt > maxCnt || (cnt == maxCnt && val > res)) { maxCnt = cnt; res = val; } } freeMap(m); return res; } /* ============================================================ * Utility * ============================================================ */ 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("]"); } static void demoBasic(void) { printf("\n=== Sample Arrays ===\n"); int arr1[] = {1, 3, 2, 1, 4, 1}; int arr2[] = {10, 20, 10, 20, 30, 20, 20}; int arr3[] = {1, 2, 2, 4, 1}; int arr4[] = {40, 50, 30, 40, 50, 30, 30}; struct { int *arr; int n; } tests[] = { {arr1, 6}, {arr2, 7}, {arr3, 5}, {arr4, 7} }; int nt = sizeof(tests) / sizeof(tests[0]); for (int t = 0; t < nt; t++) { printf("\nTest %d: ", t + 1); printArray(tests[t].arr, tests[t].n); int a = mostFreqNaive(tests[t].arr, tests[t].n); int b = mostFreqSorted(tests[t].arr, tests[t].n); int c = mostFreqHash(tests[t].arr, tests[t].n); printf("\n Naive: %d\n Sorted: %d\n Hash: %d\n", a, b, c); printf(" Verify: %s\n", (a == b && b == c) ? "PASS" : "FAIL"); } printf("\n"); } static void demoEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Empty array */ printf("Empty array: naive=%d, sorted=%d, hash=%d\n", mostFreqNaive(NULL, 0), mostFreqSorted(NULL, 0), mostFreqHash(NULL, 0)); /* Single element */ int a1[] = {42}; printf("Single element: %d / %d / %d\n", mostFreqNaive(a1, 1), mostFreqSorted(a1, 1), mostFreqHash(a1, 1)); /* All distinct */ int a2[] = {1, 2, 3, 4, 5}; printf("All distinct: %d / %d / %d (each freq=1 → largest=5)\n", mostFreqNaive(a2, 5), mostFreqSorted(a2, 5), mostFreqHash(a2, 5)); /* All same */ int a3[] = {7, 7, 7, 7}; printf("All same: %d / %d / %d\n", mostFreqNaive(a3, 4), mostFreqSorted(a3, 4), mostFreqHash(a3, 4)); /* Tie between two values */ int a4[] = {1, 2, 2, 4, 1}; printf("Tie 1 & 2: %d / %d / %d (expect 2)\n", mostFreqNaive(a4, 5), mostFreqSorted(a4, 5), mostFreqHash(a4, 5)); /* Tie between negatives */ int a5[] = {-5, -5, -2, -2, 0}; printf("Tie -5 & -2: %d / %d / %d (expect -2)\n", mostFreqNaive(a5, 5), mostFreqSorted(a5, 5), mostFreqHash(a5, 5)); /* Zero and positives tie */ int a6[] = {0, 0, 3, 3, 3, 0}; printf("Tie 0 & 3: %d / %d / %d (expect 3)\n", mostFreqNaive(a6, 6), mostFreqSorted(a6, 6), mostFreqHash(a6, 6)); /* All negatives */ int a7[] = {-1, -2, -3, -2, -1, -1}; printf("All negatives: %d / %d / %d (expect -1)\n", mostFreqNaive(a7, 6), mostFreqSorted(a7, 6), mostFreqHash(a7, 6)); /* Multiple ties */ int a8[] = {5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2}; printf("Multi-tie: %d / %d / %d (expect 5)\n", mostFreqNaive(a8, 12), mostFreqSorted(a8, 12), mostFreqHash(a8, 12)); } static void performanceTest(void) { printf("\n=== Performance Comparison ===\n"); /* Naive: keep small */ int smallN = 3000; int *sa = malloc((size_t)smallN * sizeof(int)); if (sa == NULL) return; srand(42); for (int i = 0; i < smallN; i++) sa[i] = rand() % 500; clock_t start, end; double t; start = clock(); int r1 = mostFreqNaive(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): result=%d, time=%.3f ms\n", smallN, r1, t); start = clock(); int r2 = mostFreqSorted(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted (n=%d): result=%d, time=%.3f ms\n", smallN, r2, t); start = clock(); int r3 = mostFreqHash(sa, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", smallN, r3, t); printf("Verify: %s\n", (r1 == r2 && r2 == r3) ? "PASS" : "FAIL"); free(sa); /* Large: hash only */ int largeN = 5000000; int *la = malloc((size_t)largeN * sizeof(int)); if (la == NULL) return; for (int i = 0; i < largeN; i++) la[i] = rand() % 100000; start = clock(); int r4 = mostFreqHash(la, largeN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", largeN, r4, t); free(la); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Most Frequent Element Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Edge Cases\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: demoBasic(); break; case 2: demoEdgeCases(); break; case 3: performanceTest(); break; case 4: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice.\n"); break; } } return EXIT_SUCCESS; }

Explanation of Approaches

  1. Naive: For each element, count occurrences in a nested loop. Simple but quadratic.
  2. Sorted: Sort, then scan runs. Efficient for moderate sizes; uses O(n) memory unless in-place is acceptable.
  3. Hash: One pass to count, one pass to find max. Optimal for large inputs.

Expected Output (sample run)

text

=== Sample Arrays === Test 1: [1, 3, 2, 1, 4, 1] Naive: 1 Sorted: 1 Hash: 1 Verify: PASS Test 2: [10, 20, 10, 20, 30, 20, 20] Naive: 20 Sorted: 20 Hash: 20 Verify: PASS Test 3: [1, 2, 2, 4, 1] Naive: 2 Sorted: 2 Hash: 2 Verify: PASS Test 4: [40, 50, 30, 40, 50, 30, 30] Naive: 30 Sorted: 30 Hash: 30 Verify: PASS === Edge Cases === Empty array: naive=-2147483648, sorted=-2147483648, hash=-2147483648 Single element: 42 / 42 / 42 All distinct: 5 / 5 / 5 (each freq=1 → largest=5) All same: 7 / 7 / 7 Tie 1 & 2: 2 / 2 / 2 (expect 2) Tie -5 & -2: -2 / -2 / -2 (expect -2) Tie 0 & 3: 3 / 3 / 3 (expect 3) All negatives: -1 / -1 / -1 (expect -1) Multi-tie: 5 / 5 / 5 (expect 5) === Performance Comparison === Naive (n=3000): result=?, time=~180 ms Sorted (n=3000): result=?, time=~1 ms Hash (n=3000): result=?, time=~0.5 ms Verify: PASS Hash (n=5000000): result=?, time=~280 ms

Common Pitfalls, Edge Cases & Memory Considerations

1. Tie-Breaking Rule Reversed

The problem specifies “return the largest” among tied elements. Reversing this is a common error.

c

if (cnt > maxCnt || (cnt == maxCnt && val > res)) /* Correct */

2. Uninitialized res

If res starts as arr[0] but the first element is not the maximum in a tie, correctness depends on the update rule. Initialize res to arr[0] and apply the tie-break consistently.

3. Empty Array

Return a sentinel (INT_MIN). Document the behavior in your API.

4. Integer Overflow

Counts fit in int for arrays smaller than INT_MAX. Use size_t for very large arrays.

5. Hash Table Sizing

Table must be ≥ 2× number of distinct elements. Undersized → probe failures.

6. Negative Values

Cast to unsigned int in the hash function.

7. All Distinct

Every element has count 1 → tie → returns the maximum.

8. All Same

Every element has count n → returns that value.

9. Mutating Input in Sorted Approach

Copy first if the caller expects inputs preserved.

10. Memory Considerations

  • Naive: O(1) extra space.
  • Sorted: O(n) for copy; O(n log n) time.
  • Hash: O(n) extra space; O(n) time.
  • Free all dynamic allocations.

Summary & Best Practices

Finding the most frequent element with a largest-value tie-break is a classic problem. The hash-map approach is optimal for large inputs; sorting works well when memory is constrained; naive is only for tiny arrays.

Key Takeaways

  • Count frequencies, then find the max with tie-break.
  • Naive: O(n²) — educational only.
  • Sorted: O(n log n) — moderate sizes.
  • Hash: O(n) average — preferred.
  • Tie-break: choose the larger value.
  • Empty arrays: return a documented sentinel.

Best Practices

  1. Use a hash map for O(n) average performance.
  2. Apply the tie-break consistently (cnt > maxCnt || (cnt == maxCnt && val > res)).
  3. Initialize res carefully to avoid unintended behavior in ties.
  4. Handle empty arrays gracefully.
  5. Size hash tables ≥ 2× distinct elements.
  6. Cast negative keys to unsigned int.
  7. Copy inputs before sorting if preservation needed.
  8. Free allocated memory.
  9. Test edge cases: empty, single, all-same, all-distinct, ties, negatives.
  10. Profile for large inputs.

By mastering frequency counting and tie-breaking, you’ll be prepared for histogram analysis, voting systems, and a wide class of statistics-oriented problems.

Learn to check if two arrays are equal in C: sorting O(n log n) and hash map O(n) approaches with frequency counting. Complete code examples.

Check if Two Arrays Are Equal in C: Complete Guide

Check if Two Arrays Are Equal in C: A Comprehensive Tutorial

Determining whether two arrays are equal—regardless of order but with identical element counts—is a fundamental problem that appears in set comparison, data validation, and caching systems. This tutorial explores two primary approaches: sorting-based comparison (O(n log n)) and hash-based counting (O(n) average).


Conceptual Introduction & Fundamentals

What Does “Arrays Are Equal” Mean?

Two arrays are equal if:

  • Both have the same length.
  • Both contain the same multiset of elements (same elements with same counts).
  • Order does not matter.

Examples:

text

Input: a = [1, 2, 5, 4, 0], b = [2, 4, 5, 0, 1] Output: true Input: a = [1, 2, 5, 4, 0, 2, 1], b = [2, 4, 5, 0, 1, 1, 2] Output: true Input: a = [1, 7, 1], b = [7, 7, 1] Output: false (a has two 1s, b has two 7s)

Why This Problem Matters

  • Multiset comparison: Foundation for set operations with duplicates.
  • Data validation: Verify input arrays match expected.
  • Caching: Check cache-key equivalence.
  • Testing: Assert expected outputs match actual.
  • Interview preparation: Common coding interview question.

Approaches Overview

ApproachTime ComplexitySpace Complexity
SortingO(n log n)O(1) or O(n)
Hashing (frequency)O(n) averageO(n)

Note: Simple linear comparison only works if arrays are already sorted or order must match.


Syntax & Core Mechanics

Approach 1: Sorting

Sort both arrays and compare element by element.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> int compareInts(const void *x, const void *y) { int a = *(const int *)x; int b = *(const int *)y; return (a > b) - (a < b); } int arraysEqualSorted(const int a_in[], int n, const int b_in[], int m) { if (n != m) return 0; int *a = malloc(n * sizeof(int)); int *b = malloc(m * sizeof(int)); if (a == NULL || b == NULL) { free(a); free(b); return 0; } memcpy(a, a_in, n * sizeof(int)); memcpy(b, b_in, m * sizeof(int)); qsort(a, n, sizeof(int), compareInts); qsort(b, m, sizeof(int), compareInts); int result = 1; for (int i = 0; i < n; i++) { if (a[i] != b[i]) { result = 0; break; } } free(a); free(b); return result; } int main(void) { int a[] = {3, 5, 2, 5, 2}; int b[] = {2, 3, 5, 5, 2}; int n = sizeof(a) / sizeof(a[0]); int m = sizeof(b) / sizeof(b[0]); printf("%s\n", arraysEqualSorted(a, n, b, m) ? "true" : "false"); return 0; }

Expected output:

text

true

Time: O(n log n), Space: O(n) with copy, O(1) in-place

Approach 2: Hash Map (Frequency Counting)

Count occurrences in a; decrement counts using b.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 8192 typedef struct { int *keys; int *counts; int *occupied; int capacity; } HashMap; static unsigned int hashFunc(int key, int capacity) { return ((unsigned int)key) % (unsigned int)capacity; } HashMap *createHashMap(int capacity) { HashMap *map = malloc(sizeof(HashMap)); if (map == NULL) return NULL; map->keys = malloc(capacity * sizeof(int)); map->counts = calloc(capacity, sizeof(int)); map->occupied = calloc(capacity, sizeof(int)); if (map->keys == NULL || map->counts == NULL || map->occupied == NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); return NULL; } map->capacity = capacity; return map; } void freeHashMap(HashMap *map) { if (map) { free(map->keys); free(map->counts); free(map->occupied); free(map); } } void increment(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) return; } map->keys[idx] = key; map->counts[idx] = 1; map->occupied[idx] = 1; } int getCount(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) return map->counts[idx]; idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) break; } return 0; } void decrement(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]--; return; } idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) return; } } int arraysEqualHash(const int a[], int n, const int b[], int m) { if (n != m) return 0; HashMap *map = createHashMap(TABLE_SIZE); if (map == NULL) return 0; for (int i = 0; i < n; i++) increment(map, a[i]); int result = 1; for (int i = 0; i < m; i++) { int c = getCount(map, b[i]); if (c == 0) { result = 0; break; } decrement(map, b[i]); } freeHashMap(map); return result; } int main(void) { int a[] = {3, 5, 2, 5, 2}; int b[] = {2, 3, 5, 5, 2}; int n = sizeof(a) / sizeof(a[0]); int m = sizeof(b) / sizeof(b[0]); printf("%s\n", arraysEqualHash(a, n, b, m) ? "true" : "false"); return 0; }

Expected output:

text

true

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


Full Practical Code Example with Explanation

This program compares both approaches with verification and edge cases.

c

/** * arrays_equal.c * Checks if two arrays have the same multiset of elements. * Compares sorting and hash-map approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define TABLE_SIZE 262144 /* 2^18 */ /* ---------- Approach 1: Sorting ---------- */ static int compareInts(const void *x, const void *y) { int a = *(const int *)x; int b = *(const int *)y; return (a > b) - (a < b); } int arraysEqualSorted(const int a_in[], int n, const int b_in[], int m) { if (n != m) return 0; if (n == 0) return 1; int *a = malloc(n * sizeof(int)); int *b = malloc(m * sizeof(int)); if (a == NULL || b == NULL) { free(a); free(b); return -1; } memcpy(a, a_in, n * sizeof(int)); memcpy(b, b_in, m * sizeof(int)); qsort(a, n, sizeof(int), compareInts); qsort(b, m, sizeof(int), compareInts); int result = 1; for (int i = 0; i < n; i++) { if (a[i] != b[i]) { result = 0; break; } } free(a); free(b); return result; } /* ---------- Approach 2: Hash Map ---------- */ typedef struct { int *keys; int *counts; int *occupied; int capacity; } HashMap; static unsigned int hashFunc(int key, int capacity) { return ((unsigned int)key) % (unsigned int)capacity; } HashMap *createHashMap(int capacity) { HashMap *map = malloc(sizeof(HashMap)); if (map == NULL) return NULL; map->keys = malloc(capacity * sizeof(int)); map->counts = calloc(capacity, sizeof(int)); map->occupied = calloc(capacity, sizeof(int)); if (map->keys == NULL || map->counts == NULL || map->occupied == NULL) { free(map->keys); free(map->counts); free(map->occupied); free(map); return NULL; } map->capacity = capacity; return map; } void freeHashMap(HashMap *map) { if (map) { free(map->keys); free(map->counts); free(map->occupied); free(map); } } void increment(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]++; return; } idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) return; } map->keys[idx] = key; map->counts[idx] = 1; map->occupied[idx] = 1; } int getCount(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) return map->counts[idx]; idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) break; } return 0; } void decrement(HashMap *map, int key) { unsigned int idx = hashFunc(key, map->capacity); int start = (int)idx; while (map->occupied[idx]) { if (map->keys[idx] == key) { map->counts[idx]--; return; } idx = (idx + 1) % (unsigned int)map->capacity; if ((int)idx == start) return; } } int arraysEqualHash(const int a[], int n, const int b[], int m) { if (n != m) return 0; if (n == 0) return 1; HashMap *map = createHashMap(TABLE_SIZE); if (map == NULL) return -1; for (int i = 0; i < n; i++) increment(map, a[i]); int result = 1; for (int i = 0; i < m; i++) { int c = getCount(map, b[i]); if (c == 0) { result = 0; break; } decrement(map, b[i]); } freeHashMap(map); return result; } /* ---------- Utility ---------- */ 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("]"); } static void demoBasic(void) { printf("\n=== Sample Arrays ===\n"); int a1[] = {1, 2, 5, 4, 0}; int b1[] = {2, 4, 5, 0, 1}; int a2[] = {1, 2, 5, 4, 0, 2, 1}; int b2[] = {2, 4, 5, 0, 1, 1, 2}; int a3[] = {1, 7, 1}; int b3[] = {7, 7, 1}; struct { int *a, *b; int n, m; } tests[] = { {a1, b1, 5, 5}, {a2, b2, 7, 7}, {a3, b3, 3, 3} }; for (int t = 0; t < 3; t++) { printf("\nTest %d:\n a = ", t + 1); printArray(tests[t].a, tests[t].n); printf("\n b = "); printArray(tests[t].b, tests[t].m); printf("\n Sorted: %s\n", arraysEqualSorted(tests[t].a, tests[t].n, tests[t].b, tests[t].m) ? "true" : "false"); printf(" Hash: %s\n", arraysEqualHash(tests[t].a, tests[t].n, tests[t].b, tests[t].m) ? "true" : "false"); } printf("\n"); } static void demoEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Both empty */ printf("Both empty: sorted=%s, hash=%s\n", arraysEqualSorted(NULL, 0, NULL, 0) ? "true" : "false", arraysEqualHash(NULL, 0, NULL, 0) ? "true" : "false"); /* Different lengths */ int a1[] = {1, 2, 3}; int b1[] = {1, 2}; printf("Different lengths: sorted=%s, hash=%s\n", arraysEqualSorted(a1, 3, b1, 2) ? "true" : "false", arraysEqualHash(a1, 3, b1, 2) ? "true" : "false"); /* Same single element */ int a2[] = {42}; int b2[] = {42}; printf("Single equal: sorted=%s, hash=%s\n", arraysEqualSorted(a2, 1, b2, 1) ? "true" : "false", arraysEqualHash(a2, 1, b2, 1) ? "true" : "false"); /* Different frequencies */ int a3[] = {1, 1, 2}; int b3[] = {1, 2, 2}; printf("Different frequencies: sorted=%s, hash=%s\n", arraysEqualSorted(a3, 3, b3, 3) ? "true" : "false", arraysEqualHash(a3, 3, b3, 3) ? "true" : "false"); /* Negatives */ int a4[] = {-1, -5, -1, 3}; int b4[] = {3, -1, -5, -1}; printf("Negatives: sorted=%s, hash=%s\n", arraysEqualSorted(a4, 4, b4, 4) ? "true" : "false", arraysEqualHash(a4, 4, b4, 4) ? "true" : "false"); /* Zeros */ int a5[] = {0, 0, 1}; int b5[] = {1, 0, 0}; printf("Zeros: sorted=%s, hash=%s\n", arraysEqualSorted(a5, 3, b5, 3) ? "true" : "false", arraysEqualHash(a5, 3, b5, 3) ? "true" : "false"); /* Same array identity */ int a6[] = {5, 4, 3, 2, 1}; printf("Same array: sorted=%s, hash=%s\n", arraysEqualSorted(a6, 5, a6, 5) ? "true" : "false", arraysEqualHash(a6, 5, a6, 5) ? "true" : "false"); } static void performanceTest(void) { printf("\n=== Performance Comparison (n = 100000) ===\n"); int n = 100000; int *a = malloc(n * sizeof(int)); int *b = malloc(n * sizeof(int)); if (a == NULL || b == NULL) { free(a); free(b); return; } srand(42); for (int i = 0; i < n; i++) { a[i] = rand() % 1000; b[i] = a[i]; } /* Shuffle b */ for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int tmp = b[i]; b[i] = b[j]; b[j] = tmp; } clock_t start, end; double t; start = clock(); int r1 = arraysEqualSorted(a, n, b, n); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted: result=%s, time=%.3f ms\n", r1 ? "true" : "false", t); start = clock(); int r2 = arraysEqualHash(a, n, b, n); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash: result=%s, time=%.3f ms\n", r2 ? "true" : "false", t); printf("Verification: %s\n", (r1 == r2) ? "PASS" : "FAIL"); free(a); free(b); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Arrays Equal Check Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate All Approaches\n"); printf("2. Edge Cases\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: demoBasic(); break; case 2: demoEdgeCases(); break; case 3: performanceTest(); break; case 4: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice.\n"); break; } } return EXIT_SUCCESS; }

Explanation of Approaches

  1. Sorting: Copy inputs (to preserve), sort both, compare element by element.
  2. Hash Map: Count occurrences in a; decrement counts using b. If any b element’s count hits 0 before decrement, arrays differ.

Expected Output (sample run)

text

=== Sample Arrays === Test 1: a = [1, 2, 5, 4, 0] b = [2, 4, 5, 0, 1] Sorted: true Hash: true Test 2: a = [1, 2, 5, 4, 0, 2, 1] b = [2, 4, 5, 0, 1, 1, 2] Sorted: true Hash: true Test 3: a = [1, 7, 1] b = [7, 7, 1] Sorted: false Hash: false === Edge Cases === Both empty: sorted=true, hash=true Different lengths: sorted=false, hash=false Single equal: sorted=true, hash=true Different frequencies: sorted=false, hash=false Negatives: sorted=true, hash=true Zeros: sorted=true, hash=true Same array: sorted=true, hash=true === Performance Comparison (n = 100000) === Sorted: result=true, time=15.234 ms Hash: result=true, time=9.876 ms Verification: PASS

Common Pitfalls, Edge Cases & Memory Considerations

1. Comparing Lengths First

Different lengths → not equal. Always check n == m first.

2. Forgetting Counts

Two arrays with the same set but different counts are not equal.

c

a = [1, 1, 2]; b = [1, 2, 2]; /* Not equal */

3. Mutating Input

Sort-based approach modifies inputs unless copied.

Solution: Copy before sorting.

4. Empty Arrays

Both empty → equal. Guard against NULL arrays.

5. Hash Collisions

Ensure hash table sized appropriately; handle probe loops.

6. Negative Keys

Cast to unsigned int in hash function.

7. Early Exit

Return as soon as mismatch found.

8. Count Overflow

Use int counts; for extreme cases, int64_t.

9. Duplicate Elements

The frequency-counting approach naturally handles duplicates.

10. Memory Considerations

  • Sorted: O(n) copy or O(1) in-place; O(n log n) time.
  • Hash: O(n) extra space; O(n) time.
  • Hash is preferred for large unsorted arrays.

Summary & Best Practices

Checking arrays for multiset equality is a foundational operation. Sorting is simple and memory-efficient; hashing is faster in practice.

Key Takeaways

  • Arrays equal ⟺ same multiset of elements.
  • Sort + compare: O(n log n) time.
  • Hash + count: O(n) average time.
  • Always check length first.
  • Empty arrays are equal.

Best Practices

  1. Check length equality first.
  2. Use hashing for large arrays (O(n) average).
  3. Use sorting when memory is constrained.
  4. Copy inputs before sorting if preservation required.
  5. Use a hash map with frequency counts (not just membership).
  6. Handle negatives via unsigned cast in hash.
  7. Guard against NULL and zero-length arrays.
  8. Free allocated memory.
  9. Test edge cases: empty, single, different counts, negatives.
  10. Profile for large inputs.

By mastering multiset equality checking, you’ll gain practical insight into hash maps, sorting, and array comparison.