一、第几天
2000年的1月1日,是那一年的第1天。 那么,2000年的5月4日,是那一年的第几天?
注意:需要提交的是一个整数,不要填写任何多余内容。
解法一:
1月:31
2月:是否闰年——>是:29
3月:31
4月:30
5月:4
累加:31+29+31+30+4=125
解法二:求出相差多少天,最后+1
public class 第几天 {
public static void main(String[] args) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = dateFormat.parse("2000-01-01");
Date date2 = dateFormat.parse("2000-05-04");
long days = (date2.getTime() - date1.getTime()) / 1000 / 60 / 60 / 24;
System.out.println("两个时间相距:" + days + "天");
}
}
124+1=125!!
记住不要搞错了!!
二、方格计数
如图p1.png所示,在二维平面上有无数个1x1的小方格。 我们以某个小方格的一个顶点为圆心画一个半径为1000的圆。
你能计算出这个圆里有多少个完整的小方格吗?
注意:需要提交的是一个整数,不要填写任何多余内容。
有这样想过的朋友举爪——认为就是求中间粉色正方形的面积,其边长为(r *2) -2
然而,题目就是故意这样的吧!!!
我这个思路没有考虑全面,这些方块不一定能构成上面粉色区域的正方形,还有下面这张图的红色部分!!!我的思路过于简单
所以比较好的解法应该是:
求出一个象限的方块个数,然后*4 即可求出整个圆里面的方块。
若一个象限中,x和y 构成的矩形如果在r *r 构成的矩形中,那么说明存在有完整的小方块,但是为什么能取到 = 号我就没有想到了
public class 方格计数 {
public static void main(String[] args) {
int r = 1000;
int ans = 0;
for (int x = 1; x <= r; x++) {
for (int y = 1; y <= r; y++) {
if (x * x + y * y <= r * r) {
ans++;
}
}
}
System.out.println(ans * 4);
}
}
// 更优化的方法
int y = r;
int cnt = 0;
for (int x = 1; x <= r; x++) {
while (x * x + y * y > r * r && y > 0) {
y--;
}
cnt += y;// 出来的y都是合法的
}
System.out.println(cnt * 4);
或者暴力更好理解——只要四个点都在圆内,那就构成一个
public static void func(int r) {
int ans = 0;
for (int i = -1000; i <= r; i++) {
for (int j = -1000; j <= r; j++) {
// 相邻4点都在圆内,则可以构成
if (check(r, i, j) && check(r, i + 1, j + 1) && check(r, i + 1, j) && check(r, i, j + 1)) {
ans++;
}
}
}
System.out.println(ans);
}
public static boolean check(int r, int x, int y) {
// 坐标合法
if (x >= -r && x <= r && y >= -r && y <= r) {
if (x * x + y * y <= r * r) {// 点在圆内
return true;
}
}
return false;
}
取= 号是因为:
这道题,涉及数学的思维,我太菜了,根本理解不了!!一点都理解不了!!啊啊啊啊啊
三、复数幂
设 i 为虚数单位。对于任意正整数n,(2+3i) ^ n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少?—— 即(2+3i)的123456次幂,这个数字很大,要求精确表示。
答案写成 “实部±虚部i” 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。
(2+3i)^2 写成: -5+12i,
(2+3i)^5 的写成: 122-597i
注意:需要提交的是一个很庞大的复数,不要填写任何多余内容。
复数运算公式:
( a + bi ) * ( c + di ) = ( a * c - b * d ) + ( a * d + b * c )i
最重要的是:i^2 =-1
public class 复数幂 {
public static void main(String[] args) throws FileNotFoundException {
BigInteger a = BigInteger.valueOf(2);
BigInteger two = BigInteger.valueOf(2);
BigInteger b = BigInteger.valueOf(3);
BigInteger three = BigInteger.valueOf(3);
BigInteger aa = null;
BigInteger bb = null;
for (int i = 1; i < 123456; i++) {
aa = a.multiply(two).subtract(b.multiply(three));// a*2-b*3
bb = a.multiply(three).add(b.multiply(two));
a = aa;
b = bb;
}
System.setOut(new PrintStream(new File("E:\\蓝桥杯训练\\题目文件\\复数幂.txt")));
// (b.compareTo(BigInteger.ZERO) < 0 ? "-" : "+") 本身b就有正负号,可以不用讨论
System.out.println(a.toString() + b.toString() + "i");
}
}
答案:
a、 b 都是负的!太长了,电脑都卡了,就不粘贴了
四、测试次数
打扰了:不会做
leetcode类似的题:鸡蛋掉落
看一下大佬的:
public class _04_测试次数 {
public static void main(String[] args) {
for(int i=0;i<10;i++)
for(int j=0;j<10000;j++)
memo[i][j] = 99999999;//找最小,初始化INF
System.out.println(f(3,1000));
}
static int[][] memo = new int[10][10005];
static int f(int n,int m) {
if(n==1)
return m;
if(m==1 || m==2)
return 1;
if(memo[n][m]!=99999999)
return memo[n][m];
for(int i=1;i<=m;i++) {
memo[n][m] = Math.min(memo[n][m], 1+Math.max(f(n,m-i),f(n-1,i)));
}
return memo[n][m];
}
}
五、快速排序
以下代码可以从数组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));
}
}
注意:只提交划线部分缺少的代码,不要抄写任何已经存在的代码或符号。
我的答案:
if (i - l + 1 < k)
return quickSelect(a, i + 1, r, k); // 填空
else
return quickSelect(a, l, i - 1, k);
我错了,我是按照经典快排的思想,但是这道题应该是随机快排的思想
public class 快速排序 {
// 以下代码可以从数组a[]中找出第k小的元素。
public static int quickSelect(int a[], int l, int r, int k) {
// 1、随机选择一个元素作为基准元素(此时选择的基准元素为x)
Random rand = new Random();
int p = rand.nextInt(r - l + 1) + l;
int x = a[p];
// 这里令我们随机选择的元素a[p]与数组最右端的元素a[r]互换
int tmp = a[p];
a[p] = a[r];
a[r] = tmp;
// 2、相当于设置两个指针i和j,i为左指针,j为右指针,它们分别指向数组最左和最右的元素
int i = l, j = r;
while (i < j) {
// 3、将左指针指向的元素与基准元素相比较,如果左指针指向的元素小于基准元素,则左指针向右移动一位
while (i < j && a[i] < x)
i++;
// 4、通过上一步的移动,左指针左边的元素全部比基准元素小,
// 令数组最右的元素(此时的数组最右的元素等于基准元素x)等于此时左指针指向的元素,
// 右指针向左移动一位,此时的a[j]大于等于x
if (i < j) {
a[j] = a[i];
j--;
}
// 5、将右指针指向的元素与基准元素相比较,如果右指针指向的元素大于基准元素,则右指针向左移动一位
while (i < j && a[j] > x)
j--;
// 6、通过上一步的移动,右指针右边的元素全部比基准元素大,
// 令左指针指向的元素等于此时右指针指向的元素,左指针向右移动一位,此时的a[i]小于等于x
if (i < j) {
a[i] = a[j];
i++;
}
}
// 7、通过步骤3、4、5、6后,可以确定a[i]的位置就是我们选择的基准元素排序后应该所在的位置
a[i] = x;
// 8、令基准元素位置的索引i等于p
p = i;
// 9、通过算式i - l + 1得到的是在数组中第几小的元素,若它等于k,则得到题目要求的数组a[]中第k小的元素
if (i - l + 1 == k)
return a[i];
// 若小于k,即我们所求的元素在i的右边,此时左指针指向的元素为a[p+1],右指针指向的元素依旧是a[r],
// 所求第k小的元素变为第k - (i - l + 1)小
if (i - l + 1 < k)
return quickSelect(a, i + 1, r, k - (i - l + 1)); // 填空
else
// 若大于k,即我们所求的元素在i的左边,此时左指针指向的元素为认为a[l],右指针指向的元素为a[i-1],仍旧是求第k小的元素
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));
}
}
答案应该是:
a, i + 1, r, k - (i - l + 1)
六、递增三元组
给定三个整数数组 A = [A1, A2, … AN], B = [B1, B2, … BN], C = [C1, C2, … CN], 请你统计有多少个三元组(i, j, k) 满足:
- 1 <= i, j, k <= N
- 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
暴力肯定过不了,所以要想办法优化
- 对三个数组都排序;
- 以b数组为基准,从b[0]开始,在a数组中找到比b[0]大的第一个元素的下标p,那么可以得到在a数组中比b[0]小的元素个数为p;(下标:p-1-0+1)
- 在c数组中找到比b[0]大的第一个元素的下标q,那么比b[0]大的元素有N-q个(下标:N-1-q+1)
- 同理,进一步的,b[1]比b[0]大,所以可以直接在b[0]得到的结果上,继续在a数组中找比b[1]大的第一个元素的下标p(继续用p),在c数组中找到比b[1]大的第一个元素的下标q。。。
- cnt+= p * (N - q) * 1L;//数据很大!要用long!
细节:一定要先判断下标是否合法,再进行数组元素的比较
用两种方法来做,前面部分都一样:
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] a = new int[N];
int[] b = new int[N];
int[] c = new int[N];
for (int i = 0; i < N; i++) {
a[i] = sc.nextInt();
}
for (int i = 0; i < N; i++) {
b[i] = sc.nextInt();
}
for (int i = 0; i < N; i++) {
c[i] = sc.nextInt();
}
Arrays.sort(a);
Arrays.sort(b);
Arrays.sort(c);
解法一:上述的方法
long cnt = 0;
int p = 0;
int q = 0;
for (int i = 0; i < N; i++) {
while (p < N && a[p] < b[i]) {// 下标应先合法,在a中找到比b[i]大的第一个元素的下标
p++;
}
while (q < N && b[i] > c[q]) {// 在c中找到比b[i]大的第一个元素的下标
q++;
}
cnt += 1L * p * (N - q);// 数据很大,用long
}
System.out.println(cnt);
细节:cnt += 1L * p * (N - q);// 数据很大,用long
解法二:二分
long res = 0;
for (int i = 0; i < N; i++) {
// 在b数组中找到比a[i]恰好大的第一个元素的下标,那么这个下标及其之后都比a[i]大
int temp = binarySearch(b, a[i]);
if (temp != -1) {
// 在c中找到恰好比b[temp]大的第一个元素的下标,那么这个下标及其之后都比b[temp]大
int temp2 = binarySearch(c, b[temp]);
if (temp2 != -1) {
res += (N - temp) * (N - temp2);
}
}
}
System.out.println(res);
}
public static int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length;
int ans = -1;
while (left < right) {
int mid = (right - left) / 2 + left;
if (arr[mid] > target) {
right = mid;// 找到恰好>target的第一个下标
ans = mid;// 记录当前比target大的下标
} else if (arr[mid] <= target) {
left = mid + 1;
}
}
return ans;// 找不到就返回-1,说明没有target
}
细节:分析数据的范围,判断用什么类型的变量
七、螺旋折线
如图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
分析:数据范围很大,所以用long。
解法一、暴力,过不了
static public long stepFun(int x, int y) {
int level = 0;
int row = 0, col = 0;
long step = 0;
Out: while (row != x || col != y) {
// 向左
for (int i = 0; i <= level; i++) {
if (row == x && col == y) {
break Out;
}
step++;
row--;
}
// 向上
for (int i = 0; i <= level; i++) {
if (row == x && col == y) {
break Out;
}
step++;
col++;
}
level++;
// 向右
for (int i = 0; i <= level; i++) {
if (row == x && col == y) {
break Out;
}
step++;
row++;
}
// 向下
for (int i = 0; i <= level; i++) {
if (row == x && col == y) {
break Out;
}
step++;
col--;
}
level++;
}
return step;
}
解法二:数学,找规律
划分一条线,以右下角的点为基准:
- 如果在右上部分,那么每一层到达右下角的最大距离呈现以4为公差的等差数列:
a0=4
an=a0+(n-1)*d
- 如果在左下部分,那么每一层到达右下角的最大距离呈现以4为公差的等差数列:
a0=2
an=a0+(n-1)*d
把上面的拆解成四个方向:
其实我这里还少考虑了横行的坐标轴,修改“下”部分:
(y <= 0 && x >= y - 1 && x <= -y)
好,我们现在知道了方向,现在我们思考
- 如何找到该坐标位于哪一层?
- 如何计算该坐标与右下角基准点的距离?
搞了半天我也没有做对,算了我没有这个能力,我不做了,拜拜
public class 螺旋折线2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long x = scanner.nextLong();// 传入的数据也很大!
long y = scanner.nextLong();
long distance = 0;// 与第几圈的(x,-x)的距离
long n = 0;// 第几圈
if (y > 0 && Math.abs(x) <= y) {
n = y;
distance = y - x + 2 * y;
} else if (x > 0 && Math.abs(y) <= x) {
n = x;
distance = x + y;// y<0,如果是右部分的上部分,早在上面的if中就被拦下了
} else if (y <= 0 && x >= y - 1 && x <= -y) {
n = -y;
distance = -(-y - x);//x+y
} else if (x < 0 && y >= x + 1 && y <= -x) {
n = -x - 1;
distance = -(y - x - 1 - 2 * x - 1);
}
System.out.println(sum(1L, 2 * n, 1) * 2 - distance);
}
// 等差数列求和
public static long sum(long a0, long n, int d) {
return (2 * a0 + (n - 1) * d) * n / 2;
}
}
八、日志统计
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有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
public class 日志统计 {
static class R {// 静态外部类,涉及R对象所有的属性
int ts, id;// ts时刻此id收到一个赞
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int D = scanner.nextInt();
int K = scanner.nextInt();
R[] obj = new R[N];// 存储对象
for (int i = 0; i < N; i++) {
R r = new R();
r.ts = scanner.nextInt();
r.id = scanner.nextInt();
obj[i] = r;
}
// 按照时间先后排序
Arrays.sort(obj, new Comparator<R>() {
@Override
public int compare(R o1, R o2) {
return o1.ts - o2.ts;
}
});
Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
SortedSet<Integer> set = new TreeSet<>();
// 尺取法
int j = 0;
for (int i = 0; i < obj.length; i++) {
while (j < N && obj[j].ts - obj[i].ts <= K) {
int id = obj[j].id;
Integer isExist = cnt.get(id);
if (isExist != null) {// 已存在值
cnt.put(id, cnt.get(id) + 1);// 更新
} else {
cnt.put(id, 1);
}
if (cnt.get(id) >= K) {
set.add(id);
}
j++;
}
// 出来的j不满足条件
Integer temp = cnt.get(obj[i].id);// 如果当前值不为0,则-1
if (temp != null) {// 更新i
cnt.put(obj[i].id, temp - 1);
}
}
for (Integer integer : set)
System.out.println(integer);
}
}
九、全球变暖
你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
【输入格式】
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。
【输出格式】
一个整数表示答案。
【输入样例】
7
上图
【输出样例】
1
public class 全球变暖 {
static int[][] dir = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };// 下上右左
static int N;// 数据规模
static char[][] grid;// 地图数据
static int ans;// 被完全淹没的岛屿数量
static boolean[][] flag;// 标记每个格子是否被访问
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
N = scanner.nextInt();
scanner.nextLine();
grid = new char[N][N];
flag = new boolean[N][N];
for (int i = 0; i < N; i++) {
grid[i] = scanner.nextLine().toCharArray();
}
// 双重循环检测地图上的每个格子,以未被访问的#开始
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] == '#' && !flag[i][j]) {
bfs(i, j);
}
}
}
System.out.println(ans);
}
public static void bfs(int x, int y) {// 传入位置,搜索周边是否有'#'
flag[x][y] = true;// 一旦进入bfs,说明被访问
int cntOfBlock = 0;// 陆地的数量
int cntOfSubmerge = 0;// 被淹没的陆地数量
Queue<Point> queue = new LinkedList<>();
queue.add(new Point(x, y));
while (!queue.isEmpty()) {
Point temp = queue.poll();
cntOfBlock++;// 双重循环决定进入bfs的是陆地
boolean submerge = false;
// 查看上下左右是否有'.',有则会被淹没
for (int i = 0; i < 4; i++) {
int nextX = temp.x + dir[i][0];
int nextY = temp.y + dir[i][1];
if (nextX < 0 || nextX >= N || nextY < 0 || nextY >= N) {
continue;
}
if (grid[nextX][nextY] == '.') {
submerge = true;
} else if (grid[nextX][nextY] == '#' && !flag[nextX][nextY]) {
// 周围有岛屿连成一片,那么应该算一块成群的岛屿,继续往下找
queue.add(new Point(nextX, nextY));
flag[nextX][nextY] = true;
}
}
// 有'.'环绕
if (submerge) {
cntOfSubmerge++;
}
}
if (cntOfBlock == cntOfSubmerge) {
ans++;
}
}
static class Point {
int x, y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
}
十、放弃