第十届蓝桥杯个人赛省赛JavaB组——第八题人物相关性分析(滑动窗口、双指针)
题目:
人物相关性分析
小明正在分析一本小说中的人物相关性。他想知道在小说中 Alice 和 Bob 有多少次同时出现。
更准确的说,小明定义 Alice 和 Bob "同时出现" 的意思是:在小说文本 中 Alice 和 Bob 之间不超过 KKK 个字符。
例如以下文本:
This is a story about Alice and Bob.Alice wants to send a private message to Bob.
假设 KKK = 20,则 Alice 和 Bob 同时出现了 2 次,分别是"Alice and Bob" 和 "Bob. Alice"。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。
注意:
-
Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。
-
Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能 有字母。例如 Bobbi 並不算出现了 Bob。
输入描述
第一行包含一个整数 K(1≤K≤106)K(1 \leq K \leq 10^6)K(1≤K≤106)。
第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超过 106 10^6106。
输出描述
输出一个整数,表示 Alice 和 Bob 同时出现的次数。
输入输出样例
示例
输入
20
This is a story about Alice and Bob.Alice wants to send a private message to Bob.
输出
2
- 最大运行时间:1s
- 最大运行内存: 512M
解题思路:一开始就想到,简单的纯暴力破解肯定是会超时的(时间复杂度会到达O(n^2)量级),所以就想看看有没有其他方法,后面看蓝桥B站中的老师讲到这题,说是可以用双指针来实现,我们可以通过一次遍历将Alice和Bob出现的索引都用两个集合存储起来,然后对每一个Alice进行遍历,遍历的同时,循环内部直接用双指针夹住【alice[i] - k - 3, alice[i] + k + 5】这个范围,因为对于当前第i + 1个Alice来说,只有在当前这个范围内才有可能和当前第i + 1个Alice同时出现。有了这思路之后,于是我就开始写代码了,最后在蓝桥官网的Oj上成功运行出来了。
Java代码:
import java.util.Scanner; import java.util.Vector; /** * @Author LZP * @Date 2021/3/12 12:33 * @Version 1.0 * 试题 H: 人物相关性分析 * 时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分 * 【问题描述】 * 小明正在分析一本小说中的人物相关性。他想知道在小说中 Alice 和 Bob * 有多少次同时出现。 * 更准确的说,小明定义 Alice 和 Bob“同时出现”的意思是:在小说文本 * 中 Alice 和 Bob 之间不超过 K 个字符。 * 例如以下文本: * This is a story about Alice and Bob. Alice wants to send a private message to Bob. * 假设 K = 20,则 Alice 和 Bob 同时出现了 2 次,分别是”Alice and Bob” * 和”Bob. Alice”。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。 * 注意: * 1. Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。 * 2. Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能 * 有字母。例如 Bobbi 並不算出现了 Bob。 * 【输入格式】 * 第一行包含一个整数 K。 * 第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超 * 过 1000000。 * 【输出格式】 * 输出一个整数,表示 Alice 和 Bob 同时出现的次数。 * 【样例输入】 * 20 * This is a story about Alice and Bob. Alice wants to send a private message to Bob. * 【样例输出】 * 2 * 【评测用例规模与约定】 * 对于所有评测用例,1 ≤ K ≤ 1000000。 */ public class Main { /** * alice和bob两个集合分别都是用来存储对应人物在输入字符串中每次出现的下标 */ private static Vector<Integer> alice = new Vector<>(); private static Vector<Integer> bob = new Vector<>(); private static int K; private static String data = null; public static boolean check(int i, int num, String peopleSuf, boolean isAlice) { // 看前面 if (i - 1 >= 0) { int preChar = data.charAt(i - 1); if ((preChar >= 65 && preChar <= 90) || (preChar >= 97 && preChar <= 122)) { // 如果是字母,就直接返回 return false; } } String nextFourChar; // 从当前字符开始算,看后第5个字符是否为字母 if (i + num < data.length()) { int nextFifthChar = data.charAt(i + num); if ((nextFifthChar >= 65 && nextFifthChar <= 90) || (nextFifthChar >= 97 && nextFifthChar <= 122)) { // 如果是字母,就直接返回 return false; } nextFourChar = data.substring(i + 1, i + num); } else { // 右边没有字符了,直接取到最后 // 查看后四个字符是否为lice nextFourChar = data.substring(i + 1); } if (nextFourChar.equals(peopleSuf)) { // 是Alice,记录此时的i索引 if (isAlice) { alice.add(i); } else { bob.add(i); } return true; } return false; } public static void main(String[] args) { Scanner input = new Scanner(System.in); K = Integer.parseInt(input.nextLine()); data = input.nextLine(); input.close(); // 1、先扫描一遍字符串,将其中的Alice和Bob出现的所有索引都记录下来 int index = 0; while (index < data.length()) { if (data.charAt(index) == 'A') { if (check(index, 5, "lice", true)) { index += 5; } else { index++; } } else if (data.charAt(index) == 'B') { if (check(index, 3, "ob", false)) { index += 3; } else { index++; } } else { index++; } } // for (int i = 0; i < alice.size(); i++) { // System.out.printf("%d ", alice.get(i)); // } // System.out.println(); // for (int i = 0; i < bob.size(); i++) { // System.out.printf("%d ", bob.get(i)); // } // System.out.println(); // 2、遍历每一个Alice,通过双指针去实现在每一个Alice[alice[i] - K - 3, alice[i] + K + 3]范围内有多少个,依次做累加 // 定义两个指针,初始都为0,两者都代表当前是的第几个Bob,只不过这里第1个Bob,下标是0 // 用于累加 long ans = 0; int left = 0, right = 0; // left和right是两个集合的下标,而取出来的也是下标,不过取出来的下标是在输入字符串中的下标 for (int i = 0; i < alice.size(); i++) { /* 每次取Bob的下标跟alice.get(i) - K - 3进行比较,外层循环是遍历每一个Alice,而循环内部是找出该Alice左边K范围内的Bob和 右边K范围内的Bob之和 */ // 以下两个while是用来确定当前Alice能够与Bob同时出现的最大范围,也就是left与right之间的距离 // 先固定住左边界,然后移动右边界 while (left < bob.size() && bob.get(left) < alice.get(i) - K - 3) { left++; } // 注意:这里的==一定要算在内 while (right < bob.size() && bob.get(right) <= alice.get(i) + K + 5) { right++; } // 此时bob.get(right) > alice.get(i) + K + 5 所以这里是要算right - 1与left之间有多少个数(包含两个边界) if ((right - 1) - left + 1 > 0) { // 判断距离是否大于零,若大于0,则表示当前的Alice对应的这个范围有与她同时出现的Bob ans += ((right - 1) - left + 1); } } System.out.println(ans); } }