1

I am trying to figure out an optimal solution for the following problem:

Given a stack of 20 integers, sort the stack so that only values in range from 1 to 4 would remain in ascending order. You may only move one value at a time.

I have implemented an iterative & a recursive quick sort & now I'm looking for more optimal solutions. I have come up with the following so far, which runs on average approx 10x faster over 10k iterations. I am struggling to reason about space & time complexity & I'm sure there's plenty of room for a better solution.

Which sorts should I be considering? Which data types should I be considering? What's the best possible worse case time complexity O(?) for the given problem?

Stack<Integer> stack = new Stack<>();
stack.push(5);
stack.push(3);
stack.push(2);
stack.push(1);
stack.push(3);
stack.push(5);
stack.push(3);
stack.push(1);
stack.push(4);
stack.push(7);
        
HashMap<Integer, Integer> map = new HashMap<>();
map.put(1, 0);
map.put(2, 0);
map.put(3, 0);
map.put(4, 0);
        
int popped;
        
while (!stack.isEmpty()) {
    popped = stack.pop();
            
    if (map.containsKey(popped)) {
        map.put(popped, map.get(popped) + 1);
    }
}
        
while (map.get(4) > 0) {
    stack.push(4);
    map.put(4, map.get(4) - 1);
}
        
while (map.get(3) > 0) {
    stack.push(3);
    map.put(3, map.get(3) - 1);
}
        
while (map.get(2) > 0) {
    stack.push(2);
    map.put(2, map.get(2) - 1);
}
        
while (map.get(1) > 0) {
    stack.push(1);
    map.put(1, map.get(1) - 1);
}
1
  • move one value leaves too much room for interpretation: Move arbitrarily within stack? Move to a second stack? Just push & pop "the" (one&only) stack? Commented Oct 31, 2022 at 11:31

1 Answer 1

2

You're reinventing the Counting sort algorithm, which runs in linear time O(n).

Instead of a HashMap you can use a plain array. And you definitely don't need this swarm of redundant while-loops.

Here's the description of the algorithm in pseudocode:

function CountingSort(input, k)

count ← array of k + 1 zeros
output ← array of same length as input

for i = 0 to length(input) - 1 do      // STEP 1
    j = key(input[i])
    count[j] = count[j] + 1

for i = 1 to k do                      // we don't need this step
    count[i] = count[i] + count[i - 1]

for i = length(input) - 1 down to 0 do // STEP 2
    j = key(input[i])
    count[j] = count[j] - 1
    output[count[j]] = input[i]

return output

For your use-case you don't need the second for-loop where there the cumulative frequency is being calculated.

So to address the problem, we need to take the following step:

  • STEP 1 is to generate a histogram of frequencies.

  • STEP 2 is to iterate over frequencies backwards (i.e. starting from the very last index of the array) and push the values equal to index + minValue (in this case 1). Each value should be pushed the number of times equal to its frequency.

Here's an implementation:

final int min = 1;
final int max = 4;

int[] freq = new int[max - min + 1]; // histogram, i.e. array of frequencies of values in range [min, max]
        
// STEP 1: generating a histogram

while (!stack.isEmpty()) {
    int next = stack.pop();
    if (next >= min && next <= max) freq[next - min]++; // calculating the frequency of each element
}
    
// STEP 2: populating the stack in sorted order
        
for (int i = freq.length - 1; i >= 0; i--) {
    while (freq[i] > 0) {
        stack.push(i + min);
        freq[i]--;
    }
}

return stack;

If you want to use the HashMap, fine, you can apply the same algorithm, just don't create redundant loops.

Regarding the time complexity in both cases it would be linear as I've said before. Array would perform slightly better, but more importantly it gives you a better feeling of the internal mechanics of the algorithm.

final int min = 1;
final int max = 4;
        
Map<Integer, Integer> freq = new HashMap<>(); // histogram, i.e. array of frequencies of values in range [min, max]

// STEP 1: generating a histogram
        
while (!stack.isEmpty()) {
    int next = stack.pop();
    if (next >= min && next <= max) 
        freq.merge(next, 1, Integer::sum); // calculating the frequency of each element
}
    
// STEP 2: populating the stack in sorted order
    
for (int i = max; i >= min; i--) {
    int count = freq.get(i);
    while (count > 0) {
        stack.push(i + min);
        count--;
    }
}
        
return stack;

Sidenote: class java.util.Stack is legacy and not encouraged to be used. When you need an implementation of Stack data structure in Java, use classes that implement Deque interface.

Sign up to request clarification or add additional context in comments.

15 Comments

I should have explained myself better, its a plate stacking problem so I don't think counting sort would be suitable as you would need to lift multiple plates at a time?
@riverside96 How can remove multiple element from the stack at a time? I'm not quite understood this phrase. Can you share complete code? If you're asking is it possible to apply the counting sort to sorting the contents of the stack - then the answer is yes, you need to empty out the whole stack, walk the all steps of the algorithm and populate the stack again.
@riverside96 Or maybe the problem description is incomplete? If it's a coding challenge (or something like that), then please provide the original description.
In the counting sort the array values are sorted in place. If you imagine the array to be a pile of plates stacked vertically, sorting them in placer would not be possible if we can not move more than one plate at a time.
Sorry? I don't follow.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.