news 2026/8/25 13:07:34

每日leetcode

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
每日leetcode

1563. 石子游戏 V - 力扣(LeetCode)

题目

几块石子排成一行,每块石子都有一个关联值,关联值为整数,由数组stoneValue给出。

游戏中的每一轮:Alice 会将这行石子分成两个非空行(即,左侧行和右侧行);Bob 负责计算每一行的值,即此行中所有石子的值的总和。Bob 会丢弃值最大的行,Alice 的得分为剩下那行的值(每轮累加)。如果两行的值相等,Bob 让 Alice 决定丢弃哪一行。下一轮从剩下的那一行开始。

剩下一块石子时,游戏结束。Alice 的分数最初为0

返回Alice 能够获得的最大分数

示例 1:

输入:stoneValue = [6,2,3,4,5,5]输出:18解释:在第一轮中,Alice 将行划分为 [6,2,3],[4,5,5] 。左行的值是 11 ,右行的值是 14 。Bob 丢弃了右行,Alice 的分数现在是 11 。 在第二轮中,Alice 将行分成 [6],[2,3] 。这一次 Bob 扔掉了左行,Alice 的分数变成了 16(11 + 5)。 最后一轮 Alice 只能将行分成 [2],[3] 。Bob 扔掉右行,Alice 的分数现在是 18(16 + 2)。游戏结束,因为这行只剩下一块石头了。

示例 2:

输入:stoneValue = [7,7,7,7,7,7,7]输出:28

示例 3:

输入:stoneValue = [4]输出:0

提示:

  • 1 <= stoneValue.length <= 500
  • 1 <= stoneValue[i] <= 106

思路

  1. 神了这题目写的真的有点迷惑,纯看文字没注意看括号都没发现是把一排石子分成左右两边,想了半天都没想到一个好一点的复杂度的解法,原来是divide不是select(跪了)。
  2. 题目理解了就能开始做了,一拍脑袋想不到什么哪种选择策略可以保证最终分数一定是最高的,所以只能无脑穷举了。
  3. 直接可以想到的方法就是做递归,每层递归做一件事情,遍历所以空,来一刀,然后左右分块继续向下递归,直到只剩一个数字为止,返回最后的总得分,然后取最大的一个即可(这个如果用python来做切片的话挺方便的,C++的话貌似还得勤勤恳恳赋一下值,偷个懒传引用就好了),代码如下:
  4. class Solution { public: int countSum(vector<int>& stoneValue, int start, int end) { int sum = 0; for(int i = start; i <= end; i++) sum += stoneValue[i]; return sum; } int getMaxScore(vector<int>& stoneValue, int start, int end) { if(start == end) return 0; int leftSum = 0, rightSum = 0, maxScore = 0, currentScore = 0, leftScores = 0, rightScores = 0; for(int i = start; i < end; i++) { leftSum = countSum(stoneValue, start, i); rightSum = countSum(stoneValue, i+1, end); currentScore = min(leftSum, rightSum); if(leftSum < rightSum) maxScore = max(maxScore, currentScore+getMaxScore(stoneValue, start, i)); else if(leftSum > rightSum) maxScore = max(maxScore, currentScore+getMaxScore(stoneValue, i+1, end)); else { maxScore = max(maxScore, currentScore+getMaxScore(stoneValue, start, i)); maxScore = max(maxScore, currentScore+getMaxScore(stoneValue, i+1, end)); } } return maxScore; } int stoneGameV(vector<int>& stoneValue) { return getMaxScore(stoneValue, 0, stoneValue.size()-1); } };
  5. 显然这样的方法随着递归层数的增加效果是非常差的,最差估计能接近O(2^n)。
  6. 可以注意到递归的过程中发生了多次的重复计算,根据智能分析的建议和较优复杂度情况,可以采用记忆化存储的方式,设置n*n的状态数组v,v[i][j]表示从i到j这个区间的最大得分,调用的过程中先查查这个得分是否被计算了,若已经被计算就直接赋值即可。
  7. class Solution { public: int countSum(vector<int>& stoneValue, int start, int end) { int sum = 0; for(int i = start; i <= end; i++) sum += stoneValue[i]; return sum; } int getMaxScore(vector<vector<int>>& partialValue,vector<int>& stoneValue, int start, int end) { if(start == end) { partialValue[start][end] = 0; return 0; } int leftSum = 0, rightSum = 0, maxScore = 0, currentScore = 0, leftScores = 0, rightScores = 0; for(int i = start; i < end; i++) { leftSum = countSum(stoneValue, start, i); rightSum = countSum(stoneValue, i+1, end); currentScore = min(leftSum, rightSum); if(leftSum < rightSum) { if(partialValue[start][i] == -1) partialValue[start][i] = getMaxScore(partialValue, stoneValue, start, i); maxScore = max(maxScore, currentScore+partialValue[start][i]); } else if(leftSum > rightSum) { if(partialValue[i+1][end] == -1) partialValue[i+1][end] = getMaxScore(partialValue, stoneValue, i+1, end); maxScore = max(maxScore, currentScore+partialValue[i+1][end]); } else { if(partialValue[start][i] == -1) partialValue[start][i] = getMaxScore(partialValue, stoneValue, start, i); maxScore = max(maxScore, currentScore+partialValue[start][i]); if(partialValue[i+1][end] == -1) partialValue[i+1][end] = getMaxScore(partialValue, stoneValue, i+1, end); maxScore = max(maxScore, currentScore+partialValue[i+1][end]); } } return maxScore; } int stoneGameV(vector<int>& stoneValue) { int len = stoneValue.size(); vector<vector<int>> partialValue(len, vector<int>(len, -1)); return getMaxScore(partialValue, stoneValue, 0, len-1); } };
  8. 结果还是超时了,最后分析之后发现countSum行为才是大量重复计算的根源,直接改成前缀和计算能节省大量的时间开销。

代码实现

class Solution { public: int countSum(vector<int>& stoneValue, int start, int end) { int sum = 0; for(int i = start; i <= end; i++) sum += stoneValue[i]; return sum; } int getMaxScore(vector<vector<int>>& partialValue,vector<int>& stoneValue, int start, int end) { if(start == end) { partialValue[start][end] = 0; return 0; } int leftSum = 0, rightSum = 0, maxScore = 0, currentScore = 0, leftScores = 0, rightScores = 0; rightSum = countSum(stoneValue, start, end); for(int i = start; i < end; i++) { leftSum += stoneValue[i]; rightSum -= stoneValue[i]; currentScore = min(leftSum, rightSum); if(leftSum < rightSum) { if(partialValue[start][i] == -1) partialValue[start][i] = getMaxScore(partialValue, stoneValue, start, i); maxScore = max(maxScore, currentScore+partialValue[start][i]); } else if(leftSum > rightSum) { if(partialValue[i+1][end] == -1) partialValue[i+1][end] = getMaxScore(partialValue, stoneValue, i+1, end); maxScore = max(maxScore, currentScore+partialValue[i+1][end]); } else { if(partialValue[start][i] == -1) partialValue[start][i] = getMaxScore(partialValue, stoneValue, start, i); maxScore = max(maxScore, currentScore+partialValue[start][i]); if(partialValue[i+1][end] == -1) partialValue[i+1][end] = getMaxScore(partialValue, stoneValue, i+1, end); maxScore = max(maxScore, currentScore+partialValue[i+1][end]); } } return maxScore; } int stoneGameV(vector<int>& stoneValue) { int len = stoneValue.size(); vector<vector<int>> partialValue(len, vector<int>(len, -1)); return getMaxScore(partialValue, stoneValue, 0, len-1); } };

复杂度分析

时间复杂度:因为会考虑所有区间,区间总数为n(n+1)/2=O(n^2),再加上每个区间会进行一次比那里,长度为O(L),不妨近似为O(n)——所以总的时间复杂度应该是O(n^3)。

空间复杂度:O(n^2)——引入了记忆化存储的n*n的数组,其余开销为栈开销,最坏是O(n)。

知识积累

  • vector初始化(以二层为例):vector<vector<int>> 变量名(外层长度, vector<int>(内层长度, 初始值));
  • vector初始化的另一种形式:变量名.assign(外层长度, vector<int>(内层长度))——估计默认值为0.
  • 记忆化存储——涉及大规模重复计算的时候,可以考虑使用空间换时间的方法,通过记忆化存储的方式减少计算量。

官方题解

  • 官解使用的是动态规划的方法,基础方法的逻辑基本上是一致的。
  • 不过题解有个优化的方案将时间复杂度从O(n^3)优化到了O(n^2),官解比较难懂所以参考了另外一个大佬的思路:
    • 如果左区间小于右区间的一个子集,那么它必然小于包含该子集的任意右区间的区间情况,那么这个时候就不必再重复进行计算工作了,直接赋值最后结果即可——相当于在做剪枝。
    • 所以可以直接计算所有n^2个的区间和,再通过大小比较和子集的包含情况来快速赋值,这样动态规划的过程中就不含O(L)的区间和计算了,只剩O(1)的加法运算。那么时间复杂度就可以降维成O(n^2)的预处理区间和计算和O(n^2)的动态规划,最后的时间复杂度就降维成O(n^2)了。官解的方法则是把预处理的逻辑插入了动态规划的计算中,通过剪枝的手段来实现优化,底层逻辑应该是差不多的。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 12:57:29

Agent Harness的演进过程

摘要本文分析了三个时代Agent发展过程来帮助理解Agent Harness形态的演进和未来方向在 Harness 1.0&#xff08;Bolt-On 时代&#xff09;&#xff0c;Harness 是外部脚手架—如 ReAct 这样的提示技术、AutoGPT 这样的完全模型自主循环&#xff0c;以及 Cursor/Copilot 这样的人…

作者头像 李华
网站建设 2026/8/25 12:50:38

WebSocket实时通信:从协议到实现

TL;DR 核心要点速览 Gin框架是Go最流行的Web框架 gRPC适合内部服务,REST适合对外API JWT Token是无状态认证的标准方案 Go标准库net/http可直接构建HTTP服务 Swagger/OpenAPI可自动生成API文档 本篇是Go Web开发模块,含完整项目代码 摘要:本文详细介绍从协议到实现,涵盖核心原…

作者头像 李华
网站建设 2026/8/25 12:46:12

控糖主食怎么选?认准低GI认证和工艺

国际糖尿病联合会2024年的数据摆在那儿&#xff0c;国内超4.6亿人有控糖刚需。想拿低GI大米替换白米饭&#xff0c;最怕买到普通糙米和杂豆随便混在一起的“伪轻食”。挑真正的全谷物轻食米当日常控糖主食&#xff0c;核心就一条&#xff1a;别管包装上“杂粮”两个字印得多大&…

作者头像 李华
网站建设 2026/8/25 12:44:16

我把B站收藏夹做成了个人知识库,自动总结视频要点为笔记

有个数据我特别有共鸣&#xff1a;B站收藏夹里躺着的视频&#xff0c;打开率基本接近零。我也是一样&#xff0c;看到好的网课、技术直播、行业分享&#xff0c;先收藏为敬&#xff0c;然后……就再也没打开过。收藏&#xff1d;学过&#xff0c;这种自我安慰&#xff0c;收藏得…

作者头像 李华
网站建设 2026/8/25 12:43:54

Go泛型入门:类型参数与约束

TL;DR 核心要点速览 Go编译型语言,执行速度比Python快10倍 Goroutine初始栈2KB,比线程轻100倍 Channel是Go并发通信的核心原语 GMP调度器自动管理goroutine调度 Go标准库覆盖HTTP/JSON/加密等常用场景 本篇是Go入门模块,建议按顺序学习 摘要:Go泛型入门,类型参数、约束(constr…

作者头像 李华
网站建设 2026/8/25 12:40:39

读数据可视化13空间标量场(上)

1. 空间数据1.1. Spatial Data1.2. 带有物理空间坐标的数据1.3. 标量场1.3.1. Scalar Fields&#xff0c;密度场1.3.2. 空间采样位置上记录单个标量的数据场1.3.3. 一维、二维和三维真实物理空间的标量场数据&#xff0c;其数据对象大多来源于科学计算和实验探测1.4. 当空间数据…

作者头像 李华