Back to blog
Dec 29, 2024
3 min read

Efficient Group Sorting: Employee Income Distribution

An exploration of an O(n·lg k) algorithm for dividing employees into income-based groups

We’re tasked with creating an algorithm that can sort n employees into k equal-sized groups (where k is a power of 2) based on their incomes. The interesting constraint here is that every person in a lower group must have a lower income than every person in any higher group. Think of it as creating distinct income brackets where there’s no overlap between the brackets.

Understanding the Constraints

Before diving into the solution, let’s clarify the key requirements:

  1. We have n employees (where n is a multiple of k)
  2. We need k groups (where k is a power of 2)
  3. Each group must be equal in size (n/k employees)
  4. There must be a strict ordering between groups
  5. The solution must run in O(n·lg k) time

The Solution: A Divide-and-Conquer Approach

DIVIDE-INTO-GROUPS(A, n, k)
    if k = 1
        return [A]
    
    mid_rank = (n * (k/2)) / k
    threshold = R-SELECT(A, 1, n, mid_rank)
    
    left = []
    right = []
    for i = 1 to n
        if A[i] ≤ threshold
            append A[i] to left
        else
            append A[i] to right
    
    left_groups = DIVIDE-INTO-GROUPS(left, n/2, k/2)
    right_groups = DIVIDE-INTO-GROUPS(right, n/2, k/2)
    
    return left_groups ∪ right_groups

Breaking Down the Algorithm

Let’s analyze how this algorithm works step by step:

The Base Case

When k = 1, we simply return the entire array as a single group. This forms our recursive base case.

Finding the Split Point

We use the R-SELECT algorithm to find the perfect income threshold that will divide our employees into two parts. This threshold ensures that everyone in the first part has a lower income than everyone in the second part.

The Partition Step

We partition the employees around this threshold, creating two smaller subproblems. This step maintains our crucial ordering property while setting up the recursive structure.

Recursive Division

The algorithm recursively applies the same process to each half, dividing them further until we reach our desired k groups. Since k is a power of 2, this creates a perfectly balanced binary tree of partitions.

Time Complexity Analysis

The efficiency of our algorithm comes from several key observations:

The R-SELECT algorithm runs in O(n) time on average, and we make lg k recursive calls. At each level of recursion, we process n elements in total.

This gives us our desired O(n·lg k) time complexity. Let’s break down why this is optimal:

  1. We can’t avoid looking at each employee at least once: O(n)
  2. We need lg k levels to divide into k groups
  3. The work at each level is linear in n