本人是二本院校大二的计算机系学生,已经报名了下一届的蓝桥杯省赛,整个寒假学习了很多算法知识,我是看《算法很美》这个课程学习算法的,一套学习下来确实受益匪浅,视频在b站上面都有。
此前已经刷了两套真题,分别为:
2017年第八届蓝桥杯JavaB组省赛 刷题笔记、思路及答案
2016年第七届蓝桥杯JavaB组省赛 刷题笔记、思路及答案
注意:我是以我自身的水平来对题目进行分析的,所以可能代表了多数水平和我相当的朋友的观点,但不代表官方解法(特殊说明除外)。所以如果代码设计题有答案上的错误,还请各位大佬指正哈哈。接下来就来看看这次的第九届蓝桥杯JavaB组省赛的刷题笔记、思路及答案。
第一题:第几天(已完成)
第二题:方格计数(已完成)
第三题:复数幂(已完成)
第四题:测试次数
第五题:快速排序(已完成)
第六题:递归三元组(已完成)
第七题:螺旋折线(已完成)
第八题:日志统计(已完成)
第九题:全球变暖(已完成)
第十题:堆的计数
【题目】
2000年的1月1日,是那一年的第1天。
那么,2000年的5月4日,是那一年的第几天?
注意:需要提交的是一个整数,不要填写任何多余内容。
【思路】
第一题往往是送分题,这个计算时间的题目可以有很多种解法。
1、简单粗暴法:
2000年是闰年,2月有29天,1月、3月、4月分别是:31天、31天、30天。因此总天数是31+29+31+30+4 = 125天
2、excel快速计算:
我是用excel计算的,excel对于日期时间等功能的计算非常的方便,在A1格子写上末尾日期,然后A2写上初始日期,然后写公式让两个格子相减,即能获得天数。如下图:
值得注意的是,关于两个数值的相减,一定要检查看看结果有没有多1或少1,为了测试excel减法的情况,我们举个例子:
很明显,第一天如果是1月1日,那么1月3日应该是第3天,也就是说excel计算出来的答案需要+1,也就是正确答案为125.
3、编程法:
还得写一个for循环,判断日期,判断闰平年,多麻烦,还不如以上两种方法计算,更简单精确。所以这里就不演示编程法了。
【正确答案】
125
【题目】
如图p1.png所示,在二维平面上有无数个1x1的小方格。
我们以某个小方格的一个顶点为圆心画一个半径为1000的圆。
你能计算出这个圆里有多少个完整的小方格吗?
【思路】
常规做法:
1、首先我们要简化问题,计算包含在圆圈内的格子数目,我们只需要计算以圆心为原点的坐标轴的第一象限(也就是1/4个圆内)的正方形个数即可,然后把算出来的结果×4即可。
2、然后使用两层for循环,遍历1/4圆中的每一个格子(遍历范围=半径×半径),利用勾股定理计算这个格子是否满足在圆内(如下图),也就是判断这个格子离圆心的最远距离L是否小于等于半径R(L=格子右上角那个点到圆心的距离)。如果满足,count++;
在上图中,红色箭头满足 :2² + 1² <= R² (R=3),因此这个格子在圆内,计数+1;
蓝色箭头不满足 :3² + 1² <= R²(R=3),因此这个格子不在圆内,继续下一次循环。
3、遍历完两层for循环,输出计数count*4即可。
优化做法:
这个做法是我仔细想了之后做出来的能减少for循环次数的方法,基本原理和上面的差不多。
1、还是只看1/4个圆,然后我们可以发现,黑色涂黑部分是一定在圆内的,这一部分可以直接算出来,而不用for循环一个个遍历,省下很多时间。
设黑色区域的边长为x,则一定满足 x² + x² < R²(从图上也能看出来),x < √(R²/2), 这里的√(R²/2)相对比x大一点点,大出来的这一点一定是小数部分,所以去掉小数部分,就是x的值。
2、随后我们只需遍历绿框所示的部分,大大减少了循环次数。实际上我们只需要计算其中一个绿框的部分然后×2即可。
【参考答案】
public class test
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int r = sc.nextInt();
//预计算,减少循环次数 r^2 = x^2+x^2 --> x = (int)√(r^2 / 2)
int x = (int)Math.sqrt((r*r) / 2); //一定在圆内的正方形格子边长
int count = x*x;
// System.out.println(x);//测试
int y = 1; //阶数,纵向指针
int i = x + 1; //横向指针,用于遍历(绿框内)每个格子
//这个循环只是遍历绿框部分
while(y <= x) //i横向(向右)指针,y纵向(向上)指针,当y超过了绿框部分,就跳出循环
{
if(i*i + y*y <= r*r) //关键语句,就算遍历了多余的格子,只要能进入这个判断,就能确定该格子是否在圆内。
{
count += 2; //因为对称性(计算两个绿框部分)
i ++; //向右递增
}
else
{
y ++; //向上递增
i = x + 1;
}
}
System.out.println(count*4);
}
}
答案经过测试,和网上大佬的答案是相同的。
【题目】
设i为虚数单位。对于任意正整数n,(2+3i)^n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少? 即(2+3i)的123456次幂,这个数字很大,要求精确表示。
答案写成 "实部±虚部i" 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。(2+3i)^2 写成: -5+12i,
(2+3i)^5 的写成: 122-597i
注意:需要提交的是一个很庞大的复数,不要填写任何多余内容。
【思路】
首先这道题虽然只是计算123456次方,理论上说使用for循环执行123456也可以做出来,但是由于数据过于庞大,运行肯定会超时。所以需要尝试简化循环次数,所以考虑使用快速幂。如果不懂快速幂,建议百度学习一下,不难。简单来说就是使用递归、二分法来快速计算乘方。
此外,由于数据庞大,所以需要使用BigInteger类,BigInteger类可以表示无限大的整数。
另外计算复数的乘方时,要把虚部和实部分开来算,怎么分开算?其实很简单:
(a + bi)*(c + di) = ac +adi + bci - bd
=(ac - bd)+(ad + bc)i
两个复数相乘,复数A的实部a,虚部b,复数B的实部c,虚部d,相乘后,得到新的复数C的实部(ac - bd),虚部(ad + bc)。
实部和虚部用一个数组存储即可,这个数组就是代表了这个复数。
【参考答案】
package test;
import java.math.BigInteger;
public class A3
{
static BigInteger[] ini = {new BigInteger("2"), new BigInteger("3")};
//快速幂,计算a^n
public static BigInteger[] quick(BigInteger[] a, int n)
{
if(n == 1)
return ini;
if(n % 2 == 0) //如果n是偶数,返回a^(n/2) * a^(n/2)
return mul(quick(a, n / 2), quick(a, n / 2));
else //如果n是奇数,返回a^(n/2) * a
return mul(quick(a, n - 1), ini);
}
//两个复数相乘,返回新的复数
public static BigInteger[] mul(BigInteger[] a, BigInteger[] b)
{
// shi = a[0] * b[0] - a[1] * b[1]
// xu = a[0] * b[1] + a[1] * b[0]
BigInteger[] t = new BigInteger[2];
t[0] = a[0].multiply(b[0]).subtract(a[1].multiply(b[1]));
t[1] = a[0].multiply(b[1]).add(a[1].multiply(b[0]));
return t;
}
public static void main(String[] args)
{
BigInteger[] one = quick(ini, 123456); //计算ini的123456次方
System.out.printf("%d%di",one[0],one[1]);
}
}
代码经过测试,与网上的答案一致。
由于已经提前知道最终结果的实部和虚部都是负数,所以直接打印("%d%di")即可。如果说虚部是正数,才需要打印("%d+%di")。
【题目】
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
请填写这个最多测试次数。
注意:需要填写的是一个整数,不要填写任何多余内容。
【思路及答案】
目前还是看不懂题目,建议参考下面大佬的文章。这道题实际上是leetcode里的原题,名字叫“鸡蛋掉落”。等弄懂了题目我再写出思路。
LeetCode 鸡蛋掉落(最清晰的解法)
【题目】
以下代码可以从数组a[]中找出第k小的元素。
它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
请仔细阅读分析源码,填写划线部分缺失的内容。
import java.util.Random;
public class Main{
public static int quickSelect(int a[], int l, int r, int k) {
Random rand = new Random();
int p = rand.nextInt(r - l + 1) + l;
int x = a[p];
int tmp = a[p]; a[p] = a[r]; a[r] = tmp;
int i = l, j = r;
while(i < j) {
while(i < j && a[i] < x) i++;
if(i < j) {
a[j] = a[i];
j--;
}
while(i < j && a[j] > x) j--;
if(i < j) {
a[i] = a[j];
i++;
}
}
a[i] = x;
p = i;
if(i - l + 1 == k) return a[i];
if(i - l + 1 < k) return quickSelect( _________________________________ ); //填空
else return quickSelect(a, l, i - 1, k);
}
public static void main(String args[]) {
int [] a = {1, 4, 2, 8, 5, 7};
System.out.println(quickSelect(a, 0, 5, 4));
}
}
【思路及答案】
我写的太长了,就另写了一篇博客来解这道题:
蓝桥杯第九届 javaB组省赛 第五题 快速排序(详解每行代码)
【题目】
给定三个整数数组
A = [A1, A2, ... AN],
B = [B1, B2, ... BN],
C = [C1, C2, ... CN],
请你统计有多少个三元组(i, j, k) 满足:
1. 1 <= i, j, k <= N
2. Ai < Bj < Ck
【输入格式】
第一行包含一个整数N。
第二行包含N个整数A1, A2, ... AN。
第三行包含N个整数B1, B2, ... BN。
第四行包含N个整数C1, C2, ... CN。
对于30%的数据,1 <= N <= 100
对于60%的数据,1 <= N <= 1000
对于100%的数据,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000
【输出格式】
一个整数表示答案
【输入样例】
3
1 1 1
2 2 2
3 3 3
【输出样例】
27
【思路】
这里我用了类似于动态规划的做法。一定要先读懂题目的意思,题目的意思是说,在三行数据中,每行取一个,取出的这三个数字 (Ai,Bj,Ck ) 连起来时应满足 (Ai < Bj < Ck) ,那么这就是一个符合条件的三元组,然后题目要求找出这些三元组的个数。
1、先处理第二、三行:
我们先把第二三行处理,即先找出Bj < Ck的情况。
此时我们主要看第二行的数据,我们的任务是先列出第二行中每个元素的情况数(情况数:当我第二行选择了某个元素时,第三行有多少个元素符合情况)。拿输入样例举例:
【1 1 1】
【2 2 2】
【3 3 3】
我们先定义一个数组s2,这个数组用于记录第二行的情况数。然后在第二行定义指针p=0, 这个指针是向右扫描的。(以下"[ ]"符号代表该行的指针所在位置)
此时p=0,第二行对应的元素是2,那么当这个元素是2时,第三行有几个元素满足条件呢?可以发现,第三行全部元素都是3,而2 < 3满足条件,所以说,在只看第二、三行的情况下,且p=0时,情况数有3个,然后把这个记录存到刚刚定义的数组s2,即s2[p] = 3;
【 1 1 1】
【[2] 2 2】 p=0 s2【3,?,?】
【 3 3 3】
同理,当p=1时,对应的元素是2,在第三行也有3个元素满足条件,因此s2[1] = 3; 再同理,p=2时,s2[2] = 3;
【 1 1 1】
【 2 [2]2】 p=1 s2【3,3,?】
【 3 3 3】
【 1 1 1 】
【 2 2 [2]】 p=2 s2【3,3,3】
【 3 3 3 】
至此,第二行的情况数已记录完成,s2 = 【3,3,3】,这个数组代表的意思是,当我第二行选择了其中的第 i 个元素,这个元素后续的情况有s2[i]种。
2、然后处理第一、二行:
处理第一、二行,先定义一个数组s1,用于记录第一行的情况数(和上面的第二行类似)。那么就需要在第一行中定义一个指针p=0(q是第二行指针):
【[1] 1 1】 p=0 s1【?,?,?】
【 2 2 2】 q=0
【 3 3 3】
比如当我第一行选了第一个元素(p=0)时,然后我再在第二行选第1个元素。之前已经计算过,当第二行选择了第1个元素后,后面有3种情况,所以,在第一行是1(p=0)、第二行是2(q=0)的前提下,这种情况有3种。
同理,在第一行是1(p=0)、第二行是2(q=1)的前提,情况有3种。
再同理,在第一行是1(p=0)、第二行是2(q=2)的前提,情况有3种。
综上,第一行是1(p=0)的情况下,一共有3+3+3=9种情况,数组s1[0] = 9;
s1[0] = 9的意思是,当我第一行选了第一个元素,后续情况就有9种。
【[1] 1 1】 p=0 s1【9,?,?】
【 2 2 2】
【 3 3 3】
同理,第一行是1(p=1)的情况下,一共有3+3+3=9种情况,数组s1[1] = 9;
【 1 [1]1】 p=1 s1【9,9,?】
【 2 2 2】
【 3 3 3】
再同理,p=2,数组s1[2] = 9;
【 1 1 [1]】 p=2 s1【9,9,9】
【 2 2 2 】
【 3 3 3 】
至此,第一行的情况数已记录完成,s1 = 【9,9,9】,这个数组代表的意思是,当我第一行选择了其中的第 i 个元素,这个元素后续的情况有s1[i]种。
3、计算总个数
现在题目的问题就可以简化成:第一行所有元素的情况数之和。
很显然,数组s1的和,就是答案。明白了思路再写代码就容易了。
【参考答案】
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class A6
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[][] arr = new int[N][N];
int[] arr1 = new int[N]; //第一行
for(int i = 0; i < N; i++)
arr1[i] = sc.nextInt();
int[] arr2 = new int[N]; //第二行
for(int i = 0; i < N; i++)
arr2[i] = sc.nextInt();
int[] arr3 = new int[N]; //第三行
for(int i = 0; i < N; i++)
arr3[i] = sc.nextInt();
//排序
Arrays.sort(arr1);
Arrays.sort(arr2);
Arrays.sort(arr3);
//上面一堆都是初始化数组,接下来正式进入思路代码
// int[] s1 = new int[N]; //记录第一行每个数字的情况数
int[] s2 = new int[N]; //记录第二行每个数字的情况数
int sum1 = 0; //用于记录s1数组的总和,其实就是最终答案。
int sum2 = 0; //用于记录s2数组的总和
int p1 = 0, p2 = 0, p3 = 0; //三行指针
//先处理第二、第三行
while(p2 < N && p3 < N)
{
/*
比如:
【 1, 2, 4, 5】 Ai
【[3], 4, 6, 9】 Bj
【2, [5], 7, 9】 Ck p3=1
当第二行选择“3”时,在第三行找到第一个比它大的数“5”,记录其位置p3=1
那么5后续的数都满足条件(前提是数组排好序),因此情况数是N-p3
*/
if(arr2[p2] < arr3[p3]) //第二行的起始数小于第三行的起始数(不能等于!题目要求)
{
s2[p2] = N - p3;
sum2 += s2[p2];
p2 ++;
}
else
{
p3 ++;
}
}
p2 = 0; //初始化
//处理第一、二行
while(p1 < N && p2 < N)
{
//第一行的起始数小于第二行的起始数
if(arr1[p1] < arr2[p2])
{
sum1 += sum2; //直接相加,可以不用s1数组
p1 ++;
}
else
{
/*
sum2本来就是s2的总和,最好的情况就是s2所有元素都能参与,
比如: 【1,2,3】 【1, 2, 3 】
【2,3,4】 用s2记录后--> 【(2,3),(3,2), (4,1)】 --> sum2 = 3+2+1=6
【3,4,5】 (k,v)--> (元素k,该元素的情况数v)
综上,当第1行是“1”时,第二行所有元素都能参与,情况数就是6
当第1行是“2”时,第二行就不是全部元素参与了,而会丢掉(2,3)这个元素,
此时的情况数是2+1,这个和sum2-s2[0](3+2+1 - 3)得到的是一样的,就是减去前缀的意思。
*/
sum2 -= s2[p2]; //减去前缀
p2 ++; //指针后移
}
}
System.out.println(sum1);
}
}
【题目】
如图p1.pgn所示的螺旋折线经过平面上所有整点恰好一次。
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。
例如dis(0, 1)=3, dis(-2, -1)=9
给出整点坐标(X, Y),你能计算出dis(X, Y)吗?
【输入格式】
X和Y
对于40%的数据,-1000 <= X, Y <= 1000
对于70%的数据,-100000 <= X, Y <= 100000
对于100%的数据, -1000000000 <= X, Y <= 1000000000
【输出格式】
输出dis(X, Y)
【输入样例】
0 1
【输出样例】
3
【思路】
首先化简一下思路,在螺旋的路线中,把图中画 “X” 的线段旋转到下方,使得红色线段可以围成一个正方形。每一层都照这种做法,就会得到很多个嵌套的正方形。每一层正方形的起点和终点都是左下角的坐标(拿第一层举例,原来起始点坐标应该是在 (0,0) ,经过旋转变化后,起始点就到了 (-1,-1) )
举例,如下图所示,目标坐标在(2,2),目标坐标所在层数是2,所以必定要加上第一层的距离(为8)。然后计算第二层的距离:
x轴距离:目标坐标横坐标 - 起始坐标横坐标 = 2 - ( -2 ) = 4
y轴距离:目标坐标纵坐标 - 起始坐标纵坐标 = 2 - ( -2 ) = 4
横纵坐标差之和 = x 轴距离 + y 轴距离
所以总距离 = 第一层距离数 + 横纵坐标差之和 = 16, 也就是蓝色箭头个数之和。
但是很显然,上面计算距离的公式是不完整的。比如下面图示的情况,当目标坐标为(2,0) 时,上面公式就得不出正确答案。
因此我们要换一种思路,即反向计算(绿色箭头)。仍然先计算目标坐标与起始坐标的横纵坐标差之和:
横坐标之差 = 2 - ( -2 ) = 4
纵坐标之差 = 0 - ( -2 ) = 2
横纵坐标差之和 = 6
这里计算的横纵坐标差之和是绿色箭头的距离,所以总距离 = 第二层距离数 - 横纵坐标差之和 = 24 - 6 = 18. 这里要注意【第二层距离数】包括了【第一层距离数】
【参考答案】
package _9s_LanQiao;
import java.util.Scanner;
public class A7
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int X = sc.nextInt();
int Y = sc.nextInt();
int n = Math.max(Math.abs(X), Math.abs(Y)); //该点所在的层数
/*
* 第1层 = 2*4 = 8
* 第1+2层 = 2*4 + 4*4 = 24
* 第1+2+3层 = 2*4 + 4*4 + 6*4 = 48
* 第1+...+n-1层 = 8*(1+2+..+n-1) = 4n(n-1)
* 第1+...+n层 = 8*(1+2+..+n) = 4n(n+1)
*/
long sum_n = 4 * n * (n + 1); //第n层完整正方形
long sum_n_1 = 4 * n * (n - 1); //第n-1层完整正方形
long sum = 0; //总距离
//先计算【目标坐标与起始坐标】的横纵坐标差,计算总距离(横纵坐标差之和):
int x_x = X - (-n);
int y_y = Y - (-n);
int xy = x_x + y_y; //总距离(横纵坐标差之和)
//如果目标坐标落在了正方形左侧、上侧:
if((-n <= Y && Y <= n && X == -n) || (-n <= X && X <= n && Y == n))
sum = sum_n_1 + xy;
//如果目标坐标落在了正方形右侧、下侧:
if((-n <= Y && Y <= n && X == n) || (-n <= X && X <= n && Y == -n))
sum = sum_n - xy;
System.out.println(sum);
}
}
时间复杂度O(1),代码可以更简洁。
【题目】
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。其中每一行的格式是:
ts id
表示在ts时刻编号id的帖子收到一个"赞"。
现在小明想统计有哪些帖子曾经是"热帖"。如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。
给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。
【输入格式】
第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。
对于50%的数据,1 <= K <= N <= 1000
对于100%的数据,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000
【输出格式】
按从小到大的顺序输出热帖id。每个id一行。
【输入样例】
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
【输出样例】
1
3
【思路】
1、读题:
在输入样例中,输入的格式是【时间点ts+帖子号id】,这一行表示帖子号为id的帖子在时间点ts收获了一个赞。
然后输入的【参数D】是指一段时间,【参数K】是指在这段时间D内,如果点赞数不少于K,则这个帖子是热帖
用图片讲述一下题目所表达的意思:
图中数轴的值(黑色数字)代表时间点ts,红色数字代表帖子的id。所以数值上每个点都表示了该帖子在该时间点收获了一个点赞。那么如何判断帖子是否是热帖?那就看一个时间段D内,该帖子是否被点赞了K次。我们假设K = 3,D = 4:
假设这个时间段D的起点和数轴的起点相同,那么这个时间段D中,帖子1被点赞了1次,帖子2被点赞了2次。(不包括帖子3,因为题目说了这个时间段区间是左闭右开区间),由于没有一个帖子点赞数大于等于K(K=3),所以这个时间段内没有热帖。
按照题目的意思,这个时间段是可以任意移动的,比如我可以移动到如下图:
然后我们把时间段移动到一个比较巧妙的位置:
这种情况,很明显帖子2被点赞了3次,满足热帖的条件(因为K=3),因此,帖子2是热帖。
2、代码实现
根据上面的思路,我们只需要得到某个帖子所有的时间点,然后用时间段一个个去比较,如果时间段内存在K个及以上的时间点,那这个帖子就是热帖了。
我还是参考了一下网上大佬的代码才做出来的。当我看到下面这段代码时,我就知道应该怎么做了:
Map<Integer, List<Integer>> map = new HashMap<>();
创建map集合,key是帖子的id,value是一个list集合,用于存放该帖子id的所有时间点。
【参考答案】
package _9s_LanQiao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/*
*
第一行包含三个整数N、D和K。 N帖子数,D时间段,K热帖点赞不少于该数
【输入样例】
ts id
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
【输出样例】
1
3
*/
public class A8
{
public static void main(String[] args)
{
Map<Integer, List<Integer>> map = new HashMap<>();
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int D = sc.nextInt();
int K = sc.nextInt();
for(int i = 0; i < N; i++)
{
int ts = sc.nextInt(); //时间点
int id = sc.nextInt(); //帖子号
List<Integer> list = new ArrayList<>();
if(!map.containsKey(id)) //如果hashmap没有这个id
{
list.add(ts);
}
else //如果有这个id,拿出列表,添加值进去
{
list = map.get(id);
list.add(ts);
}
map.put(id, list);
}
for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()) //为了拿到key和value
{
//entry.getKey()可拿到键k,entry.getValue()可拿到值v
List<Integer> list = entry.getValue();
Collections.sort(list); //拿出这个帖子的所有时间点,排序
for(int i = 0; i < list.size() - K + 1; i++)
{
//拿时间段D去比较,就是用上面提到的思路,满足条件就输出id即可。
//这里我的思路是:如果从第i个时间点开始,往下的第K-1个时间点与第i个时间点之差 如果小于时间段D,那这个帖子就是热帖。
//也就是说时间段D内存在大于等于K个的时间点,那这个帖子就是热帖。
if(list.get(i + K - 1) - list.get(i) < D) //[0,9] D=10, K=2
{
System.out.println(entry.getKey());
break;
}
}
}
}
}
【题目】
你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
【输入格式】
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。
【输出格式】
一个整数表示答案。
【输入样例】
7
.......
.##....
.##....
....##.
..####.
...###.
.......
【输出样例】
1
【思路】
先理解一下题目的意思(我之前由于没有理解题目意思,思路和代码全错了,所以看懂题很重要!!!):
‘ . ’是海洋,’ # ‘是陆地。题目给定一个岛屿图,经过变化后,只要“陆地”的上下左右有一个是“海洋”,那么这块“陆地”就会被淹没(变成’ . ')
判断一块区域是不是岛屿:“陆地”的上下左右连在一起的一片陆地就是一块岛屿。
随后通过dfs,把相连的“陆地”都标记为“1”,把不会淹没的“陆地”标记为“2”,由于进行一次dfs时查找的范围就是一块岛屿,所以如果这块岛屿出现了“2”标记,说明这块岛屿还存在,没有被完全淹没。
【参考答案】
import java.util.Scanner;
/*
*
7
.......
.##....
.##....
....##.
..####.
...###.
.......
.......
.......
.......
.......
....#..
.......
.......
*/
public class A9
{
//定义方向,上右下左
static int[][] d = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
static int x = 0, y = 1; //只是为了方便直观
static boolean stay = false; //这块岛屿是否能留下来?先初始化为不能留下来。
static int count = 0; //计算有多少个岛屿沦陷了
//打印地图,方便测试
public static void print(int[][]map)
{
for(int i = 0; i < map.length; i++)
{
for(int j = 0; j < map[0].length; j++)
{
System.out.print(map[i][j]);
}
System.out.println();
}
}
public static void dfs(char[][] map, int[] node, int[][] book)
{
//只要进来dfs了,坐标node就一定是陆地,把他标记成1
book[node[x]][node[y]] = 1;
//定义flag,初始化为true,意思是默认当前的node的上下左右都是“陆地”。
boolean flag = true;
//四个方向走一遍
for(int i = 0; i < 4; i++)
{
//下一个结点
int[] next = new int[] {node[x] + d[i][x], node[y] + d[i][y]};
//如果这个方向的像素仍然是陆地且dfs没有走过
if(map[next[x]][next[y]] == '#' && book[next[x]][next[y]] == 0)
dfs(map, next, book); //下一节点进行dfs
if(map[next[x]][next[y]] == '.')
flag = false; //只要node有一个方向有水,它就留不下来,flag记为false
}
//如果flag是true,说明这个节点node上下左右都是陆地,那它就不会被淹没,记为2
if(flag)
{
book[node[x]][node[y]] = 2;
stay = true; //这块岛屿留下来了。
}
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //地图边长
String[] t = new String[n];
Scanner sc2 = new Scanner(System.in);
for(int i = 0; i < t.length; i++)
t[i] = sc2.nextLine();
char[][] is = new char[n][n]; //岛屿平面地图
for(int i = 0; i < n; i++)
{
char[] temp = t[i].toCharArray();
for(int j = 0; j < n; j++)
is[i][j] = temp[j];
}
//上面一大串都是为了得到地图的二维数组,下面开始正式dfs
int[][] book = new int[n][n]; //标记数组
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(is[i][j] == '#' && book[i][j] == 0) //如果发现了一块"新"陆地,且这个地方没有走过(没有标记成1或2)
{
stay = false; //初始化
int[] node = new int[] {i, j}; //就把当前节点(坐标)传入dfs
dfs(is, node, book);
if(!stay) //如果岛屿没有留下来,计数
count ++;
}
}
}
// print(book);
System.out.println(count);
}
}
【题目】
标题:堆的计数
我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。
假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?
例如对于N=4有如下3种:
1
/ \
2 3
/
4
1
/ \
3 2
/
4
1
/ \
2 4
/
3
由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。
【输入格式】
一个整数N。
对于40%的数据,1 <= N <= 1000
对于70%的数据,1 <= N <= 10000
对于100%的数据,1 <= N <= 100000
【输出格式】
一个整数表示答案。
【输入样例】
4
【输出样例】
3
【思路及答案】
我看网上大佬的答案,用到了逆元、动态规划等,太难了……还是等到会做了再写吧。