LeetCode 1686. 石子游戏 VI【排序,贪心】【Py3,Go】2000

2024-02-03 09:12

本文主要是介绍LeetCode 1686. 石子游戏 VI【排序,贪心】【Py3,Go】2000,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

Alice 和 Bob 轮流玩一个游戏,Alice 先手。

一堆石子里总共有 n 个石子,轮到某个玩家时,他可以 移出 一个石子并得到这个石子的价值。Alice 和 Bob 对石子价值有 不一样的的评判标准 。双方都知道对方的评判标准。

给你两个长度为 n 的整数数组 aliceValuesbobValuesaliceValues[i]bobValues[i] 分别表示 Alice 和 Bob 认为第 i 个石子的价值。

所有石子都被取完后,得分较高的人为胜者。如果两个玩家得分相同,那么为平局。两位玩家都会采用 最优策略 进行游戏。

请你推断游戏的结果,用如下的方式表示:

  • 如果 Alice 赢,返回 1
  • 如果 Bob 赢,返回 -1
  • 如果游戏平局,返回 0

示例 1:

输入:aliceValues = [1,3], bobValues = [2,1]
输出:1
解释:
如果 Alice 拿石子 1 (下标从 0开始),那么 Alice 可以得到 3 分。
Bob 只能选择石子 0 ,得到 2 分。
Alice 获胜。

示例 2:

输入:aliceValues = [1,2], bobValues = [3,1]
输出:0
解释:
Alice 拿石子 0 , Bob 拿石子 1 ,他们得分都为 1 分。
打平。

示例 3:

输入:aliceValues = [2,4,3], bobValues = [1,6,7]
输出:-1
解释:
不管 Alice 怎么操作,Bob 都可以得到比 Alice 更高的得分。
比方说,Alice 拿石子 1 ,Bob 拿石子 2 , Alice 拿石子 0 ,Alice 会得到 6 分而 Bob 得分为 7 分。
Bob 会获胜。

提示:

  • n == aliceValues.length == bobValues.length
  • 1 <= n <= 10^5
  • 1 <= aliceValues[i], bobValues[i] <= 100

解法 排序+贪心

设 Alice 选的数字之和为 A A A,Bob 选的数字之和为 B B B

  • 如果 A − B > 0 A−B>0 AB>0 那么 Alice 赢;
  • 如果 A − B < 0 A−B<0 AB<0 那么 Bob 赢;
  • 如果 A − B = 0 A-B=0 AB=0 则平局。
  • 所以 Alice 需要最大化 A − B A−B AB ,Bob 需要最小化 A − B A-B AB

下文把数组 aliceValues \textit{aliceValues} aliceValues 记作 A A A,把数组 bobValues \textit{bobValues} bobValues 记作 B B B

a = [ 2 , 4 , 3 , 5 ] , b = [ 1 , 6 , 7 , 1 ] a=[2,4,3,5], b=[1,6,7,1] a=[2,4,3,5],b=[1,6,7,1] 为例说明。

假设 Bob 把所有石子都拿走,则 A = 0 , B = 15 , A − B = − 15 A=0,\ B=15,\ A−B=−15 A=0, B=15, AB=15

先来想一想,如果 Alice 只能拿走一颗石子,应该拿走哪颗呢?

  1. 拿走第一颗,那么 A = 2 , B = 14 , A − B = − 12 A=2,\ B=14,\ A-B=-12 A=2, B=14, AB=12
  2. 拿走第二颗,那么 A = 4 , B = 9 , A − B = − 5 A=4,\ B=9,\ A-B=-5 A=4, B=9, AB=5
  3. 拿走第三颗,那么 A = 3 , B = 8 , A − B = − 5 A=3,\ B=8,\ A-B=-5 A=3, B=8, AB=5
  4. 拿走第四颗,那么 A = 5 , B = 14 , A − B = − 9 A=5,\ B=14,\ A-B=-9 A=5, B=14, AB=9

对比 Bob 把所有石子都拿走的情况,如果 Alice 拿走第二颗或者第三颗,都可以让 − 15 -15 15 增大为 − 5 -5 5 ,增量为 10 10 10 。由于 A A A 增加了 a [ i ] a[i] a[i] B B B 减少了 b [ i ] b[i] b[i] ,所以 A − B A−B AB 的增量等于
a [ i ] − ( − b [ i ] ) = a [ i ] + b [ i ] a[i] - (-b[i]) = a[i] + b[i] a[i](b[i])=a[i]+b[i]
所以 Alice 拿走 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i] 最大的石子最优。

回到原题,Alice 可以拿走两颗石子,应该拿走哪两颗呢?

我们从 Bob 把所有石子都拿走的情况出发,也就是在 A = 0 , B = 15 A=0,\ B=15 A=0, B=15 的基础上思考,Alice 拿走哪两颗石子,可以让 A − B A-B AB 增加的尽量多?

定义 c [ i ] = a [ i ] + b [ i ] c[i]=a[i]+b[i] c[i]=a[i]+b[i] ,那么 c = [ 3 , 10 , 10 , 6 ] c=[3,10,10,6] c=[3,10,10,6] 。现在问题变成:给定数组 c c c ,Alice 每回合拿走一个数,Bob 每回合删除一个数,Alice 拿走的数之和最大是多少?注意 Bob 要让 Alice 拿走的数之和尽量小。

如此转换后,贪心策略就很显然了:Alice 从大到小拿走数字,Bob 也从大到小删除数字。

所以把 c c c 从大到小排序为 [ 10 , 10 , 6 , 3 ] [10,10,6,3] [10,10,6,3] ,两人从左往右交替取数,那么 Alice 只能拿走下标为偶数的数字,其余数字被 Bob 删除。所以 A − B A-B AB 最大可以增加 c [ 0 ] + c [ 2 ] = 10 + 6 = 16 c[0]+c[2]=10+6=16 c[0]+c[2]=10+6=16增加后 A − B = 1 > 0 A-B=1>0 AB=1>0 ,Alice 险胜!

算法
把数组按照 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i] 从大到小排序。可以创建一个 ( a [ i ] , b [ i ] ) (a[i],b[i]) (a[i],b[i])pair 数组对其排序,也可以创建一个下标数组排序。

diff \textit{diff} diff 表示 A − B A-B AB ,初始化 diff = 0 \textit{diff}=0 diff=0 。遍历数组,把偶数下标的 a [ i ] a[i] a[i] 加到 A A A 中,相当于 d i f f diff diff 增加了 a [ i ] a[i] a[i] ;把奇数下标的 b [ i ] b[i] b[i] 加到 B B B 中,相当于 diff \textit{diff} diff 减少了 b [ i ] b[i] b[i]

循环结束后,如果 d i f f > 0 diff>0 diff>0 ,返回 1 1 1 ;如果 d i f f < 0 diff<0 diff<0 ,返回 − 1 −1 1 ;如果 d i f f = 0 diff=0 diff=0 ,返回 0 0 0

问:从这个思路的本质是什么?为什么这样转换一下,问题就变得简单了许多?
答:转换前,我们需要同时考虑 a [ i ] a[i] a[i] b [ i ] b[i] b[i] 这两个变量,不好处理。转换成 Bob 先把所有数字选了,我们就只需要思考 Alice 如何选数字,只有 c [ i ] c[i] c[i] 这一个变量,更容易处理。从某种程度上来说,这也可以算作一种「正难则反」。

问:有没有其它的思考方式?
答:也可以这样思考:对比两颗石子 ( a [ i ] , b [ i ] ) (a[i],b[i]) (a[i],b[i]) ( a [ j ] , b [ j ] ) (a[j],b[j]) (a[j],b[j]) 。如果 Alice 选 i i i ,Bob 选 j j j ,那么 A − B = a [ i ] − b [ j ] A−B=a[i]−b[j] AB=a[i]b[j] ;如果 Alice 选 j j j ,Bob 选 i i i ,那么 A − B = a [ j ] − b [ i ] A−B=a[j]−b[i] AB=a[j]b[i] 。如果 Alice 选 i i i 更优,则有 a [ i ] − b [ j ] > a [ j ] − b [ i ] a[i]−b[j]>a[j]−b[i] a[i]b[j]>a[j]b[i] ,即 a [ i ] + b [ i ] > a [ j ] + b [ j ] a[i]+b[i]>a[j]+b[j] a[i]+b[i]>a[j]+b[j] ,说明 Alice 应当优先选 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i] 更大的石子。

# python
class Solution:def stoneGameVI(self, a: List[int], b: List[int]) -> int:pairs = sorted(zip(a, b), key = lambda p: -(p[0] + p[1])) # a[i]+b[i]越大排在前diff = sum(x if i % 2 == 0 else -y for i, (x, y) in enumerate(pairs)) # 表示A-Breturn (diff > 0) - (diff < 0)class Solution:def stoneGameVI(self, a: List[int], b: List[int]) -> int:s = sorted((x + y for x, y in zip(a, b)), reverse = True)diff = sum(s[::2]) - sum(b)return (diff > 0) - (diff < 0)
func stoneGameVI(a []int, b []int) int {type pair struct { x, y int }pairs := make([]pair, len(a)) // pair数组for i, x := range a {pairs[i] = pair{x, b[i]}}slices.SortFunc(pairs, func(p, q pair) int {return q.x + q.y - (p.x + p.y)})diff := 0for i, p := range pairs {if i % 2 == 0 {diff += p.x} else {diff -= p.y}}return cmp.Compare(diff, 0)
}

复杂度分析:

  • 时间复杂度: O ( n log ⁡ n ) \mathcal{O}(n\log n) O(nlogn) ,其中 n n n a a a 的长度。瓶颈在排序上。
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n)

这篇关于LeetCode 1686. 石子游戏 VI【排序,贪心】【Py3,Go】2000的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/673653

相关文章

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Barn Repair(贪心)

思路:用上M块木板时有 M-1 个间隙。目标是让总间隙最大。将相邻两个有牛的牛棚之间间隔的牛棚数排序,选取最大的M-1个作为间隙,其余地方用木板盖住。 做法: 1.若,板(M) 的数目大于或等于 牛棚中有牛的数目(C),则 目测 给每个牛牛发一个板就为最小的需求~ 2.否则,先对 牛牛们的门牌号排序,然后 用一个数组 blank[ ] 记录两门牌号之间的距离,然后 用数组 an

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的

leetcode-24Swap Nodes in Pairs

带头结点。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) { val = x; }* }*/public class Solution {public ListNode swapPairs(L

leetcode-23Merge k Sorted Lists

带头结点。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) { val = x; }* }*/public class Solution {public ListNode mergeKLists