Start learning free

LESSON 313 OF 410 · HEAP

Last Stone Weight - Java Solution and Time Complexity Analysis

Practice O(n log n) Read the full explanation

We have a collection of stones, where each stone has a positive integer weight.

Each turn, we choose the two heaviest stones and smash them together.  Suppose the stones have weights x and y with x <= y.  The result of this smash is:

  • If x == y, both stones are totally destroyed;
  • If x != y, the stone of weight x is totally destroyed, and the stone of weight y has the new weight y-x.

At the end, there is at most 1 stone left.  Return the weight of this stone (or 0 if there are no stones left.)

 

Example:

Input: [2,7,4,1,8,1]
Output: 1
Explanation: 
We combine 7 and 8 to get 1 so the array converts to [2,4,1,1,1] then,
we combine 2 and 4 to get 2 so the array converts to [2,1,1,1] then,
we combine 2 and 1 to get 1 so the array converts to [1,1,1] then,
we combine 1 and 1 to get 0 so the array converts to [1] then that's the value of the last stone.
STEP 1 OF 8
    Ctrl + Enter to check

    JAVA · HEAP

    Last Stone Weight in Java, explained

    Understanding the Problem

    The core challenge of this problem is to repeatedly find and process the two heaviest stones until only one stone remains or all stones are destroyed. This problem is significant in scenarios where we need to repeatedly process the largest elements, such as in simulations or games.

    Potential pitfalls include not efficiently finding the two heaviest stones, which can lead to suboptimal performance.

    Approach

    To solve this problem, we need to efficiently find and remove the two heaviest stones. A naive approach would involve sorting the list of stones repeatedly, but this is not optimal. Instead, we can use a max-heap (priority queue) to always access the heaviest stones efficiently.

    Naive Solution

    The naive solution involves sorting the list of stones in descending order and then processing the two heaviest stones. This approach has a time complexity of O(n log n) for each iteration, which is not efficient for large inputs.

    Optimized Solution

    An optimized solution uses a max-heap (priority queue) to keep track of the heaviest stones. This allows us to efficiently extract the two heaviest stones and reinsert the resulting stone if necessary. The time complexity of this approach is O(n log n), but it is more efficient in practice due to the properties of the heap.

    Algorithm

    1. Insert all stones into a max-heap.

    2. While there is more than one stone in the heap:

    • Extract the two heaviest stones.
    • If they are not equal, insert the difference back into the heap.

    3. If there is one stone left, return its weight. Otherwise, return 0.

    Code Implementation

    import java.util.PriorityQueue;
    import java.util.Collections;
    
    public class LastStoneWeight {
        public int lastStoneWeight(int[] stones) {
            // Create a max-heap using a priority queue
            PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
            
            // Add all stones to the max-heap
            for (int stone : stones) {
                maxHeap.add(stone);
            }
            
            // Process the stones until one or none is left
            while (maxHeap.size() > 1) {
                // Extract the two heaviest stones
                int stone1 = maxHeap.poll();
                int stone2 = maxHeap.poll();
                
                // If they are not equal, add the difference back to the heap
                if (stone1 != stone2) {
                    maxHeap.add(stone1 - stone2);
                }
            }
            
            // Return the last stone's weight or 0 if no stones are left
            return maxHeap.isEmpty() ? 0 : maxHeap.poll();
        }
        
        public static void main(String[] args) {
            LastStoneWeight solution = new LastStoneWeight();
            int[] stones = {2, 7, 4, 1, 8, 1};
            System.out.println(solution.lastStoneWeight(stones)); // Output: 1
        }
    }
    

    Complexity Analysis

    The time complexity of the optimized solution is O(n log n), where n is the number of stones. This is because each insertion and extraction operation on the heap takes O(log n) time, and we perform these operations n times.

    The space complexity is O(n) due to the storage required for the heap.

    Edge Cases

    Potential edge cases include:

    • All stones have the same weight.
    • Only one stone is present.
    • No stones are present.

    These cases are handled by the algorithm as it naturally processes the stones until one or none is left.

    Testing

    To test the solution comprehensively, consider the following test cases:

    • Simple cases with a small number of stones.
    • Cases with all stones having the same weight.
    • Cases with a large number of stones to test performance.

    Using a testing framework like JUnit can help automate and validate these test cases.

    Thinking and Problem-Solving Tips

    When approaching such problems, consider the following tips:

    • Break down the problem into smaller, manageable parts.
    • Think about data structures that can optimize the solution.
    • Practice similar problems to improve problem-solving skills.

    Conclusion

    In this blog post, we discussed the problem of finding the last stone weight after repeatedly smashing the two heaviest stones. We explored a naive solution and an optimized solution using a max-heap. We also analyzed the complexity and discussed edge cases and testing strategies. Understanding and solving such problems is crucial for developing strong problem-solving skills in programming.

    Additional Resources

    For further reading and practice, consider the following resources:

    Keep going with JavaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free