Verstappen fell to eighth on the opening lap but charged through the order and caught Leclerc late on before winning a wheel-to-wheel battle to secure the victory.
However, his race-winning pass – which resulted in minor contact and pushed Leclerc to the run-off, denying Leclerc a first F1 win – led to a post-race investigation from the stewards.
Leclerc maintained his pole advantage, aided by the anti-stall kicking in on Verstappen’s Red Bull and dropping the Dutchman back to seventh at the first corner.
Verstappen then fell to eighth as teammate Pierre Gasly passed him around the outside at Turn 3, but he retook the position around the outside of the penultimate corner.
As Leclerc built a small lead over Bottas at the front, Sebastian Vettel and Verstappen worked their way up the order.
Vettel started ninth after an engine-related problem in qualifying but was up to fourth by lap six, and Verstappen had made it to fifth three laps later.
Despite being on harder tyres that Mercedes thought would be an advantage to Leclerc’s softs, Bottas was the first driver to pit on lap 21, with Vettel stopping the same lap.
Vettel lost a big chunk of time because a radio problem for Ferrari’s mechanics meant they did not get the instruction he was pitting.
Hamilton pitted on lap 30 after quickly losing a lot of time over the last couple of laps of his first stint thanks to front wing damage likely picked up when he ran wide over the sausage kerb at Turn 1.
The time loss and lengthy stop to change his wing meant Hamilton rejoined eight seconds behind Vettel despite the Ferrari driver’s own slow stop, and when Verstappen pitted one lap later he rejoined ahead of Hamilton as well.
While Leclerc held a lead of around four seconds out front, Verstappen – 12.9s off the lead in fourth after stopping – began his charge.
It took him 17 laps to chip away at Vettel’s deficit but once in DRS range he quickly mounted an attack.
Vettel repeatedly struggled to make the apex of the Turn 3 right-hander at the top of the hill and on lap 50 of 71 Verstappen took advantage with a good run out of the corner, and used DRS to get ahead into the braking zone and pass Vettel around the outside into Turn 4.
Once clear of the Ferrari, which immediately made a second stop for soft tyres and rejoined behind a muted Hamilton, Verstappen quickly caught Bottas.
Both Mercedes drivers’ pace was reduced because of temperature management, and Verstappen nailed Bottas for second on the run up to Turn 3 on lap 56 – having only just reported a brief loss of power.
That released him to try to catch Leclerc, six seconds up the road with 15 laps left.
By the time just five laps remained, Verstappen was on the Ferrari’s gearbox and attacked on the outside into Turn 3 and 4, but Leclerc just held on.
The next lap, Verstappen dived inside Leclerc with a superb move at Turn 3, but Leclerc just held on around the outside and was able to outdrag the Red Bull down to Turn 4.
With three laps ago Leclerc half covered the inside but Verstappen moved right to the edge of the track, forced Leclerc wide – and the two bumped wheels as Leclerc tried in vain to hang on again on the outside.
Leclerc took to the run-off and complained over the radio, while Verstappen accused his rival of turning in on him.
Verstappen was told to get his head down and escape, which he did – crossing the line almost three seconds clear.
Behind the top three, Vettel used his soft tyres to catch and overhaul Hamilton for fourth.
Championship leader Hamilton was limited to a muted fifth place, but only lost five points to teammate and title rival Bottas.
Lando Norris made up for his heartbreaking end to last week’s race in France by winning the best of the rest battle for McLaren in sixth.
Carlos Sainz executed a long first stint to rise to eighth from the back of the grid, while Alfa Romeo duo Kimi Raikkonen and Antonio Giovinazzi completed the point scorers – Giovinazzi finally banking his first F1 point in the process.