CoreTechED

Loading

Tag algorithm optimization

Learn to find leaders in an array using naive O(n²) and optimal O(n) approaches. Master right-to-left traversal with C examples.

Leaders in an Array: Complete Guide

Leaders in an Array: A Comprehensive Tutorial

Given an array, a Leader is an element that is greater than or equal to all elements to its right. The rightmost element is always a leader. Finding leaders is a classic array problem that demonstrates efficient right-to-left traversal.

This tutorial provides an in-depth exploration of finding leaders in an array: the naive approach using nested loops, the optimal approach using suffix maximum, practical implementations in C, edge cases, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is a Leader?

A leader is an element that is greater than or equal to all elements to its right side. The rightmost element has no elements to its right, so it’s always a leader.

Example:

text

Array: [16, 17, 4, 3, 5, 2] Leaders: 17, 5, 2

Explanation:

  • 17 is greater than all elements to its right (4, 3, 5, 2).
  • 5 is greater than all elements to its right (2).
  • 2 has no elements to its right, so it’s a leader.

Why This Problem Matters

  • Efficient traversal: Right-to-left scanning pattern.
  • Algorithm optimization: From O(n²) to O(n).
  • Interview preparation: Common coding question.
  • Pattern recognition: Identify superior elements.

Approaches

ApproachTime ComplexitySpace Complexity
Naive (Nested Loops)O(n²)O(1)
Suffix MaximumO(n)O(1) auxiliary

Syntax & Core Mechanics

Naive Approach: O(n²)

Use nested loops to compare each element with all elements to its right.

c

#include <stdio.h> #include <stdlib.h> /* Naive approach: O(n²) */ int *leadersNaive(int arr[], int n, int *resultSize) { int *result = malloc(n * sizeof(int)); if (result == NULL) return NULL; int count = 0; for (int i = 0; i < n; i++) { int j; /* Check if any larger element to the right */ for (j = i + 1; j < n; j++) { if (arr[i] < arr[j]) { break; // arr[i] is not a leader } } /* If no larger element found, it's a leader */ if (j == n) { result[count++] = arr[i]; } } *resultSize = count; return result; } int main(void) { int arr[] = {16, 17, 4, 3, 5, 2}; int n = sizeof(arr) / sizeof(arr[0]); int resultSize; int *result = leadersNaive(arr, n, &resultSize); printf("Leaders (naive): "); for (int i = 0; i < resultSize; i++) { printf("%d ", result[i]); } printf("\n"); free(result); return 0; }

Expected output:

text

Leaders (naive): 17 5 2

Optimal Approach: O(n) Using Suffix Maximum

Scan from right to left, tracking the maximum seen so far.

c

#include <stdio.h> #include <stdlib.h> /* Optimal approach: O(n) */ int *leadersOptimal(int arr[], int n, int *resultSize) { int *result = malloc(n * sizeof(int)); if (result == NULL) return NULL; int count = 0; /* Rightmost element is always a leader */ int maxRight = arr[n - 1]; result[count++] = maxRight; /* Traverse from right to left */ for (int i = n - 2; i >= 0; i--) { if (arr[i] >= maxRight) { maxRight = arr[i]; result[count++] = maxRight; } } /* Reverse the result */ for (int i = 0; i < count / 2; i++) { int temp = result[i]; result[i] = result[count - i - 1]; result[count - i - 1] = temp; } *resultSize = count; return result; } int main(void) { int arr[] = {16, 17, 4, 3, 5, 2}; int n = sizeof(arr) / sizeof(arr[0]); int resultSize; int *result = leadersOptimal(arr, n, &resultSize); printf("Leaders (optimal): "); for (int i = 0; i < resultSize; i++) { printf("%d ", result[i]); } printf("\n"); free(result); return 0; }

Expected output:

text

Leaders (optimal): 17 5 2

Step-by-Step Illustration

text

arr[] = {16, 17, 4, 3, 5, 2} Initially: maxRight = 2, result = {2} i=4: arr[4]=5 >= 2 → maxRight=5, result={2,5} i=3: arr[3]=3 < 5 → no change i=2: arr[2]=4 < 5 → no change i=1: arr[1]=17 >= 5 → maxRight=17, result={2,5,17} i=0: arr[0]=16 < 17 → no change Reverse result: {17, 5, 2}

Full Practical Code Example with Explanation

This program demonstrates both approaches with comprehensive examples and edge cases.

Program: Leaders in Array Finder

c

/** * leaders_in_array.c * Demonstrates finding leaders using naive and optimal approaches. * Includes edge cases and performance comparison. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes */ int *findLeadersNaive(const int arr[], int n, int *resultSize); int *findLeadersOptimal(const int arr[], int n, int *resultSize); void demonstrateNaive(void); void demonstrateOptimal(void); void demonstrateEdgeCases(void); void demonstratePerformance(void); void printArray(const int arr[], int n); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Leaders in Array ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Naive Approach O(n²)\n"); printf("2. Optimal Approach O(n)\n"); printf("3. Edge Cases\n"); printf("4. Performance Comparison\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: demonstrateNaive(); break; case 2: demonstrateOptimal(); break; case 3: demonstrateEdgeCases(); break; case 4: demonstratePerformance(); 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²) */ int *findLeadersNaive(const int arr[], int n, int *resultSize) { int *result = malloc(n * sizeof(int)); if (result == NULL) { *resultSize = 0; return NULL; } int count = 0; for (int i = 0; i < n; i++) { int j; for (j = i + 1; j < n; j++) { if (arr[i] < arr[j]) { break; } } if (j == n) { result[count++] = arr[i]; } } *resultSize = count; return result; } /* Optimal approach: O(n) */ int *findLeadersOptimal(const int arr[], int n, int *resultSize) { int *result = malloc(n * sizeof(int)); if (result == NULL) { *resultSize = 0; return NULL; } int count = 0; int maxRight = arr[n - 1]; result[count++] = maxRight; for (int i = n - 2; i >= 0; i--) { if (arr[i] >= maxRight) { maxRight = arr[i]; result[count++] = maxRight; } } /* Reverse */ for (int i = 0; i < count / 2; i++) { int temp = result[i]; result[i] = result[count - i - 1]; result[count - i - 1] = temp; } *resultSize = count; return result; } /* Demonstrates naive approach */ void demonstrateNaive(void) { printf("\n=== Naive Approach O(n²) ===\n"); int arr[] = {16, 17, 4, 3, 5, 2}; int n = sizeof(arr) / sizeof(arr[0]); int resultSize; printf("Array: "); printArray(arr, n); int *result = findLeadersNaive(arr, n, &resultSize); printf("Leaders: "); for (int i = 0; i < resultSize; i++) { printf("%d ", result[i]); } printf("\n"); printf("Time: O(n²), Space: O(1) auxiliary\n"); free(result); } /* Demonstrates optimal approach */ void demonstrateOptimal(void) { printf("\n=== Optimal Approach O(n) ===\n"); int arr[] = {16, 17, 4, 3, 5, 2}; int n = sizeof(arr) / sizeof(arr[0]); int resultSize; printf("Array: "); printArray(arr, n); int *result = findLeadersOptimal(arr, n, &resultSize); printf("Leaders: "); for (int i = 0; i < resultSize; i++) { printf("%d ", result[i]); } printf("\n"); printf("Time: O(n), Space: O(1) auxiliary\n"); free(result); } /* Demonstrates edge cases */ void demonstrateEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Case 1: Empty array */ int emptyArr[] = {}; int emptyN = 0; printf("Empty array: "); if (emptyN > 0) { int size; int *result = findLeadersOptimal(emptyArr, emptyN, &size); for (int i = 0; i < size; i++) printf("%d ", result[i]); free(result); } printf("(no elements)\n"); /* Case 2: Single element */ int singleArr[] = {42}; int singleN = sizeof(singleArr) / sizeof(singleArr[0]); int size; int *result = findLeadersOptimal(singleArr, singleN, &size); printf("Single element: "); for (int i = 0; i < size; i++) printf("%d ", result[i]); printf("\n"); free(result); /* Case 3: All same elements */ int sameArr[] = {5, 5, 5, 5, 5}; int sameN = sizeof(sameArr) / sizeof(sameArr[0]); result = findLeadersOptimal(sameArr, sameN, &size); printf("All same elements: "); for (int i = 0; i < size; i++) printf("%d ", result[i]); printf("\n"); free(result); /* Case 4: Increasing order */ int incArr[] = {1, 2, 3, 4, 5}; int incN = sizeof(incArr) / sizeof(incArr[0]); result = findLeadersOptimal(incArr, incN, &size); printf("Increasing order: "); for (int i = 0; i < size; i++) printf("%d ", result[i]); printf("\n"); free(result); /* Case 5: Decreasing order */ int decArr[] = {5, 4, 3, 2, 1}; int decN = sizeof(decArr) / sizeof(decArr[0]); result = findLeadersOptimal(decArr, decN, &size); printf("Decreasing order: "); for (int i = 0; i < size; i++) printf("%d ", result[i]); printf("\n"); free(result); /* Case 6: Negative numbers */ int negArr[] = {-5, -2, -8, -1, -4}; int negN = sizeof(negArr) / sizeof(negArr[0]); result = findLeadersOptimal(negArr, negN, &size); printf("Negative numbers: "); for (int i = 0; i < size; i++) printf("%d ", result[i]); printf("\n"); free(result); } /* Demonstrates performance */ void demonstratePerformance(void) { printf("\n=== Performance Comparison ===\n"); int n = 10000; int *arr = malloc(n * sizeof(int)); if (arr == NULL) { perror("malloc failed"); return; } /* Fill with random-like values */ for (int i = 0; i < n; i++) { arr[i] = (i * 7) % 100; } int resultSize; /* Naive */ clock_t start = clock(); int *resultNaive = findLeadersNaive(arr, n, &resultSize); clock_t end = clock(); double naiveTime = ((double)(end - start)) / CLOCKS_PER_SEC * 1000; free(resultNaive); /* Optimal */ start = clock(); int *resultOptimal = findLeadersOptimal(arr, n, &resultSize); end = clock(); double optimalTime = ((double)(end - start)) / CLOCKS_PER_SEC * 1000; free(resultOptimal); printf("Array size: %d\n", n); printf("Naive O(n²): %.3f ms\n", naiveTime); printf("Optimal O(n): %.3f ms\n", optimalTime); free(arr); } /* Prints array */ void printArray(const int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); }

Explanation of Approaches

  1. Naive: For each element, check all elements to the right.
  2. Optimal: Scan from right, track maximum, reverse result.
  3. Edge cases: Empty, single, all same, increasing, decreasing, negative.
  4. Performance: Optimal is significantly faster for large arrays.

Expected Output (partial)

text

=== Leaders in Array === --- Menu --- 1. Naive Approach O(n²) 2. Optimal Approach O(n) 3. Edge Cases 4. Performance Comparison 5. Exit Enter choice: 2 === Optimal Approach O(n) === Array: 16 17 4 3 5 2 Leaders: 17 5 2 Time: O(n), Space: O(1) auxiliary --- Menu --- ... Enter choice: 3 === Edge Cases === Empty array: (no elements) Single element: 42 All same elements: 5 5 5 5 5 Increasing order: 5 Decreasing order: 5 4 3 2 1 Negative numbers: -1 -4 --- Menu --- ... Enter choice: 4 === Performance Comparison === Array size: 10000 Naive O(n²): 45.678 ms Optimal O(n): 0.234 ms

Common Pitfalls, Edge Cases & Memory Considerations

1. Not Handling Empty Array

Accessing arr[n-1] on empty array is undefined.

Solution: Check n > 0 before accessing elements.

2. Using > Instead of >=

Leaders include elements equal to the maximum on the right.

c

if (arr[i] > maxRight) // BUG: misses equal elements if (arr[i] >= maxRight) // Correct

3. Forgetting to Reverse Result

Right-to-left scan produces reversed order.

Solution: Reverse the result before returning.

4. Memory Leaks

Forgetting to free allocated result arrays.

Solution: Always free allocated memory.

5. Not Checking malloc Return

malloc can return NULL.

Solution: Check allocation return values.

6. Wrong Index in Right-to-Left Loop

Starting from n instead of n-2 after initializing with arr[n-1].

Solution: Start from n - 2.

7. All Elements Same

Every element is a leader.

Solution: Use >= to include equal elements.

8. Integer Overflow in Indices

Large arrays overflow int.

Solution: Use size_t for indices.

9. Not Validating Input

Invalid input causes crashes.

Solution: Validate user input.

10. Memory Considerations

  • Naive: O(1) auxiliary space.
  • Optimal: O(1) auxiliary space (result array not counted as auxiliary).
  • Both allocate O(n) for result.
  • No recursion, so no stack space issues.

Summary & Best Practices

Finding leaders in an array demonstrates efficient right-to-left traversal. The optimal O(n) approach using suffix maximum is significantly faster than the naive O(n²) approach.

Key Takeaways

  • Leader: element >= all elements to its right.
  • Rightmost element is always a leader.
  • Naive: O(n²) with nested loops.
  • Optimal: O(n) with right-to-left scan.
  • Use >= for equal elements.
  • Reverse result after right-to-left scan.

Best Practices

  1. Use optimal O(n) approach.
  2. Use >= for leader comparison.
  3. Handle empty arrays.
  4. Check malloc return values.
  5. Free allocated memory.
  6. Reverse result correctly.
  7. Test with edge cases.
  8. Use const for read-only arrays.
  9. Document complexity.
  10. Profile performance for large arrays.

By mastering this problem, you’ll develop efficient array traversal skills applicable to many algorithmic challenges.

Learn to count zeros in a sorted binary array using linear scan and binary search. Master O(log n) algorithm with edge cases and performance tips.

Count Zeros in Sorted Binary Array: C Guide

Zeros Count in Sorted Binary Array: A Comprehensive Tutorial

Counting zeros in a sorted binary array is a classic algorithmic problem that demonstrates efficient searching techniques. A sorted binary array contains only 0s and 1s, with all 0s appearing before all 1s (or vice versa). Finding the count of zeros efficiently is essential for understanding binary search, time complexity analysis, and optimization techniques.

This tutorial provides an in-depth exploration of counting zeros in a sorted binary array: problem understanding, naive and optimized approaches, binary search implementation, edge cases, and best practices.


Conceptual Introduction & Fundamentals

What is a Sorted Binary Array?

A sorted binary array is an array that contains only two distinct values: 0 and 1. The array is sorted, meaning all 0s come first, followed by all 1s (ascending order). For example:

text

[0, 0, 0, 1, 1, 1] // Three zeros followed by three ones [0, 0, 0, 0] // All zeros [1, 1, 1, 1] // All ones [] // Empty array

Problem Statement

Given a sorted binary array (all 0s followed by all 1s), count the number of zeros in the array.

Examples:

  • Input: [0, 0, 0, 1, 1] → Output: 3
  • Input: [0, 0, 0, 0] → Output: 4
  • Input: [1, 1, 1, 1] → Output: 0
  • Input: [] → Output: 0

Why This Problem Matters

  • Algorithm design: Demonstrates linear search vs. binary search trade-offs.
  • Time complexity analysis: O(n) vs. O(log n) comparison.
  • Foundation for advanced problems: Variations include finding first occurrence, last occurrence, and count of any element in sorted arrays.
  • Interview preparation: Common coding interview question.
  • Real-world applications: Counting occurrences in sorted datasets, database queries, and data analysis.

Approaches Overview

ApproachTime ComplexitySpace Complexity
Linear scanO(n)O(1)
Binary search (find first 1)O(log n)O(1)
Binary search (find last 0)O(log n)O(1)
Standard library bsearchO(log n)O(1)

Syntax & Core Mechanics

Approach 1: Linear Scan (Naive)

Traverse the array from left to right, counting zeros until a 1 is encountered.

c

#include <stdio.h> int countZerosLinear(const int arr[], int n) { int count = 0; for (int i = 0; i < n; i++) { if (arr[i] == 0) { count++; } else { break; // Since sorted, first 1 means no more zeros } } return count; } int main(void) { int arr[] = {0, 0, 0, 1, 1, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Zeros: %d\n", countZerosLinear(arr, n)); return 0; }

Expected output:

text

Zeros: 3

Time complexity: O(n) in worst case (all zeros).

Approach 2: Binary Search (Find First 1)

Since the array is sorted, use binary search to find the index of the first 1. The count of zeros equals that index.

c

#include <stdio.h> int countZerosBinary(const int arr[], int n) { int left = 0; int right = n - 1; int firstOneIndex = n; // Default: no 1 found while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == 1) { firstOneIndex = mid; // Record position right = mid - 1; // Search left half } else { left = mid + 1; // Search right half } } return firstOneIndex; // Number of zeros = index of first 1 } int main(void) { int arr[] = {0, 0, 0, 1, 1, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Zeros: %d\n", countZerosBinary(arr, n)); return 0; }

Expected output:

text

Zeros: 3

Time complexity: O(log n).

Binary Search Logic Explained

  1. Initialize left = 0, right = n - 1, firstOneIndex = n.
  2. While left <= right:
    • Calculate mid = left + (right - left) / 2 (avoids overflow).
    • If arr[mid] == 1, update firstOneIndex = mid and search left half (right = mid - 1).
    • If arr[mid] == 0, search right half (left = mid + 1).
  3. After loop, firstOneIndex is the index of the first 1.
  4. The count of zeros equals firstOneIndex.

Approach 3: Binary Search (Find Last 0)

Alternatively, find the index of the last 0. The count of zeros equals lastZeroIndex + 1.

c

#include <stdio.h> int countZerosLastZero(const int arr[], int n) { int left = 0; int right = n - 1; int lastZeroIndex = -1; // Default: no 0 found while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == 0) { lastZeroIndex = mid; // Record position left = mid + 1; // Search right half } else { right = mid - 1; // Search left half } } return lastZeroIndex + 1; // Count of zeros } int main(void) { int arr[] = {0, 0, 0, 1, 1, 1}; int n = sizeof(arr) / sizeof(arr[0]); printf("Zeros: %d\n", countZerosLastZero(arr, n)); return 0; }

Expected output:

text

Zeros: 3

Handling Edge Cases

c

#include <stdio.h> int countZerosEdge(const int arr[], int n) { /* Empty array */ if (n == 0) return 0; /* All ones: first element is 1 */ if (arr[0] == 1) return 0; /* All zeros: last element is 0 */ if (arr[n - 1] == 0) return n; /* Mixed: binary search */ int left = 0; int right = n - 1; int firstOneIndex = n; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == 1) { firstOneIndex = mid; right = mid - 1; } else { left = mid + 1; } } return firstOneIndex; }

Full Practical Code Example with Explanation

This program demonstrates multiple approaches to count zeros in a sorted binary array, including linear scan, binary search, and recursive binary search, with performance comparison.

Program: Zero Counter for Sorted Binary Arrays

c

/** * count_zeros_sorted_binary.c * Counts zeros in a sorted binary array using multiple approaches. * Demonstrates linear scan, iterative binary search, and recursive binary search. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes */ int countZerosLinear(const int arr[], int n); int countZerosBinary(const int arr[], int n); int countZerosRecursive(const int arr[], int left, int right); int countZerosWrapper(const int arr[], int n); int readInteger(const char *prompt, int *outValue); void demonstrateApproach(const char *name, int result, clock_t start, clock_t end); void printArray(const int arr[], int n); int main(void) { int choice; char buffer[MAX_LINE_LEN]; /* Test arrays */ int arr1[] = {0, 0, 0, 1, 1, 1}; int n1 = sizeof(arr1) / sizeof(arr1[0]); int arr2[] = {0, 0, 0, 0, 0}; 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[] = {0}; int n5 = sizeof(arr5) / sizeof(arr5[0]); printf("=== Zero Counter for Sorted Binary Arrays ===\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); printf("Zeros (linear): %d\n", countZerosLinear(arr1, n1)); printf("Zeros (binary): %d\n", countZerosBinary(arr1, n1)); printf("Zeros (recursive): %d\n", countZerosWrapper(arr1, n1)); printf("\nArray 2 (all zeros): "); printArray(arr2, n2); printf("Zeros (linear): %d\n", countZerosLinear(arr2, n2)); printf("Zeros (binary): %d\n", countZerosBinary(arr2, n2)); printf("Zeros (recursive): %d\n", countZerosWrapper(arr2, n2)); printf("\nArray 3 (all ones): "); printArray(arr3, n3); printf("Zeros (linear): %d\n", countZerosLinear(arr3, n3)); printf("Zeros (binary): %d\n", countZerosBinary(arr3, n3)); printf("Zeros (recursive): %d\n", countZerosWrapper(arr3, n3)); printf("\nArray 4 (empty): "); printArray(arr4, n4); printf("Zeros (linear): %d\n", countZerosLinear(arr4, n4)); printf("Zeros (binary): %d\n", countZerosBinary(arr4, n4)); printf("Zeros (recursive): %d\n", countZerosWrapper(arr4, n4)); printf("\nArray 5 (single zero): "); printArray(arr5, n5); printf("Zeros (linear): %d\n", countZerosLinear(arr5, n5)); printf("Zeros (binary): %d\n", countZerosBinary(arr5, n5)); printf("Zeros (recursive): %d\n", countZerosWrapper(arr5, n5)); break; case 2: { int n; if (readInteger("Enter array size (0-100): ", &n) != 0) { fprintf(stderr, "Invalid size.\n"); break; } if (n < 0 || n > 100) { printf("Size must be 0-100.\n"); break; } int *customArr = malloc(n * sizeof(int)); if (customArr == NULL && n > 0) { fprintf(stderr, "Memory allocation failed.\n"); break; } printf("Enter %d elements (only 0s and 1s, sorted):\n", n); for (int i = 0; i < n; i++) { int value; char prompt[50]; snprintf(prompt, sizeof(prompt), " Element %d: ", i); if (readInteger(prompt, &value) != 0) { fprintf(stderr, "Invalid element.\n"); free(customArr); break; } if (value != 0 && value != 1) { printf("Only 0 and 1 allowed. Please try again.\n"); i--; continue; } /* Check sorted order */ if (i > 0 && value < customArr[i - 1]) { printf("Array must be sorted (all 0s before all 1s).\n"); i--; continue; } customArr[i] = value; } printf("\nCustom array: "); printArray(customArr, n); printf("Zeros (linear): %d\n", countZerosLinear(customArr, n)); printf("Zeros (binary): %d\n", countZerosBinary(customArr, n)); printf("Zeros (recursive): %d\n", countZerosWrapper(customArr, n)); free(customArr); break; } case 3: { printf("\n=== Performance Comparison ===\n"); /* Create a large array with half zeros, half ones */ int largeSize = 10000000; int *largeArr = malloc(largeSize * sizeof(int)); if (largeArr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); break; } /* Fill: first half zeros, second half ones */ for (int i = 0; i < largeSize / 2; i++) { largeArr[i] = 0; } for (int i = largeSize / 2; i < largeSize; i++) { largeArr[i] = 1; } clock_t start, end; /* Linear scan */ start = clock(); int linearResult = countZerosLinear(largeArr, largeSize); end = clock(); demonstrateApproach("Linear scan", linearResult, start, end); /* Binary search */ start = clock(); int binaryResult = countZerosBinary(largeArr, largeSize); end = clock(); demonstrateApproach("Binary search", binaryResult, start, end); /* Recursive binary search */ start = clock(); int recursiveResult = countZerosWrapper(largeArr, largeSize); end = clock(); demonstrateApproach("Recursive binary search", recursiveResult, 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; } /* Linear scan: O(n) */ int countZerosLinear(const int arr[], int n) { int count = 0; for (int i = 0; i < n; i++) { if (arr[i] == 0) { count++; } else { break; /* Since sorted, no more zeros after first 1 */ } } return count; } /* Iterative binary search: O(log n) */ int countZerosBinary(const int arr[], int n) { int left = 0; int right = n - 1; int firstOneIndex = n; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == 1) { firstOneIndex = mid; right = mid - 1; } else { left = mid + 1; } } return firstOneIndex; } /* Recursive binary search helper */ int countZerosRecursive(const int arr[], int left, int right) { if (left > right) { return left; /* Base case: first 1 index */ } int mid = left + (right - left) / 2; if (arr[mid] == 1) { return countZerosRecursive(arr, left, mid - 1); } else { return countZerosRecursive(arr, mid + 1, right); } } /* Wrapper for recursive binary search */ int countZerosWrapper(const int arr[], int n) { if (n == 0) return 0; return countZerosRecursive(arr, 0, n - 1); } /* Displays approach name, 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("%-25s: Result = %d, Time = %.3f ms\n", name, result, 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. countZerosLinear: O(n) linear scan, stops at first 1.
  2. countZerosBinary: O(log n) iterative binary search to find first 1.
  3. countZerosRecursive: O(log n) recursive binary search.
  4. countZerosWrapper: Handles empty array for recursive approach.
  5. Performance comparison: Demonstrates time difference on large arrays.

Expected Output (sample run)

text

=== Zero Counter for Sorted Binary Arrays === --- Menu --- 1. Demonstrate with Sample Arrays 2. Enter Custom Array 3. Performance Comparison 4. Exit Enter choice: 1 === Sample Arrays === Array 1: [0, 0, 0, 1, 1, 1] Zeros (linear): 3 Zeros (binary): 3 Zeros (recursive): 3 Array 2 (all zeros): [0, 0, 0, 0, 0] Zeros (linear): 5 Zeros (binary): 5 Zeros (recursive): 5 Array 3 (all ones): [1, 1, 1, 1] Zeros (linear): 0 Zeros (binary): 0 Zeros (recursive): 0 Array 4 (empty): [] Zeros (linear): 0 Zeros (binary): 0 Zeros (recursive): 0 Array 5 (single zero): [0] Zeros (linear): 1 Zeros (binary): 1 Zeros (recursive): 1 --- Menu --- ... Enter choice: 3 === Performance Comparison === Linear scan : Result = 5000000, Time = 8.420 ms Binary search : Result = 5000000, Time = 0.001 ms Recursive binary search : Result = 5000000, Time = 0.002 ms

Common Pitfalls, Edge Cases & Memory Considerations

1. Empty Array

Accessing arr[0] or arr[n-1] on an empty array is undefined behavior.

c

int countZeros(const int arr[], int n) { if (arr[0] == 1) return 0; // BUG: empty array if (arr[n - 1] == 0) return n; // BUG: empty array }

Solution: Check n == 0 first: if (n == 0) return 0;

2. Integer Overflow in Mid Calculation

(left + right) / 2 can overflow for large values.

c

int mid = (left + right) / 2; // Potential overflow

Solution: Use left + (right - left) / 2.

3. Off-by-One in Binary Search

Incorrect loop bounds can skip elements or cause infinite loops.

c

while (left < right) { // May miss the correct element // ... }

Solution: Use while (left <= right) for inclusive bounds.

4. Unsorted Input

The binary search assumes sorted input. Unsorted arrays will give incorrect results.

c

int arr[] = {1, 0, 1, 0}; // Not sorted countZerosBinary(arr, 4); // Incorrect result

Solution: Validate sorted order or sort first.

5. Non-Binary Values

Input containing values other than 0 and 1 breaks the algorithm’s assumptions.

c

int arr[] = {0, 0, 2, 3, 1}; // Invalid binary array

Solution: Validate elements are 0 or 1.

6. Recursive Depth

Recursive binary search depth is O(log n), which is safe for typical array sizes. No stack overflow risk for n up to millions.

7. clock() Precision

clock() measures CPU time and may not be precise for very fast operations.

Solution: Use larger arrays or repeat operations for measurable differences.

8. Memory Allocation Failure

Large arrays may fail to allocate memory.

c

int *arr = malloc(10000000 * sizeof(int)); if (arr == NULL) { // Always check fprintf(stderr, "Memory allocation failed\n"); }

Solution: Always check malloc return value.

9. Array Size Type

Using int for array size can overflow for very large arrays.

c

int n = 10000000000; // Overflow

Solution: Use size_t for array sizes.

10. Cache Performance

Linear scan has better cache locality for small arrays; binary search jumps around memory. For very small arrays, linear scan may be faster.

Solution: Use linear scan for small arrays (n < 100), binary search for large arrays.


Summary & Best Practices

Counting zeros in a sorted binary array is a classic problem that demonstrates the power of binary search. While linear scan is O(n), binary search achieves O(log n) by exploiting the sorted property.

Key Takeaways

  • Sorted binary array: all 0s followed by all 1s.
  • Linear scan: O(n), simple but slow for large arrays.
  • Binary search: O(log n), efficient for large arrays.
  • Count of zeros = index of first 1 = last zero index + 1.
  • Handle edge cases: empty array, all zeros, all ones.
  • Use left + (right - left) / 2 to avoid overflow.

Best Practices

  1. Use binary search for large arrays (n > 1000).
  2. Use linear scan for small arrays (n < 100) for simplicity.
  3. Check for empty array before accessing elements.
  4. Handle all-zeros and all-ones cases explicitly for optimization.
  5. Use size_t for array sizes instead of int.
  6. Avoid integer overflow in mid calculation.
  7. Validate input to ensure array is sorted and binary.
  8. Document the algorithm’s complexity in comments.
  9. Test with edge cases: empty, single element, all zeros, all ones.
  10. Prefer iterative over recursive for large arrays to avoid stack overhead.

By mastering this algorithm, you’ll understand binary search fundamentals and be prepared for variations like finding first/last occurrences, counting specific elements, and searching in rotated arrays.