【华为OD机考B卷 | 100分】五子棋迷(JAVA题解——也许是全网最详)

news/2024/12/29 15:31:39/

前言

本人是算法小白,甚至也没有做过Leetcode。所以,我相信【同为菜鸡的我更能理解作为菜鸡的你们的痛点】。

题干

1. 题目描述

张兵和王武是五子棋迷,工作之余经常切磋棋艺。走了一会儿,轮到张兵了,他对着一条线思考起来了,这条线上的棋子分布如下:
用数组表示:-1 0 1 1 1 0 1 0 1 -1
棋子分布说明:

  1. -1代表白子,0代表空位,1代表黑子:
  2. 数组长度L,满足1<L<40,且L为奇数:

请帮他写一个程序,算出最有利的出子位置。最有利定义如下:

  1. 找到一个空位(0),用棋子(1/-1)填充该位置,可以使得当前子的最大连续长度变大;
  2. 如果存在多个位置,返回最靠近中间的较小的那个坐标;
  3. 如果不存在可行位置,直接返回-1;
  4. 连续长度不能超过5个(五子棋约束);

2. 输入描述

第一行:当前出子颜色
第二行:当前的棋局状态

3. 输出描述

一个整数,表示出子位置的数组下标

4. 示例

示例1:
输入:

1
-1 0 1 1 1 0 1 0 1 -1 1

输出:

5

说明:当前为黑子(1),放置在下表为5的位置,黑子的最大连续长度由3->5,所以满足条件

示例2:
输入:

-1
-1 0 1 1 1 0 1 0 1 -1 1

输出:

1

说明:当前为白子(-1),唯一可以防止的位置下标为1,婊子的最大长度由1->2,所以满足条件

示例3:
输入:

1
0 0 0 0 1 0 0 0 0 1 0

输出:

5

说明:可行的位置很多,但是5是最接近中间的位置坐标

解答

遇到的问题

这题整体难度不是很大,但还是遇到了一些问题。

  1. 如何从控制台输入一个变长的数组(哈,确实不会,没怎么用过Scanner
  2. 在实现过程中,其实我还发现,输入多行、不定长数据还存在无法停止输入的情况(只能通过输入非目标类型数据来停止,但我希望的是【回车】停止)
  3. 虽然脑海里大概能想到一个暴力解题办法,但总觉得怪怪的,应该有更好的方式才对

解题思路

暴力解题版:
根据题目给出的条件,做出如下思考:

  1. 新增一个方法,用来寻找【下一个空位】。由于是下一个,所以需要一个检索的起始点
  2. 新增一个方法,用来获取当前数组的中间位置的下标(其实可以不新增一个方法,单纯是个人喜欢这么做,我比较追求语义清晰跟代码可读性)
  3. 新增一个方法,用来计算给定线上棋子的【最大连续长度】
  4. 整体思路是,循环遍历每一个可以下子的点,然后记录当前点的位置,以及最大连续长度,还有距离中心点的位置。接着判断最大连续长度是否变大且<5。最后比较距离中心点的位置是否更近了

但我看了网上答案之后,发现了更好的解题方法。那就是【滑动窗口法】,这应该才是本题真正的考点。
滑动窗口法:

  1. 滑动窗口在这里的应用,主要是在求落子后新的最大连续长度。而且这个滑动起点跟终点,不是从0开始,而是从引起变化的【落子节点】开始。怎么理解?这个我们在代码解读中给大家解释一下。

滑动窗口示意图:(随着起点、终点变化,[start, end]区间也在变化)
在这里插入图片描述

代码示例

暴力破解版:但其实我不推荐,我贴出来完全是因为这是我个人自己的努力,所以为了鼓励自己就贴上,哈哈

public class FiveInRow {/*** 白棋*/static final int WHITE_CHESS = -1;/*** 黑棋*/static final int BLACK_CHESS = 1;/*** 空白*/static final int BLANK_SPACE = 0;/*** 最大连续长度*/static final int MAX_CONTINOUS_LENGTH = 5;/*** 数组长度*/static final int MAX_LENGTH = 40;public static void main(String[] args) {// 数据准备Scanner scanner = new Scanner(System.in);int currChess = scanner.nextInt();scanner.nextLine();String nextLine = scanner.nextLine();String[] nums = nextLine.split(" ");List<Integer> chesses = new ArrayList<>();for (String num : nums) {chesses.add(Integer.parseInt(num));}// 获取最有利下子位置int bestPlace = getBestPlace(currChess, chesses);System.out.println("最佳下子位置:" + bestPlace);}/*** 找到最有利的位置** @param currChess 当前棋子颜色* @param chesses   一条线上棋子分布* @return -1-找不到可以下的位置;其他-最有利下棋处*/private static int getBestPlace(int currChess, List<Integer> chesses) {int bestPlace = -1;// 获取当前连续长度int currContinuous = getCurrContinuous(currChess, chesses);int midLocation = getMidLocation(chesses.size());// 获取下一个空白点int nearest = 99;int start = 0;int nextBlankPlace = getNextBlankPlace(start, chesses);while (nextBlankPlace != -1) {List<Integer> newChesses = new ArrayList<>(chesses);newChesses.set(nextBlankPlace, currChess);int newCurrContinuous = getCurrContinuous(currChess, newChesses);if (newCurrContinuous > currContinuous && newCurrContinuous <= MAX_CONTINOUS_LENGTH) {int interval = Math.abs(nextBlankPlace - midLocation);if (interval < nearest) {bestPlace = nextBlankPlace;nearest = interval;}}start = nextBlankPlace + 1;nextBlankPlace = getNextBlankPlace(start, chesses);}return bestPlace;}/*** 获取当前连续度** @param currChess 当前棋子颜色* @param chesses   一条线上棋子分布* @return 当前连续长度*/private static int getCurrContinuous(int currChess, List<Integer> chesses) {// 当前棋局下最大连续长度int curMaxConsecutive = 0;// 当前连续长度int consecutiveCount = 0;for (int i = 0; i < chesses.size(); ++i) {if (chesses.get(i) == currChess) {consecutiveCount += 1;} else {consecutiveCount = 0;}curMaxConsecutive = Math.max(curMaxConsecutive, consecutiveCount);}return curMaxConsecutive;}/*** 从指定位置开始检索空白位置** @param start   开始检索位置* @param chesses 一条线上棋子分布* @return 下一个空白位置*/private static int getNextBlankPlace(int start, List<Integer> chesses) {if (start >= chesses.size()) {return -1;}for (int i = start; i < chesses.size(); i++) {Integer chess = chesses.get(i);if (chess == BLANK_SPACE) {return i;}}return -1;}/*** 获取中间位置的索引** @param length 数组长度* @return 中间位置的索引*/private static int getMidLocation(int length) {return length / 2;}
}

代码解读:整体来说我还是按照了解题思路完成的

  1. 多行不定长输入,我是这么完成的。我也不是很理解为什么,没有细看API源码
        // 数据准备Scanner scanner = new Scanner(System.in);int currChess = scanner.nextInt();scanner.nextLine();String nextLine = scanner.nextLine();String[] nums = nextLine.split(" ");List<Integer> chesses = new ArrayList<>();for (String num : nums) {chesses.add(Integer.parseInt(num));}

滑动窗口法:(推荐版本)
新增一个方法getBestPlaceUsingSlidingWindows替换旧方法getBestPlace

 /*** 找到最有利的位置,滑动窗口法** @param currChess 当前棋子颜色* @param chesses   一条线上棋子分布* @return -1-找不到可以下的位置;其他-最有利下棋处*/private static int getBestPlaceUsingSlidingWindows(int currChess, List<Integer> chesses) {int bestPlace = -1;// 获取当前连续长度int currContinuous = getCurrContinuous(currChess, chesses);int midLocation = getMidLocation(chesses.size());// 获取下一个空白点int nearest = 99;for (int i = 0; i < chesses.size(); i++) {Integer chess = chesses.get(i);if (chess != BLANK_SPACE) {continue;}int start = i - 1, end = i + 1;// 确定窗口真实起点while (start >= 0 && chesses.get(start).equals(currChess)) {start--;}// 确定窗口真实终点while (end < chesses.size() && chesses.get(end).equals(currChess)) {end++;}// 由于是开区间(不包含区间范围的两个节点),所以需要-1int newCurrContinuous = end - start - 1;if (newCurrContinuous > currContinuous && newCurrContinuous <= MAX_CONTINOUS_LENGTH) {int interval = Math.abs(i - midLocation);if (interval < nearest) {bestPlace = i;nearest = interval;}}}return bestPlace;}

我们解释回解题思路中,所谓【滑动起点跟终点,不是从0开始,而是从引起变化的【落子节点】开始】这段话,我们结合示例3看看,不知道大家发现没有,假设我们落子在0/1/2下标处,再求它的新的最大连续度的时候,其实真正引起连续度变化的点,就是我们落子开始的点。

例如:示例3,落子在2处
0 0 0 0 1 0 0 0 0 1 0(落子前)
0 0 1 0 1 0 0 0 0 1 0(落子后)
看,如果使用暴力解题的方法,还是会去遍历0/1坐标,有必要吗?其实可以不需要的

代码解读:滑动窗口在这里的应用,重点是里面的两个while循环,这里就不过多解释了


http://www.ppmy.cn/news/1142549.html

相关文章

利用互斥锁实现多个线程写一个文件

代码 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>FILE *fp;//线程函数1 void *wrfunc1(void *arg); //线程函数2 void *wrfunc2(void *arg); //线程函数3 void *wrfunc3(void *arg);//静态创建互斥锁 pthread_…

EM@指数函数和幂函数

文章目录 abstract指数函数基本性质 幂函数性质互为反函数的幂函数 refs abstract 指数函数和幂函数定义和性质 指数函数 一般地,函数 y a x ya^{x} yax, a > 0 , a ≠ 1 , x ∈ R a>0,a\neq{1},x\in{\mathbb{R}} a>0,a1,x∈R解析式中 a a a是非1的正数,不讨论负…

Vuex的介绍

介绍 :::warning 注意 在阅读此文章之前请确保你已经掌握了组件中的选项 data、计算属性 computed、methods 方法等相关知识。 ::: 什么是 Vuex&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以…

Springboot知识点必知必会(一)

mvc设计模式 MVC设计模式是Model-View-Controller的缩写&#xff0c;它是一种用于设计用户界面的软件设计模式。Spring MVC是Spring框架的一个模块&#xff0c;它提供了一种基于Java的方式来实现MVC设计模式。 以下是Spring MVC中MVC设计模式的组成部分和工作原理&#xff1a; …

小程序搭建的技巧|网站建设|软件定制APP开发

小程序搭建的技巧|网站建设|软件定制APP开发 首先&#xff0c;我们要知道小程序是一个非常方便的工具&#xff0c;它可以让我们在手机上运行一些应用&#xff0c;不用下载安装&#xff0c;非常方便。小程序可以分为两种&#xff1a;一种是代码开发&#xff0c;另一种是模板开发…

javaee SpringMVC中json的使用

jsp <%--Created by IntelliJ IDEA.User: 呆萌老师:QQ:2398779723Date: 2019/12/6Time: 15:55To change this template use File | Settings | File Templates. --%> <% page contentType"text/html;charsetUTF-8" language"java" %> <%St…

敏捷项目管理解锁:2023年使用YouTrack的全面指南

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

手机总是提醒系统更新,到底要不要更新呢?

不知道你们会不会和我一样&#xff0c;在收到手机系统更新的通知时&#xff0c;犹豫要不要更新? 更新完了手机会变卡顿吗? 每次都要更新吗?怎样才能关闭它呢&#xff1f; 01 手机系统更新啥内容? 手机系统更新可以分为大版本更新和小版本更新。 (1) 大版本更新 如iOS15升…