LC1046. Last Stone Weight¶
Problem Description¶
LeetCode Problem 1046:
You are given an array of integers stones
where stones[i]
is the weight of the ith
stone.
We are playing a game with the stones. On each turn, we choose the
heaviest two stones and smash them together. Suppose the heaviest two stones have
weights x
and y
with x <= y
. The result of this smash is:
- If
x == y
, both stones are destroyed, and - If
x != y
, the stone of weightx
is destroyed, and the stone of weighty
has new weighty - x
.
At the end of the game, there is at most one stone left.
Return the weight of the last remaining stone. If there are no stones left, return 0
.
Clarification¶
- Choose the two heaviest stones
- Can the input be modified?
Assumption¶
-
Solution¶
Approach - Heap¶
Use Max Heap to heapify the stones array and pop up two stones at a time to smash until only one stone left:
- if two stones equal, continue
- if two stones different, push the diff to the max heap
Return stone if max heap has one stone otherwise 0
import heapq
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
stones_negative = [-1 * w for w in stones]
heapq.heapify(stones_negative) # (1)
# Smash two stones at a time until there is one or no stone left
while len(stones_negative) > 1:
weight_a = heapq.heappop(stones_negative)
weight_b = heapq.heappop(stones_negative)
if weight_a != weight_b:
heapq.heappush(stones_negative, weight_a - weight_b) # (2)
return -heapq.heappop(stones_negative) if len(stones_negative) > 0 else 0 # (3)
- Since python implements min heap, store negative weights to achieve max heap.
weight_a - weight_b
is negative sinceweight_a < weight_b
based on sequence of popping from min heap.- Convert the negative weight back to normal.
Complexity Analysis of Approach 1¶
- Time complexity: \(O(1)\)
- Heapifying the array takes \(O(n)\) time.
- It will iterating total \(n\) stones. Each iteration will perform at most 3 heap operations. The heap operation takes \(\log s\) where \(s\) is the heap size. The heap size is reduced by one from \(n\) to \(1\) after each iteration. So the time complexity is \(O(\log n) + O(\log (n - 1)) + \cdots + O(\log 1) \approx O(n \log n - n + 1) = O(n \log n)\).
- So the total time complexity is \(O(n) + O(n \log n) = O(n \log n)\).
- Space complexity: \(O(n)\)
- Define a new array takes \(O(n)\).
- The heap initially takes \(O(n)\) space and becomes 0 in the end.
- So the total space complexity is \(O(n) + O(n) = O(n)\).
How to evaluate \(\log n!\)
From the definition of factorial, we know \(\log n! = \log (1 \times 2 \times \cdots \times n) = \log 1 + \log 2 + \cdots + \log n = \sum_{k = 1}^n \log k\).
Approximating the sum as an integral: \(\sum_{k = 1}^n \log k \approx \int_1^n \log x \mathop{dx}\)
Computing the integral by using integration by parts: \(\int \log x \mathop{dx} = x \log x - \int x \mathop{d \log x} = x \log x - \int x \frac{\mathop{d \ln x}}{\ln 10} = x \log x - \int x \frac{1}{x} \frac{1}{\ln 10} \mathop{dx} = x \log x - \frac{x}{\ln 10}\)
So \(\sum_{k = 1}^n \log k \approx \int_1^n \log x \mathop{dx} = (n \log n - \frac{n}{\ln 10}) - (1 \log 1 - \frac{1}{\ln 10}) = n \log n - \frac{n - 1}{\ln 10} \approx n \log n\).