Hello! Welcome to the next lesson in your journey to mastering Python for competitive programming.
In our last session, we focused on leveraging Python's powerful built-in sorting capabilities, using custom comparators and lambda functions. You learned that these methods are typically based on comparison sorts like Timsort, which have an excellent average-case performance of .
Today, we're going to explore a completely different approach that can, under the right circumstances, be even faster. We will be learning to apply Counting Sort for integer arrays with a limited range. This is a non-comparison-based algorithm that can achieve linear time complexity, making it a valuable and powerful tool in your competitive programming arsenal for specific types of problems.
What is Counting Sort and Why Use It?
All the sorting algorithms we've discussed so farโMerge Sort, Quicksort, and Python's built-in sortโare comparison-based. They work by comparing pairs of elements to decide their order. It's a proven fact in computer science that any comparison-based sorting algorithm has a lower bound of time complexity in the average and worst case.
Counting Sort breaks this barrier. It doesn't compare elements at all. Instead, it works by counting the number of occurrences of each unique element in the input array. This allows it to sort in linear time, but it comes with a major condition: it's only efficient for sorting integers that fall within a limited range.
For a great animated introduction to why Counting Sort is useful and the scenarios where it excels, watch the beginning of the following video.
Counting Sort explained with animations and example | Full implementation and code
This video from Nikhil Lohia provides an excellent visual introduction to Counting Sort, explaining its linear time complexity and the specific conditions under which it's most effective.
Watch from the beginning to 03:33. Focus on the core idea: instead of comparing elements, we use the values themselves as indices to count their frequencies.
How Counting Sort Works: A Step-by-Step Guide
The core idea is simple: if we know how many elements are less than a given element x, we can place x directly into its correct sorted position.
Let's break down the algorithm into two main versions: a simple, intuitive approach and a more robust, stable version. A stable sorting algorithm maintains the relative order of equal elements, which can be important in some problems.
Version 1: The Simple (In-place) Approach
This version is easy to understand and implement. It involves two main passes over the data.
- Count Frequencies: Create a
countarray to store the frequency of each number in the input array. To do this, you first need to find the maximum value in the input array to determine the size of thecountarray. - Reconstruct the Array: Iterate through the
countarray from the smallest index to the largest. For each numberi, appendito the resultcount[i]times. This overwrites the original array with the sorted elements.
This approach is demonstrated very clearly in the following video, which is geared towards competitive programming.
Sorting: Bubble, Insertion, Selection, Merge, Quick, Counting Sort - DSA Course in Python Lecture 10
In this video, Greg Hogg explains the mechanics of Counting Sort using a practical example and analyzes its complexity.
Watch from 00:30:32 to 00:34:14. Pay attention to how the 'counts' array is created and then used to rewrite the original array in sorted order. This demonstrates the simple, non-stable version of the algorithm.
While this method works for sorting simple integers, it is not stable. For example, if you were sorting objects based on an integer key, you wouldn't be able to preserve their original relative order if they had the same key. That's where the stable version comes in.
Version 2: The Stable Approach
This version is slightly more complex but is the standard, more powerful implementation. It ensures stability.
- Find the maximum value (
max_val) in the input array. - Create a
countarray of sizemax_val + 1and store the frequency of each element, just like before. - Calculate cumulative counts: Modify the
countarray so that each element at indexistores the sum of counts up toi. After this step,count[i]tells you the position where the last occurrence of elementishould go in the sorted output. - Build the output array: Create a new
outputarray of the same size as the input. Iterate through the input array in reverse. For each elementx, place it atoutput[count[x] - 1], and then decrementcount[x]. Iterating in reverse is the key to maintaining stability.
The Programiz article below provides excellent diagrams illustrating this process.
This article from Programiz provides a fantastic step-by-step visual breakdown of the stable Counting Sort algorithm.
Focus on the section 'Working of Counting Sort'. Carefully examine the four diagrams that show the transition from the initial array to the count array, then the cumulative count, and finally how the output array is constructed.
This image from the article perfectly captures the final step of placing an element into the output array using the cumulative counts.

Python Implementation
Now, let's translate the stable algorithm into Python code. The following implementation is clear and directly follows the steps we just outlined.
Counting Sort - CSU083 | Shoolini University
This resource from Shoolini University provides a well-commented Python implementation of the stable Counting Sort algorithm. It's broken down into steps that match our discussion.
Please read the section 'Basic Implementation in Python'. Note how each of the five steps (Find max, Create count array, Count occurrences, Transform to positions, Build output) is implemented in the code.
Here's the code from the article for your reference:
def counting_sort(arr):
# Step 1: Find the maximum value in the array
max_val = max(arr)
# Step 2: Create a count array of size (max_val + 1) initialized to zero
count = [0] * (max_val + 1)
# Step 3: Count occurrences of each element
for num in arr:
count[num] += 1
# Step 4: Transform count array to store actual positions (cumulative sum)
for i in range(1, len(count)):
count[i] += count[i - 1]
# Step 5: Build the sorted output array
output = [0] * len(arr)
# Iterate in reverse for stability
for num in reversed(arr):
output[count[num] - 1] = num
count[num] -= 1
return output
# Example usage
arr = [4, 2, 2, 8, 3, 3, 1]
sorted_arr = counting_sort(arr)
print("Sorted array:", sorted_arr)
# Output: Sorted array: [1, 2, 2, 3, 3, 4, 8]
Test your understanding!
Given the input array arr = [3, 1, 3, 4, 1] and using the stable counting_sort function above:
- What will the
countarray look like after Step 3 (counting occurrences)? - What will the
countarray look like after Step 4 (cumulative sum)?
Show answer
-
After Step 3 (Frequencies): The max value is 4, so the count array has size 5. The frequencies are:
count[0] = 0count[1] = 2(two 1s)count[2] = 0count[3] = 2(two 3s)count[4] = 1(one 4)
So,count = [0, 2, 0, 2, 1]
-
After Step 4 (Cumulative Sum):
count[0]remains0.count[1]becomescount[0] + count[1] = 0 + 2 = 2.count[2]becomescount[1] + count[2] = 2 + 0 = 2.count[3]becomescount[2] + count[3] = 2 + 2 = 4.count[4]becomescount[3] + count[4] = 4 + 1 = 5.
So,count = [0, 2, 2, 4, 5]
Complexity Analysis and Trade-offs
Understanding the performance of Counting Sort is key to knowing when to use it in a competition.
- Time Complexity: , where is the number of elements in the input array, and is the range of the input values (specifically,
max_val).- Finding the max:
- Counting frequencies:
- Calculating cumulative sums:
- Building the output array:
- Total: , which simplifies to .
- Space Complexity: . We need for the
countarray and for theoutputarray.
This complexity analysis reveals the algorithm's main strength and weakness. If is small or on the same order as (i.e., ), the time complexity is effectively , which is fantastically fast. However, if you have an array like [1, 10000000], then would be enormous, making the algorithm incredibly slow and memory-hungry. This makes it unsuitable for sorting data with a large range of values.
Counting Sort - CSU083 | Shoolini University
For a clear summary of these trade-offs and a helpful guide on when to choose Counting Sort, refer back to the Shoolini University resource.
Read sections 'Time & Space Complexity Analysis' and 'Trade-offs of Counting Sort'. The table in 'When to Use and When to Avoid' is particularly useful for competitive programming.
Conclusion
Great work today! You've added a non-comparison-based sorting algorithm to your toolkit. Counting Sort is a prime example of how understanding the properties of your data can lead to significant performance gains, a crucial insight for competitive programming.
Key Takeaways:
- Counting Sort is a non-comparison sorting algorithm that achieves linear time complexity, .
- It is only effective for sorting integers within a limited range (
k). - It works by counting element frequencies and using those counts to determine the final sorted positions.
- The stable version uses a cumulative count array and is generally preferred.
- Its primary weakness is high time and space complexity when the range of numbers (
k) is very large compared to the number of elements (n).
Up Next:
Now that you have a solid foundation in various sorting algorithms, we'll move on to a new class of techniques that often operate on sorted data. In our next lesson, we'll dive into the Two-Pointers and Sliding Window module, starting with how to "Apply the two-pointer technique to solve problems on sorted arrays." You'll see how a sorted array can be processed very efficiently using this clever pattern.