CoreTechED

Loading

Learn to count pairs with absolute difference k in C: naive O(n²), sort+two pointers O(n log n), and hash map O(n) approaches with code examples.

Count Pairs with Absolute Difference K in C

Count All Pairs with Absolute Difference Equal to K in C: A Comprehensive Tutorial

Counting pairs with an absolute difference equal to a target k is a classic problem that combines array traversal, sorting, and hashing. It demonstrates the same set of algorithmic trade-offs as Two Sum but requires careful handling of positive and negative differences — since the absolute value can come from either direction.

This tutorial provides an in-depth exploration of three approaches in C: naive O(n²), sort + two pointers O(n log n), and hash map O(n). Each implementation uses portable C99/C11 code with robust error handling and complete output examples.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an array arr[] and a positive integer k, count pairs (i, j) with i < j such that |arr[i] - arr[j]| == k.

Examples:

text

Input: arr = [1, 4, 1, 4, 5], k = 3 Output: 4 Pairs: (1,4), (1,4), (1,4), (4,1) Input: arr = [8, 16, 12, 16, 4, 0], k = 4 Output: 5 Pairs: (8,12), (8,4), (16,12), (12,16), (4,0)

Why This Problem Matters

  • Absolute value handling: Two complementary lookups per element.
  • Combinatorial counting: Same-value pairs occur when k = 0.
  • Hash map mastery: Frequency counting with two-pronged complement lookup.
  • Algorithm comparison: O(n²) → O(n log n) → O(n).
  • Interview preparation: Common coding interview question.

Key Insight

For each arr[i], a valid partner x satisfies either:

  • x = arr[i] + k
  • x = arr[i] - k

Checking both possibilities against a frequency map counts all pairs with absolute difference k in one pass.

Approaches Overview

ApproachTimeSpace
Naive nested loopsO(n²)O(1)
Sort + two pointersO(n log n)O(1) or O(n)
Hash mapO(n) averageO(n)

Same-Element Case (k = 0)

When k = 0, both complements coincide. The correct count is C(freq, 2) = freq × (freq - 1) / 2. The hash approach handles this naturally: for the second occurrence, only the +0 lookup succeeds (since -0 is the same key), so each pair is counted exactly once.


Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Enumerate all pairs; test absolute difference.

c

#include <stdio.h> #include <stdlib.h> long long countPairsNaive(const int arr[], int n, int k) { long long cnt = 0; for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { long long diff = (long long)arr[i] - (long long)arr[j]; if (diff < 0) diff = -diff; if (diff == (long long)k) cnt++; } } return cnt; } int main(void) { int arr[] = {1, 4, 1, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; printf("%lld\n", countPairsNaive(arr, n, k)); return 0; }

Expected output:

text

4

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

Approach 2: Sort + Two Pointers

Sort; advance i and j so that the difference matches k. Handle equal-value groups combinatorially.

c

#include <stdio.h> #include <stdlib.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); } long long countPairsSorted(const int arr_in[], int n, int k) { if (n < 2) return 0; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return -1; for (int i = 0; i < n; i++) arr[i] = arr_in[i]; qsort(arr, n, sizeof(int), compareInts); long long cnt = 0; int i = 0, j = 0; while (j < n) { long long diff = (long long)arr[j] - (long long)arr[i]; if (diff < k) { j++; } else if (diff > k) { i++; if (i > j) j = i; /* Keep j ≥ i */ } else { int ele1 = arr[i], ele2 = arr[j]; long long cnt1 = 0, cnt2 = 0; while (i < n && arr[i] == ele1) { i++; cnt1++; } while (j < n && arr[j] == ele2) { j++; cnt2++; } if (ele1 == ele2) cnt += cnt1 * (cnt1 - 1) / 2; else cnt += cnt1 * cnt2; } } free(arr); return cnt; } int main(void) { int arr[] = {1, 4, 1, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; printf("%lld\n", countPairsSorted(arr, n, k)); return 0; }

Expected output:

text

4

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

Approach 3: Hash Map (Optimal)

For each element, look up both arr[i] + k and arr[i] - k, then insert.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 8192 typedef struct { int *keys; long long *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(long long)); 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); } } long long mapGet(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) return m->counts[idx]; idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) break; } return 0; } void mapIncrement(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; } long long countPairsHash(const int arr[], int n, int k) { if (n < 2) return 0; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return -1; long long cnt = 0; for (int i = 0; i < n; i++) { /* Partner greater by k */ cnt += mapGet(m, arr[i] + k); /* Partner smaller by k */ cnt += mapGet(m, arr[i] - k); /* Now record current element */ mapIncrement(m, arr[i]); } freeMap(m); return cnt; } int main(void) { int arr[] = {1, 4, 1, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int k = 3; printf("%lld\n", countPairsHash(arr, n, k)); return 0; }

Expected output:

text

4

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

Handling k = 0

When k == 0, both lookups are identical to arr[i]. The frequency counts prior occurrences, yielding exactly C(freq, 2) automatically. No special-casing required.


Full Practical Code Example with Explanation

This program compares all three approaches and exercises edge cases including k = 0, negatives, and large datasets.

c

/** * count_pairs_diff_k.c * Counts pairs with |arr[i] - arr[j]| == k. * Compares naive, sort+two-pointer, 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; long long *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(long long)); 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); } } long long mapGet(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) return m->counts[idx]; idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) break; } return 0; } void mapIncrement(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 * ============================================================ */ long long countPairsNaive(const int arr[], int n, int k) { long long cnt = 0; for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { long long diff = (long long)arr[i] - (long long)arr[j]; if (diff < 0) diff = -diff; if (diff == (long long)k) cnt++; } } return cnt; } static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } long long countPairsSorted(const int arr_in[], int n, int k) { if (n < 2) return 0; 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); long long cnt = 0; int i = 0, j = 0; while (j < n) { long long diff = (long long)arr[j] - (long long)arr[i]; if (diff < k) { j++; } else if (diff > k) { i++; if (i > j) j = i; } else { int ele1 = arr[i], ele2 = arr[j]; long long cnt1 = 0, cnt2 = 0; while (i < n && arr[i] == ele1) { i++; cnt1++; } while (j < n && arr[j] == ele2) { j++; cnt2++; } if (ele1 == ele2) cnt += cnt1 * (cnt1 - 1) / 2; else cnt += cnt1 * cnt2; } } free(arr); return cnt; } long long countPairsHash(const int arr[], int n, int k) { if (n < 2) return 0; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return -1; long long cnt = 0; for (int i = 0; i < n; i++) { cnt += mapGet(m, arr[i] + k); cnt += mapGet(m, arr[i] - k); mapIncrement(m, arr[i]); } freeMap(m); return cnt; } /* ============================================================ * 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, 4, 1, 4, 5}; int a2[] = {8, 16, 12, 16, 4, 0}; struct { int *arr; int n; int k; const char *label; } tests[] = { {a1, 5, 3, "arr=[1,4,1,4,5], k=3"}, {a2, 6, 4, "arr=[8,16,12,16,4,0], k=4"} }; 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"); long long r1 = countPairsNaive(tests[t].arr, tests[t].n, tests[t].k); long long r2 = countPairsSorted(tests[t].arr, tests[t].n, tests[t].k); long long r3 = countPairsHash(tests[t].arr, tests[t].n, tests[t].k); printf(" Naive: %lld\n Sorted: %lld\n Hash: %lld\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=%lld, sorted=%lld, hash=%lld\n", countPairsNaive(NULL, 0, 1), countPairsSorted(NULL, 0, 1), countPairsHash(NULL, 0, 1)); /* Single */ int a1[] = {42}; printf("Single element: %lld / %lld / %lld\n", countPairsNaive(a1, 1, 5), countPairsSorted(a1, 1, 5), countPairsHash(a1, 1, 5)); /* Two equal, k=0 */ int a2[] = {5, 5}; printf("[5,5] k=0: %lld / %lld / %lld (expect 1)\n", countPairsNaive(a2, 2, 0), countPairsSorted(a2, 2, 0), countPairsHash(a2, 2, 0)); /* All same, k=0, freq=6 → C(6,2)=15 */ int a3[] = {7, 7, 7, 7, 7, 7}; printf("[7×6] k=0: %lld / %lld / %lld (expect 15)\n", countPairsNaive(a3, 6, 0), countPairsSorted(a3, 6, 0), countPairsHash(a3, 6, 0)); /* No pairs */ int a4[] = {1, 2, 3}; printf("[1,2,3] k=10: %lld / %lld / %lld\n", countPairsNaive(a4, 3, 10), countPairsSorted(a4, 3, 10), countPairsHash(a4, 3, 10)); /* Negatives */ int a5[] = {-1, -4, -1, -4, -5}; printf("[-1,-4,-1,-4,-5] k=3: %lld / %lld / %lld\n", countPairsNaive(a5, 5, 3), countPairsSorted(a5, 5, 3), countPairsHash(a5, 5, 3)); /* Zero and positives */ int a6[] = {0, 3, 0, 3}; printf("[0,3,0,3] k=3: %lld / %lld / %lld (expect 4)\n", countPairsNaive(a6, 4, 3), countPairsSorted(a6, 4, 3), countPairsHash(a6, 4, 3)); /* Large values, k small */ int a7[] = {1000000000, 1000000001, 999999999}; printf("Large values k=1: %lld / %lld / %lld\n", countPairsNaive(a7, 3, 1), countPairsSorted(a7, 3, 1), countPairsHash(a7, 3, 1)); } 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() % 2000) - 1000; int k = 500; clock_t start, end; double t; start = clock(); long long r1 = countPairsNaive(sa, smallN, k); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): result=%lld, time=%.3f ms\n", smallN, r1, t); start = clock(); long long r2 = countPairsSorted(sa, smallN, k); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted (n=%d): result=%lld, time=%.3f ms\n", smallN, r2, t); start = clock(); long long r3 = countPairsHash(sa, smallN, k); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%lld, 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(); long long r4 = countPairsHash(la, largeN, 500); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d, k=500): result=%lld, 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, k; if (readInteger("Enter array size (1-10000): ", &n) != 0 || n < 1 || n > 10000) { printf("Invalid size.\n"); return; } int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return; for (int i = 0; i < n; i++) { char prompt[40]; snprintf(prompt, sizeof(prompt), " arr[%d]: ", i); if (readInteger(prompt, &arr[i]) != 0) { printf("Invalid value.\n"); free(arr); return; } } if (readInteger("Enter k (non-negative): ", &k) != 0 || k < 0) { printf("Invalid k.\n"); free(arr); return; } printf("\nArray: "); printArray(arr, n); printf("\n"); printf("Pair count: %lld\n", countPairsHash(arr, n, k)); free(arr); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Count Pairs with Absolute Difference K 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: Compute |arr[i] - arr[j]| for all pairs. Simple, quadratic.
  2. Sorted + Two Pointers: After sorting, use sliding pointers so the difference is monotone.
  3. Hash Map: For each element, look up arr[i] + k and arr[i] - k before insertion.

Expected Output (sample run)

text

=== Sample Arrays === Test 1: arr=[1,4,1,4,5], k=3 Array: [1, 4, 1, 4, 5] Naive: 4 Sorted: 4 Hash: 4 Verify: PASS Test 2: arr=[8,16,12,16,4,0], k=4 Array: [8, 16, 12, 16, 4, 0] Naive: 5 Sorted: 5 Hash: 5 Verify: PASS === Edge Cases === Empty array: naive=0, sorted=0, hash=0 Single element: 0 / 0 / 0 [5,5] k=0: 1 / 1 / 1 (expect 1) [7×6] k=0: 15 / 15 / 15 (expect 15) [1,2,3] k=10: 0 / 0 / 0 [-1,-4,-1,-4,-5] k=3: 4 / 4 / 4 [0,3,0,3] k=3: 4 / 4 / 4 (expect 4) Large values k=1: 2 / 2 / 2

Common Pitfalls, Edge Cases & Memory Considerations

1. int Overflow in the Count

For large arrays with many duplicates, the count may exceed int.

Solution: Use long long for the count.

2. Integer Overflow in Difference

arr[i] - arr[j] can overflow int.

Solution: Cast operands to long long before subtracting.

3. Double Counting in Hash Map

Checking +k and -k looks redundant, but each finds a different pair orientation. Only pairs where the current element is the larger (via -k) or smaller (via +k) side are counted once each.

4. k = 0 Handling

Both lookups target arr[i]. The frequency counts prior occurrences; each pair is counted once, yielding C(freq, 2) automatically.

5. Negative k

The problem specifies k is positive; still guard against negative input.

6. Hash Table Sizing

Undersized table → probe failures.

Solution: Size ≥ 2× n.

7. Negatives Keys

Cast to unsigned int in the hash function.

8. Two-Pointer Invariant

Keep j ≥ i when moving i.

c

if (i > j) j = i;

9. Mutating Inputs in Sort

Copy before sorting if preservation is needed.

10. Memory Considerations

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

Summary & Best Practices

Counting pairs with absolute difference k is a classic problem with three clean solutions. The hash-map approach is optimal at O(n) average time using two-pronged complement lookup.

Key Takeaways

  • Pair condition: |arr[i] - arr[j]| == k.
  • Naive: O(n²).
  • Sort + two pointers: O(n log n), combinatorial counting for equal groups.
  • Hash map: O(n) average with two complement lookups per element.
  • k = 0 handled naturally by frequency counting.
  • Use long long for sums and counts.

Best Practices

  1. Use the hash map for O(n) average performance.
  2. Use long long for the count and differences.
  3. Look up both arr[i] + k and arr[i] - k before insertion.
  4. Cast negative keys to unsigned int.
  5. Size hash tables ≥ 2× n.
  6. Handle k = 0 via natural frequency counting.
  7. Keep j ≥ i in the sorted two-pointer approach.
  8. Free allocated memory.
  9. Test edge cases: empty, single, k = 0, negatives, large values.
  10. Profile for large inputs.

By mastering this problem, you’ll be equipped for count-based pair problems involving absolute value, distance, and metric-based lookups.

Learn to count pairs with a given sum in C: naive, sort+two pointers, and hash map O(n) approaches with combinatorial counting and edge cases.

Count Pairs With Given Sum in C: Complete Guide

2 Sum – Count Pairs with Given Sum in C: A Comprehensive Tutorial

Counting the number of pairs in an array whose sum equals a target value is a classic problem that extends the well-known Two Sum. Instead of merely checking whether a pair exists, we must count all valid pairs, including cases where elements repeat.

This tutorial provides an in-depth exploration of three approaches in C: naive O(n²), sort + two pointers O(n log n), and hash map O(n). Each is implemented with portable C99/C11 code, robust error handling, and complete output examples.


Conceptual Introduction & Fundamentals

What is the Problem?

Given an integer array arr[] and a target value, count the number of distinct index pairs (i, j) with i < j such that arr[i] + arr[j] == target.

Examples:

text

Input: arr = [1, 5, 7, -1, 5], target = 6 Output: 3 Explanation: (1,5), (7,-1), (1,5) Input: arr = [1, 1, 1, 1], target = 2 Output: 6 Explanation: 4C2 = 6 unordered pairs of identical elements Input: arr = [10, 12, 10, 15, -1], target = 125 Output: 0

Why This Problem Matters

  • Counting combinatorics: Same-value pairs require the formula C(k, 2) = k(k-1)/2.
  • Hash map mastery: Frequency-based counting in one pass.
  • Algorithm comparison: O(n²) → O(n log n) → O(n).
  • Real-world relevance: Analytics, matching systems, and graph edge counting.
  • Interview preparation: Common coding interview question.

Approaches Overview

ApproachTimeSpace
Naive nested loopsO(n²)O(1)
Sort + two pointersO(n log n)O(1) or O(n)
Hash mapO(n) averageO(n)

The Same-Element Formula

When both elements are equal (2 × value == target), the number of pairs is C(freq, 2) = freq × (freq - 1) / 2.


Syntax & Core Mechanics

Approach 1: Naive Nested Loops

Enumerate all pairs (i, j) with i < j.

c

#include <stdio.h> int countPairsNaive(const int arr[], int n, int target) { int cnt = 0; for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] + arr[j] == target) cnt++; } } return cnt; } int main(void) { int arr[] = {1, 5, 7, -1, 5}; int n = sizeof(arr) / sizeof(arr[0]); int target = 6; printf("%d\n", countPairsNaive(arr, n, target)); return 0; }

Expected output:

text

3

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

Approach 2: Sort + Two Pointers

Sort the array; move left/right inward; handle groups of equal values.

c

#include <stdio.h> #include <stdlib.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); } long long countPairsSorted(const int arr_in[], int n, int target) { if (n < 2) return 0; int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return -1; for (int i = 0; i < n; i++) arr[i] = arr_in[i]; qsort(arr, n, sizeof(int), compareInts); long long res = 0; int left = 0, right = n - 1; while (left < right) { int sum = arr[left] + arr[right]; if (sum < target) { left++; } else if (sum > target) { right--; } else { int ele1 = arr[left], ele2 = arr[right]; long long cnt1 = 0, cnt2 = 0; while (left <= right && arr[left] == ele1) { left++; cnt1++; } while (left <= right && arr[right] == ele2) { right--; cnt2++; } if (ele1 == ele2) { res += cnt1 * (cnt1 - 1) / 2; /* C(cnt1, 2) */ } else { res += cnt1 * cnt2; } } } free(arr); return res; } int main(void) { int arr[] = {1, 5, 7, -1, 5}; int n = sizeof(arr) / sizeof(arr[0]); int target = 6; printf("%lld\n", countPairsSorted(arr, n, target)); return 0; }

Expected output:

text

3

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

Approach 3: Hash Map

Insert elements as you go, checking for the complement before insertion.

c

#include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 8192 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); } } long long mapGet(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) return m->counts[idx]; idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) break; } return 0; } void mapIncrement(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; } long long countPairsHash(const int arr[], int n, int target) { if (n < 2) return 0; FreqMap *freq = createMap(TABLE_SIZE); if (freq == NULL) return -1; long long cnt = 0; for (int i = 0; i < n; i++) { long long c = mapGet(freq, target - arr[i]); cnt += c; mapIncrement(freq, arr[i]); } freeMap(freq); return cnt; } int main(void) { int arr[] = {1, 5, 7, -1, 5}; int n = sizeof(arr) / sizeof(arr[0]); int target = 6; printf("%lld\n", countPairsHash(arr, n, target)); return 0; }

Expected output:

text

3

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


Full Practical Code Example with Explanation

This program compares all three approaches and exercises edge cases including large values and many duplicates.

c

/** * count_pairs_sum.c * Counts pairs in an array whose sum equals a target. * Compares naive, sort+two-pointers, 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; long long *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(long long)); 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); } } long long mapGet(FreqMap *m, int key) { unsigned int idx = hashFunc(key, m->capacity); int start = (int)idx; while (m->occupied[idx]) { if (m->keys[idx] == key) return m->counts[idx]; idx = (idx + 1) % (unsigned int)m->capacity; if ((int)idx == start) break; } return 0; } void mapIncrement(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 * ============================================================ */ long long countPairsNaive(const int arr[], int n, int target) { long long cnt = 0; for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { if ((long long)arr[i] + arr[j] == (long long)target) cnt++; } } return cnt; } static int compareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } long long countPairsSorted(const int arr_in[], int n, int target) { if (n < 2) return 0; 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); long long res = 0; int left = 0, right = n - 1; while (left < right) { long long sum = (long long)arr[left] + arr[right]; if (sum < target) { left++; } else if (sum > target) { right--; } else { int ele1 = arr[left], ele2 = arr[right]; long long cnt1 = 0, cnt2 = 0; while (left <= right && arr[left] == ele1) { left++; cnt1++; } while (left <= right && arr[right] == ele2) { right--; cnt2++; } if (ele1 == ele2) res += cnt1 * (cnt1 - 1) / 2; else res += cnt1 * cnt2; } } free(arr); return res; } long long countPairsHash(const int arr[], int n, int target) { if (n < 2) return 0; FreqMap *m = createMap(TABLE_SIZE); if (m == NULL) return -1; long long cnt = 0; for (int i = 0; i < n; i++) { cnt += mapGet(m, target - arr[i]); mapIncrement(m, arr[i]); } freeMap(m); return cnt; } /* ============================================================ * 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, 5, 7, -1, 5}; int a2[] = {1, 1, 1, 1}; int a3[] = {10, 12, 10, 15, -1}; struct { int *arr; int n; int target; const char *label; } tests[] = { {a1, 5, 6, "arr=[1,5,7,-1,5], target=6"}, {a2, 4, 2, "arr=[1,1,1,1], target=2"}, {a3, 5, 125, "arr=[10,12,10,15,-1], target=125"} }; 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"); long long r1 = countPairsNaive(tests[t].arr, tests[t].n, tests[t].target); long long r2 = countPairsSorted(tests[t].arr, tests[t].n, tests[t].target); long long r3 = countPairsHash(tests[t].arr, tests[t].n, tests[t].target); printf(" Naive: %lld\n Sorted: %lld\n Hash: %lld\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=%lld, sorted=%lld, hash=%lld\n", countPairsNaive(NULL, 0, 0), countPairsSorted(NULL, 0, 0), countPairsHash(NULL, 0, 0)); /* Single */ int a1[] = {5}; printf("Single element target 10: naive=%lld, sorted=%lld, hash=%lld\n", countPairsNaive(a1, 1, 10), countPairsSorted(a1, 1, 10), countPairsHash(a1, 1, 10)); /* Two equal */ int a2[] = {5, 5}; printf("[5,5] target 10: %lld / %lld / %lld\n", countPairsNaive(a2, 2, 10), countPairsSorted(a2, 2, 10), countPairsHash(a2, 2, 10)); /* All same, large */ int a3[] = {7, 7, 7, 7, 7, 7}; printf("[7×6] target 14: %lld / %lld / %lld (expect C(6,2)=15)\n", countPairsNaive(a3, 6, 14), countPairsSorted(a3, 6, 14), countPairsHash(a3, 6, 14)); /* Negatives */ int a4[] = {-1, -2, -3, -4}; printf("[-1,-2,-3,-4] target -5: %lld / %lld / %lld\n", countPairsNaive(a4, 4, -5), countPairsSorted(a4, 4, -5), countPairsHash(a4, 4, -5)); /* Mixed */ int a5[] = {3, 5, 2, 4, 3, 5, 3}; printf("[3,5,2,4,3,5,3] target 8: %lld / %lld / %lld\n", countPairsNaive(a5, 7, 8), countPairsSorted(a5, 7, 8), countPairsHash(a5, 7, 8)); /* No pair */ int a6[] = {1, 2, 3}; printf("[1,2,3] target 100: %lld / %lld / %lld\n", countPairsNaive(a6, 3, 100), countPairsSorted(a6, 3, 100), countPairsHash(a6, 3, 100)); /* Large values (overflow test) */ int a7[] = {2000000000, 2000000000, -2000000000, -2000000000}; printf("Large values target 0: %lld / %lld / %lld\n", countPairsNaive(a7, 4, 0), countPairsSorted(a7, 4, 0), countPairsHash(a7, 4, 0)); } 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() % 2000) - 1000; int target = 1234; clock_t start, end; double t; start = clock(); long long r1 = countPairsNaive(sa, smallN, target); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): result=%lld, time=%.3f ms\n", smallN, r1, t); start = clock(); long long r2 = countPairsSorted(sa, smallN, target); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Sorted (n=%d): result=%lld, time=%.3f ms\n", smallN, r2, t); start = clock(); long long r3 = countPairsHash(sa, smallN, target); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%lld, 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(); long long r4 = countPairsHash(la, largeN, 199999); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%lld, 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 (1-10000): ", &n) != 0 || n < 1 || n > 10000) { printf("Invalid size.\n"); return; } int *arr = malloc((size_t)n * sizeof(int)); if (arr == NULL) return; for (int i = 0; i < n; i++) { char prompt[40]; snprintf(prompt, sizeof(prompt), " arr[%d]: ", i); if (readInteger(prompt, &arr[i]) != 0) { printf("Invalid value.\n"); free(arr); return; } } int target; if (readInteger("Enter target: ", &target) != 0) { printf("Invalid target.\n"); free(arr); return; } printf("\nArray: "); printArray(arr, n); printf("\n"); printf("Pair count: %lld\n", countPairsHash(arr, n, target)); free(arr); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Count Pairs With Given Sum 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: Enumerate all n(n-1)/2 pairs. Straightforward but quadratic.
  2. Sorted + Two Pointers: Sort and move pointers inward; count runs of equal values and use combinatorial formulas.
  3. Hash Map: For each element, count how many times its complement has already been seen; insert the current element.

Expected Output (sample run)

text

=== Sample Arrays === Test 1: arr=[1,5,7,-1,5], target=6 Array: [1, 5, 7, -1, 5] Naive: 3 Sorted: 3 Hash: 3 Verify: PASS Test 2: arr=[1,1,1,1], target=2 Array: [1, 1, 1, 1] Naive: 6 Sorted: 6 Hash: 6 Verify: PASS Test 3: arr=[10,12,10,15,-1], target=125 Array: [10, 12, 10, 15, -1] Naive: 0 Sorted: 0 Hash: 0 Verify: PASS === Edge Cases === Empty array: naive=0, sorted=0, hash=0 Single element target 10: naive=0, sorted=0, hash=0 [5,5] target 10: 1 / 1 / 1 [7×6] target 14: 15 / 15 / 15 (expect C(6,2)=15) [-1,-2,-3,-4] target -5: 1 / 1 / 1 [3,5,2,4,3,5,3] target 8: 3 / 3 / 3 [1,2,3] target 100: 0 / 0 / 0 Large values target 0: 2 / 2 / 2

Common Pitfalls, Edge Cases & Memory Considerations

1. Using int for the Count

For large arrays with many duplicates, the count can exceed int (e.g., n = 100000 with all equal values → ~5×10⁹ pairs).

Solution: Use long long for the count.

2. Integer Overflow in Sum

arr[i] + arr[j] can overflow for large values.

Solution: Cast to long long before adding.

c

if ((long long)arr[i] + arr[j] == (long long)target) ...

3. Counting Self-Pairs

The hash map approach checks the complement before inserting the current element, ensuring i < j.

4. Same-Element Pairs

When 2 × value == target, use C(freq, 2) = freq × (freq - 1) / 2 in the sorted approach, or let the hash map naturally accumulate matches (since the first insertion precedes the second lookup).

5. Duplicate Keys in Hash

The hash map must store frequencies, not just presence.

6. Hash Table Sizing

Undersized table → probe failures.

Solution: Size ≥ 2× n.

7. Negative Values

Cast to unsigned int in the hash function.

8. Mutating Inputs

The sorted approach copies inputs; this preserves originals.

9. Empty or Single-Element Arrays

No pairs possible; return 0 early.

10. Memory Considerations

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

Summary & Best Practices

The count-pairs-sum problem is the natural extension of Two Sum. It requires frequency tracking rather than mere existence.

Key Takeaways

  • Count all index pairs (i, j), i < j, with arr[i] + arr[j] == target.
  • Naive: O(n²).
  • Sort + two pointers: O(n log n) with combinatorial counting.
  • Hash map: O(n) average using complement lookup.
  • Use long long for both sum and count.
  • Handle same-element pairs with C(freq, 2).

Best Practices

  1. Use the hash map for O(n) average performance.
  2. Count with long long to prevent overflow.
  3. Sum with long long to prevent overflow.
  4. Check complement before insertion to avoid self-pairs.
  5. Handle same-element pairs with C(freq, 2) in sorted approach.
  6. Size hash tables ≥ 2× n.
  7. Cast negative keys to unsigned int.
  8. Copy inputs before sorting when preservation is needed.
  9. Free allocated memory.
  10. Test edge cases: empty, single, all-same, negatives, large values.

By mastering this problem, you’ll be well prepared for Three Sum, Four Sum, and other pair-counting variants.