Skip to content

Commit 531c8bf

Browse files
committed
LeetCode 1962. Remove Stones to Minimize the Total
1 parent 5623123 commit 531c8bf

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
354354
| [1832. Check if the Sentence Is Pangram][lc1832] | 🟢 Easy | [![python](res/py.png)][lc1832py] |
355355
| [1899. Merge Triplets to Form Target Triplet][lc1899] | 🟠 Medium | [![python](res/py.png)][lc1899py] |
356356
| [1926. Nearest Exit from Entrance in Maze][lc1926] | 🟠 Medium | [![python](res/py.png)][lc1926py] |
357+
| [1962. Remove Stones to Minimize the Total][lc1962] | 🟠 Medium | [![python](res/py.png)][lc1962py] |
357358
| [1971. Find if Path Exists in Graph][lc1971] | 🟢 Easy | [![python](res/py.png)][lc1971py] |
358359
| [1991. Find the Middle Index in Array][lc1991] | 🟢 Easy | [![python](res/py.png)][lc1991py] |
359360
| [1996. The Number of Weak Characters in the Game][lc1996] | 🟠 Medium | [![python](res/py.png)][lc1996py] |
@@ -1051,6 +1052,8 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
10511052
[lc1899py]: leetcode/merge-triplets-to-form-target-triplet.py
10521053
[lc1926]: https://leetcode.com/problems/nearest-exit-from-entrance-in-maze/
10531054
[lc1926py]: leetcode/nearest-exit-from-entrance-in-maze.py
1055+
[lc1962]: https://leetcode.com/problems/remove-stones-to-minimize-the-total/
1056+
[lc1962py]: leetcode/remove-stones-to-minimize-the-total.py
10541057
[lc1971]: https://leetcode.com/problems/find-if-path-exists-in-graph/
10551058
[lc1971py]: leetcode/find-if-path-exists-in-graph.py
10561059
[lc1991]: https://leetcode.com/problems/find-the-middle-index-in-array/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# 1962. Remove Stones to Minimize the Total
2+
# 🟠 Medium
3+
#
4+
# https://leetcode.com/problems/remove-stones-to-minimize-the-total/
5+
#
6+
# Tags: Array - Heap (Priority Queue)
7+
8+
import timeit
9+
from heapq import heapify, heapreplace
10+
from typing import List
11+
12+
13+
# Use a heap, ideally a maximum heap, but in Python we can use a minimum
14+
# heap with negated values to remove half of the stones from the largest
15+
# pile at each step.
16+
#
17+
# Time complexity: O(k*log(n)) - We copy and heapify the input array at
18+
# O(n), then iterate over it k times pushing and popping from the heap
19+
# at O(log(n)) cost.
20+
# Space complexity: O(n) - The values heap has the same size as the
21+
# input. If we can mutate the input, we could use that array and reduce
22+
# the complexity to O(1) extra memory.
23+
#
24+
# Runtime 1604 ms Beats 100%
25+
# Memory 28.6 MB Beats 44.43%
26+
class PriorityQueue:
27+
def minStoneSum(self, piles: List[int], k: int) -> int:
28+
vals = [-x for x in piles]
29+
heapify(vals)
30+
for _ in range(k):
31+
heapreplace(vals, vals[0] // 2)
32+
return -sum(vals)
33+
34+
35+
# Use bucket sort, create an array of size 10^4
36+
#
37+
# Time complexity: O(k*log(n)) - We copy and heapify the input array at
38+
# O(n), then iterate over it k times pushing and popping from the heap
39+
# at O(log(n)) cost.
40+
# Space complexity: O(n) - The values heap has the same size as the
41+
# input. If we can mutate the input, we could use that array and reduce
42+
# the complexity to O(1) extra memory.
43+
#
44+
# Runtime 1404 ms Beats 100%
45+
# Memory 28.3 MB Beats 92.80%
46+
class BucketSort:
47+
def minStoneSum(self, piles: List[int], k: int) -> int:
48+
# Create an array of buckets where we will place the number of
49+
# piles with that number of elements on them.
50+
buckets = [0] * 10001
51+
# The value of the greatest pile and the total sum.
52+
i = total = 0
53+
# Store the number of piles of each size on the buckets.
54+
for j in range(len(piles)):
55+
buckets[piles[j]] += 1
56+
if piles[j] > i:
57+
i = piles[j]
58+
total += piles[j]
59+
# Iterate back over the buckets staying at the biggest pile.
60+
while k and i > 1:
61+
# If we have cleared all piles size i, move onto the next
62+
# greatest size.
63+
if buckets[i] == 0:
64+
i -= 1
65+
continue
66+
# This is the number of elements we need to remove from the
67+
# current pile of size i.
68+
removed = i // 2
69+
# The size of the remaining pile after we remove the
70+
# elements is j.
71+
j = i - removed
72+
# After the update, we have one less pile of size i, one
73+
# more of size j.
74+
buckets[j] += 1
75+
buckets[i] -= 1
76+
# After the update we have `removed` less elements.
77+
total -= removed
78+
# We have consumed one update.
79+
k -= 1
80+
return total
81+
82+
83+
def test():
84+
executors = [
85+
PriorityQueue,
86+
BucketSort,
87+
]
88+
tests = [
89+
[[5, 4, 9], 2, 12],
90+
[[10000], 10000, 1],
91+
[[4, 3, 6, 7], 3, 12],
92+
]
93+
for executor in executors:
94+
start = timeit.default_timer()
95+
for _ in range(1):
96+
for col, t in enumerate(tests):
97+
sol = executor()
98+
result = sol.minStoneSum(t[0], t[1])
99+
exp = t[2]
100+
assert result == exp, (
101+
f"\033[93m» {result} <> {exp}\033[91m for"
102+
+ f" test {col} using \033[1m{executor.__name__}"
103+
)
104+
stop = timeit.default_timer()
105+
used = str(round(stop - start, 5))
106+
cols = "{0:20}{1:10}{2:10}"
107+
res = cols.format(executor.__name__, used, "seconds")
108+
print(f"\033[92m» {res}\033[0m")
109+
110+
111+
test()

0 commit comments

Comments
 (0)