蓝桥杯算法训练 礼物(java,个人想法,递归找临界点)

问题描述

JiaoShou在爱琳大陆的旅行完毕,即将回家,为了纪念这次旅行,他决定带回一些礼物给好朋友。
在走出了怪物森林以后,JiaoShou看到了排成一排的N个石子。
这些石子很漂亮,JiaoShou决定以此为礼物。
但是这N个石子被施加了一种特殊的魔法。
如果要取走石子,必须按照以下的规则去取。
每次必须取连续的2K个石子,并且满足前K个石子的重量和小于等于S,后K个石子的重量和小于等于S。
由于时间紧迫,Jiaoshou只能取一次。
现在JiaoShou找到了聪明的你,问他最多可以带走多少个石子。

输入格式

  第一行两个整数N、S。
 
  第二行N个整数,用空格隔开,表示每个石子的重量。

输出格式

  第一行输出一个数表示JiaoShou最多能取走多少个石子。

样例输入

  8 3
 
  1 1 1 1 1 1 1 1

样例输出

6

数据规模和约定

  对于20%的数据:N<=1000
  对于70%的数据:N<=100,000
  对于100%的数据:N<=1000,000,S<=1012,每个石子的重量小于等于109,且非负

代码部分

话不多说,代码先奉上(代码长,主要是后面做了一些优化,可以看讲解,有进行拆分)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;public class Main {static int N, max = 0;static long S;public static void main(String[] args) throws IOException {//      用 streamTokenizer 会比 Scanner 效率高StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));streamTokenizer.nextToken();N = (int) streamTokenizer.nval;streamTokenizer.nextToken();S = (long) streamTokenizer.nval;
//      x【n】: 前 n 个 石块的重量总和long[] x = new long[N + 1];
//      sum: 用于统计前缀和long sum = 0;
//      从x【1】 开始 -  x【N】 方便后边归纳, x【0】 默认为 0for(int a = 1; a <= N; a++) {streamTokenizer.nextToken();sum += (long) streamTokenizer.nval;x[a] = sum;}
//      有多种开始的情况, 比如
//      0 - 10, 可能从0开始捡取, 也可能1 , 2 , 3for(int a = 1; a <= N - max; a++) {search(x, a, N, a - 1);}System.out.println(max * 2);}/*** * @param x 石块重量的前缀和* @param left 当前范围左边的点* @param right 当前范围右边的点* @param start 开始于哪个点(不包括那个点)*/private static void search(long[] x, int left, int right, int start) {//      特殊情况: 如果整个初始范围都 小于等于 S,即没有临界点
//      说明: 整个初始范围都可以一起捡走if(x[right] - x[start] <= S) {max = Math.max((right - start) / 2, max);return;}
//      计算中位数int mid = (left + right)/ 2;
//      找到临界点: 比如第0个石块, 到第n个石块,重量总和小于等于S, 并且第0个石块,到第n + 1个石块,重量总和大于S
//      那 n 就为临界点if(x[mid] - x[start] <= S&& x[mid + 1] - x[start] > S) {while(true) {//              计算长度int length = mid - start;
//              优化: 当长度小于等于当前记录的最高长度,
//              不需要再找了, 后面找到也不会大于记录的最高长度if(length <= max) {return;}
//              mid * 2 - start: 最右边的点if(mid * 2 - start <= N) {//                  如果右半区间也满足 小于等于 S, 说明该范围可以拾取if(x[mid * 2 - start] - x[mid] <= S) {max = Math.max(max, length);return;}}mid--;}}
//      递归终止条件if(mid == left) {return;}
//      没找到临界点, 如果当前大于 S, 则缩小范围, right = midif(x[mid] - x[start] > S) {search(x, left, mid, start);}
//      没找到临界点, 如果当前大于 S, 则缩小范围,left = midif(x[mid] - x[start] < S) {search(x, mid, right, start);}}
}

讲解

尽可能分解,我做题的每一步让大家理解。(因为不会做动画,只能勉强做一些ppt的动画让大家了解)

整体思路

在看到这题时, 我首先想到的是: 利用滑动窗口,首先设定好长度,然后一次次寻找
 
但遗憾的是:循环次数似乎过多,容易超时
 
后面,我更改了思路:

  • 首先(找临界点): 定义好初始位置, 找到 初始位置第n个石块 的重量前缀和, 刚好满足 <= S(要求的重量), 到 第 n + 1个石块, >= S

    • 正常循环寻找
  • 二分寻找

  • 具体代码

    x: 前缀和数组

    left:左边界

    right: 右边界

    start:数组下标开始的位置 - 1

    (引入start 为了确定从哪一块石头开始)

    比如

    原数组 : 1, 2, 3, 4, 5, 6, 7, 8

    前缀和数组:1, 3, 6, 10, 15, 21, 28, 36

    前缀和数组下标:1, 2, 3, 4, 5, 6, 7, 8

    ​ 可以从1开始取, 也可以从2- 7 开始取。

    ​ 如果从2 开始取, 就要把之前的前缀减去

    ​ x【2】 - x【1】

  • private static void search(long[] x, int left, int right, int start) {//      计算中位数int mid = (left + right)/ 2;
    //      找到临界点: 比如第0个石块, 到第n个石块,重量总和小于等于S, 并且第0个石块,到第n + 1个石块,重量总和大于S
    //      那 n 就为临界点if(x[mid] - x[start] <= S&& x[mid + 1] - x[start] > S) {//找到后做的内容}
    //      没找到临界点, 如果当前大于 S, 则缩小范围, right = midif(x[mid] - x[start] > S) {search(x, left, mid, start);}
    //      没找到临界点, 如果当前大于 S, 则缩小范围,left = midif(x[mid] - x[start] < S) {search(x, mid, right, start);}}
    
  • 找到临界点后,此时mid 左区域满足, 要判断mid 的右区域,此时右区域两种情况:

    • 满足 <= S
    • 不满足 <= S
  • 接下来对这两种情况,进行分别解决

    • 满足:获取长度length,max = Math.max(较大值, length) ,终止循环。

    • 不满足:mid–,继续本次循环

      (因为左半区域已经到临界点了,mid-- ,只会使左半区域远离临界点,即左半区域始终满足条件)

  • 具体代码

  •          while(true) {//              计算长度int length = mid - start;
    //              mid * 2 - start = mid + mid - start
    //              此处mid * 2 - start <= N 是为了防止下标超出数组if(mid * 2 - start <= N) {//                  如果右半区间也满足 小于等于 S, 说明该范围可以拾取if(x[mid * 2 - start] - x[mid] <= S) {max = Math.max(max, length);return;}}
    //              不满足,继续循环,mid--mid--;}
    

终止过程

这些已经帮助我们可以找到答案了,但存在两种没有临界点的特殊情况。

  1. 此次区域内都大于临界点
  2. 此次区域内都小于临界点

对于这两种情况, 我们要分别处理。

对于情况1

//       递归终止条件if(mid == left) {return;}

即二分法搜寻结束了还没找到,直接停止循环。

对于情况2

//       特殊情况: 如果整个初始范围都 小于等于 S
//      说明: 整个初始范围都可以一起捡走if(x[right] - x[start] <= S) {max = Math.max((right - start) / 2, max);return;}

整个函数整体

/*** * @param x 石块重量的前缀和* @param left 当前范围左边的点* @param right 当前范围右边的点* @param start 开始于哪个点(不包括那个点)*/private static void search(long[] x, int left, int right, int start) {if(x[right] - x[start] <= S) {max = Math.max((right - start) / 2, max);return;}int mid = (left + right)/ 2;if(x[mid] - x[start] <= S&& x[mid + 1] - x[start] > S) {while(true) {int length = mid - start;if(mid * 2 - start <= N) {if(x[mid * 2 - start] - x[mid] <= S) {max = Math.max(max, length);return;}}mid--;}}
//      递归终止条件if(mid == left) {return;}
//      没找到临界点, 如果当前大于 S, 则缩小范围, right = midif(x[mid] - x[start] > S) {search(x, left, mid, start);}
//      没找到临界点, 如果当前大于 S, 则缩小范围,left = midif(x[mid] - x[start] < S) {search(x, mid, right, start);}}

主函数分别传入不同初始点,获得max

     for(int a = 1; a <= N; a++) {search(x, a, N, a - 1);}

优化

​ 上面这些已经勉强可以完成,不超时(最长2s多),但为了完善,我们得做一些优化

优化一:减少传入初始点的次数

通过观察,我们发现当 剩余长度 < max, 就没必要传入。

比如 1,2,3,4,5, max = 3

这时候我再传入初始点3, 4, 5,他们整个范围小于 3,就算整个范围的石头都可以取,也小于max。

//      修改后的循环for(int a = 1; a <= N - max; a++) {search(x, a, N, a - 1);}

但这样时间,仅仅减少了200ms左右

优化二: 减少找到临界点后的循环搜索

通过观察,我们发现找到临界点后,我们得缓慢向前继续寻找,是否存在使右边区域满足的mid点。 这时,如果我们判断剩余范围如果小于 max, 就没必要具体再找到那个点了。(找到了,长度也不可能 >max)

         while(true) {//              计算长度int length = mid - start;
//              此处为优化: 当长度小于等于当前记录的最高长度,
//              不需要再找了, 后面找到也不会大于记录的最高长度if(length <= max) {return;}
//              mid * 2 - start: 最右边的点if(mid * 2 - start <= N) {//                  如果右半区间也满足 小于等于 S, 说明该范围可以拾取if(x[mid * 2 - start] - x[mid] <= S) {max = Math.max(max, length);return;}}mid--;}

虽然只加了一个判断,但效果却出奇的好

注意点

本题要求总共连续取多少石头,所以最后 max * 2 输出

总结

这样优化,其实差不多了。欢迎各位评论

蓝桥杯算法训练 礼物(java,个人想法,递归找临界点)相关推荐

  1. 蓝桥杯算法训练(java)--网络流裸题

    题目:一个有向图,求1到N的最大流 输入格式 第一行N M,表示点数与边数 接下来M行每行s t c表示一条从s到t的容量为c的边 先备知识与注意事项 考虑如下情境: 在某个污水处理厂的某一道程序里, ...

  2. 蓝桥杯 算法训练——礼物(二分法)Python

    这个博客是摆烂小白冲刺蓝桥杯国赛的算法笔记,呜呜因为太过摆烂现在六级.期末和国赛全在一起是真的会栓Q的好吗...我每次学习懂一题都会很开心,吃饭都香那种开心(因为太过小白),今天是六一祝大家六一快乐啊 ...

  3. 蓝桥杯算法训练 礼物 C++详解

    资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 JiaoShou在爱琳大陆的旅行完毕,即将回家,为了纪念这次旅行,他决定带回一些礼物给好朋友. 在走出了怪物森林以后,JiaoShou ...

  4. 蓝桥杯算法训练(java)--Anagrams问题前缀表达式大小写转换

    Anagrams问题 Anagrams指的是具有如下特性的两个单词:在这两个单词当中,每一个英文字母(不区分大小写)所出现的次数都是相同的.例如,"Unclear"和"N ...

  5. 蓝桥杯 算法训练-礼物

    问题描述 JiaoShou在爱琳大陆的旅行完毕,即将回家,为了纪念这次旅行,他决定带回一些礼物给好朋友. 在走出了怪物森林以后,JiaoShou看到了排成一排的N个石子. 这些石子很漂亮,JiaoSh ...

  6. 蓝桥杯 算法训练 提货单 Java

    一.题目 提货单 时间限制: 1Sec 内存限制: 128MB 题目描述 有一份提货单,其数据项目有:商品名(MC).单价(DJ).数量(SL).定义一个结构体prut,其成员是上面的三项数据.在主函 ...

  7. 蓝桥杯——算法训练——进击的青蛙

    问题描述 青蛙X正准备跳过一座桥,这座桥被划分为N段,记青蛙所在的起始点为0,桥的末端为N.桥上的一些点有一些石子,这些点是无法跳上去的.青蛙每次跳跃能向前跳跃+1,+2,+3段,现在请你算出跳到末端 ...

  8. 蓝桥杯——算法训练——数字三角形

    蓝桥杯--算法训练--数字三角形 这道题不难,但是比较典型,可以作为动态规划(dp)的入门篇,属于线性dp(LIS,LCS和数字三角形都是此类题型). ------------------------ ...

  9. 蓝桥杯算法训练-强力党逗志芃

    持续更新蓝桥杯算法训练题解,有兴趣可以关注一波呀 题目 逗志芃励志要成为强力党,所以他将身上所以的技能点都洗掉了重新学技能.现在我们可以了解到,每个技能都有一个前提技能,只有学完了前提技能才能学习当前 ...

最新文章

  1. Android下的动画
  2. Careercup - Google面试题 - 4699414551592960
  3. oracle+root+密码忘记,教您如何修复各种UNIX系统下root密码
  4. Java基础:多线程
  5. 详解CSS三大特性之层叠性、继承性和重要性——Web前端系列学习笔记
  6. 腾讯视频怎么开启禁止界面硬件加速
  7. youleb多风格响应式博客wordpress主题模板
  8. Google将推中文信息流产品,今日头条们请注意~
  9. 高斯公式,斯克托斯公式
  10. java随机抽取_java 随机抽取案例,不重复抽取
  11. 计算机组成原理——常用计算单位的转换(时间单位换算s、ms、μs;K、M、G为单位的数值大小)
  12. SDIO wifi Marvell8801/Marvell88w8801 介绍(二) ---- SDIO协议介绍
  13. 2020年下半年系统架构设计师下午真题及答案解析
  14. hpux oracle19c dbca DBT-05509 Failed To Connect To The Specified Database
  15. 电脑桌面计算机点开一直在刷新,Win10系统桌面一直在刷新怎么办
  16. visio付款流程图_职场人士常用的3款超好用流程图软件!
  17. 《乘风破浪的姐姐》的观后感,有钱真好!
  18. 手机游戏《魔塔》实现细节——(1)需求收集
  19. Libvirt — 使用 OvS 代替 Linux Bridge
  20. 来自wzc的简单拓扑dp———浙江农林大学第二十届程序设计竞赛暨团体程序设计天梯赛选拔赛(同步赛)

热门文章

  1. HSQLDb导出数据到Mysql
  2. 光照与渲染(九)- 光源类型
  3. jdbc:oracle:thin:@192.168.3.98:1521:orcl(简单介绍)
  4. oracle启动监听报 Instance “orcl“, status UNKNOWN, has 1 handler(s) for错误解决方案
  5. 清华梦的粉碎--写给清华大学的退学申请
  6. 安卓数据存储的5种方式
  7. 化工行业供应链系统解决方案
  8. Qt程序启动时报错0xc000007b解决办法
  9. 常量表达式和非常量表达式
  10. 独立后台全新菜谱外卖CPS微信小程序源码下载