算法之 数组两端取数游戏

2024-03-19 14:20

本文主要是介绍算法之 数组两端取数游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目

  同学A与B玩取数游戏。即有一个2n项的数组,其中每个都是整数且对两位同学都是可见的,两位同学轮流从 两端 取走数字(假设A同学先取)。
胜负评判:所取数之和较大者获胜(可能存在平局)。

分析

  1. 如果题目问A同学的胜负情况,那可以直接回答胜或者平局,因为数组对两位同学都是可见的,都做出最佳决策的情况下肯定是先取者获胜,或者平局。
  2. 如果题目问A同学最后会比B同学多多少分。那么可以用递归求解,我们拿一个具体的例子,{1,3,30,4}  很显然不能用贪心策略,让A同学直接取当前两端的较大值,因为那样的话,很大的30会被B取走,所以可以看出来两次取数之间有关联。我们可以看出来A同学应该取1,然后B同学取4,然后A取30,B取3,最终A同学得分为31,B为7。

    sum( i , j ) 为A同学从数组 a 下标的 i 处到 j 处这个范围下采取最佳决策后的得分与B同学的差值,所以我们最后要求的是sum( 0 , 3 ),表示A同学的最大得分
    那么思考每一步,是取左边的数还是右边的数呢?这取决于 a[左] - sum( 左+1,右)
    a[右] - sum( 左, 右-1 ) 哪个大。这里要注意不是 + 号 而是 - 号,因为A同学取完之后是B同学取。还要注意递归的终止条件。
 public static int fun(int[] a, int i, int j) {if(i +1 == j) return Math.abs(a[i]-a[j]);int temp1 = a[i] - fun(a, i+1, j);int temp2 = a[j] - fun(a, i, j-1);return Math.max(temp1,temp2);}

  鉴于以上递归过程中会大量重复计算值,可能会使递归栈的深度过大导致栈溢出,而且降低了性能,于是考虑使用Dynamic Programming动态规划来解决。
  和一般DP问题一样,有个二维数组来保存记录,DP[n][n] 和以上sum( i , j )的意义一样,n是原数组 a 的大小。
递归表达式:

DP[i][i] =a[i]
DP[i][j] = max(a[i]-DP[i+1][j], a[j]-DP[i][j-1])

写成代码的话就是:

public static int fun2(int[] nums) {int n = nums.length;int[][] dp = new int[n][n];for(int i = 0; i < n; i++)dp[i][i] = nums[i];for(int i = 0; i < n-1; i++)for(int j = i+1; j < n; j++)dp[j-i-1][j] = Math.max(nums[j-i-1] - dp[j-i][j], nums[j] - dp[j-i-1][j-1]);return dp[0][n-1];}

这里要注意的是这个矩阵的填写方向,for循环还有点难写。第一轮运算图

彩蛋

 以上方法在很久之前都见过,也没什么新意。稍加改动就可以求出A,B同学具体取的数字,但是在最近看一本《常用算法与程序设计》的时候,发现了这个题还有更简单粗暴的方式,复杂度是O(n)
 先求出序列中奇数号整数之和S1,再求出偶数号整数之和S2.
那么 | S1 - S2 | 就是A,B同学最终得分的差值了。而且,A同学不是取全体奇数号项,就是取全体偶数项,这个值得动脑筋想想。
还是例子 {1,3,30,4},那么 S1 = 1+30 = 31, S2 = 2+4 = 6
那么要胜的A同学,先取奇数项,即1,然后剩下的能取的3和4都是偶数项,B会取较大的4,然后A继续取数,那么能取的一定是奇数项了。所以证明了取的数是全体奇数项。
由此可见,什么递归,动归,还是逻辑分析动脑筋最重要啊!

这篇关于算法之 数组两端取数游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/qq_37186947/article/details/90298869
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/826264

相关文章

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

Java中数组转换为列表的两种实现方式(超简单)

《Java中数组转换为列表的两种实现方式(超简单)》本文介绍了在Java中将数组转换为列表的两种常见方法使用Arrays.asList和Java8的StreamAPI,Arrays.asList方法简... 目录1. 使用Java Collections框架(Arrays.asList)1.1 示例代码1.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::