Say we have the following
unsortedArray = [4, 3, 4, 2]
Here is the algorithm:
Step 1) Iterate over the unsortedArray and use each element as the index into a new array we call countingArray. The value we will hold in each position is the count of times that that number appears. Each time we access a position we increment it by 1.
countingArray = [0, 0, 0, 0, 0, ..., 0, 0, 0, 0] // before iteration
countingArray = [0, 0, 0, 0, 1, ..., 0, 0, 0, 0] // after handling 4
countingArray = [0, 0, 0, 1, 1, ..., 0, 0, 0, 0] // after handling 3
countingArray = [0, 0, 0, 1, 2, ..., 0, 0, 0, 0] // after the second 4
countingArray = [0, 0, 1, 1, 2, ..., 0, 0, 0, 0] // after handling 2
We can allocate countingArray in advance because the range of the numbers we wish to sort is limited and known a-priori. In your example countingArray will have 101 elements.
Time complexity of this step is O(n) because you are iterating over n elements from unsortedArray. Inserting them into countingArray has constant time complexity.
Step 2) As shown in the example above countingArray is going to have positions with value 0 where there were no numbers to count in unsortedArray. We are going to skip these positions in the following iteration we will describe.
In countingArray non-zero positions define a number that we want to sort, and the content in that position define the count of how many times that number should appear in the final sortedArray.
We iterate over countingArray and starting at the first position of sortedArray put that number into count number of adjacent positions. This builds sortedArray and takes O(n).
countingArray = [0, 0, 1, 1, 2, ..., 0, 0, 0, 0]
// After skipping the first 2 0s and seeing a count of 1 in position 2
sortedArray = [2, 0, 0, 0]
// After seeing a count of 1 in position 3
sortedArray = [2, 3, 0, 0]
// In position 4 we have a count of 2 so we fill 4 in 2 positions
sortedArray = [2, 3, 4, 4]
=======
Total time complexity is O(n) * 2 = O(n)