阅读 1925

校招中我,细细品味了这些题... | 掘金技术征文

本人北京某高校计算机硕士,总觉得本科毕业还是不久前的事情,转眼间研究生也要毕业了,7月开始投入校招大军,到目前为止也积攒了一些面试笔试题以及个人的学习总结,现在分享一波,希望能对大家有所帮助。skr~~😊

今日头条后端方向笔试题

第一题:字符交换

题目

字符串S由小写字母构成,长度为n。定义一种操作,每次都可以挑选字符串中任意的两个相邻字母进行交换。询问在至多交换m次之后,字符串中最多有多少个连续的位置上的字母相同?
输入描述:

第一行为一个字符串S与一个非负整数m。(1 <= |S| <= 1000, 1 <= m <= 1000000)

输出描述:

一个非负整数,表示操作之后,连续最长的相同字母数量。

输入例子1:

abcbaa 2

输出例子1:

2

例子说明1:
使2个字母a连续出现,至少需要3次操作。即把第1个位置上的a移动到第4个位置。 所以在至多操作2次的情况下,最多只能使2个b或2个a连续出现。

思路

动态规划。 以a字符为例,令 dp[i][j] 表示将从第 i 个 a 字符(包含)到第 j 个 a 字符(包含)之间的所有 a 字符移动到一起的交换次数,我们可以知道将所有的字符往中间移动的代价是最小的。
同时,假设从第 i + 1 个 a 字符到第 j - 1 个 a 字符之间的所有字符 a 都已经移动到一起了,无论它们的位置如何,则只需把 i 位置和 j 位置的 a 字符忘中间移动,即可得到把第 i 个 a 字符(包含)到第 之间的所有 到一起的最小操作次数,且该步骤的操作次数一定为第 j 个 a 字符的下标减去第 i 个 a 字符的下标加一再减第 i + 1 个 a 字符到第 j - 1 个 a 字符之间的所有字符 a 的数量。

动态转移方程为:

dp[i][j] = dp[i + 1][j] + (index[j] – indexAfterMove[j – 1] – 1) + (indexAfterMove[i + 1] – index[i] – 1) = dp[i + 1][j] + (index[j] – index[i]) – (indexAfterMove[j – 1] – indexAfterMove[i + 1]) – 2 = dp[i + 1][j] + (index[j] – index[i]) – len(i + 1, j – 1) – 1

代码

package exam.q3;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import static java.lang.Math.max;
 
public class Main {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		String str = sc.next();
		int m = sc.nextInt();
		
		int len = str.length();
		HashMap<Character, ArrayList<Integer>> map = new HashMap<>();
		
		for(int i = 0 ; i < len; i ++) {
			ArrayList<Integer> index = map.getOrDefault(str.charAt(i), new ArrayList<>());
			index.add(i);
			map.put(str.charAt(i), index);
		}
	
		int ans = 0;
		for(char k: map.keySet()){
			ArrayList<Integer> index = map.get(k);
			int lenOfIndex = index.size();
			int[][] dp = new int[lenOfIndex][lenOfIndex];
			for(int i = 0; i < lenOfIndex - 1; i++) {
				dp[i][i + 1] =  index.get(i + 1) - 1 - index.get(i);
			}
			for(int num = 2; num <= lenOfIndex; num++) {
				for(int i = 0; i < lenOfIndex - num + 1; i++) {
					dp[i][i + num - 1] = dp[i + 1][i + num - 2]  + index.get(i  + num - 1) - index.get(i) + 1 - num;
					if(dp[i][i + num - 1] <= m) {
						//System.out.println(k + " " + index.get(i) + " " + index.get(i + num - 1) + " " + num + " " + dp[i][i + num - 1]);
						ans = max(ans, num);
					}
				}
			}
		}
		System.out.println(ans);
		
		sc.close();
	}
 
}
复制代码

第二题:二阶魔方

题目

二阶魔方又叫小魔方,是 2*2*2 的立方形结构。每一面都有 4 个块,共有 24 个块。每次操作可以将任意一面逆时针或者顺时针旋转 90°,如将上面逆时针旋转 90° 操作如下。

Nero 在小魔方上做了一些改动,用数字替换每个块上面的颜色,称之为数字魔方。魔方上每一面的优美度就是这个面上 4 个数字的乘积,而魔方的总优美度就是 6 个面优美度总和。 现在 Nero 有一个数字魔方,他想知道这个魔方在操作不超过 5 次的前提下能达到的最大优美度是多少。 魔方展开后每一块的序号如下图:
输入描述:
输入一行包含 24 个数字,按序号顺序给出魔方每一块上面的数字。所有数大小范围为[-100,100]。
输入描述:
输出一行包含一个数字,表示最大优美度。

输入例子1:

2 -3 -2 3 7 -6 -6 -7 9 -5 -9 -3 -2 1 4 -9 -1 -10 -5 -5 -10 -4 8 2

输出例子1:

8281

思路

本来想用类来抽象魔方的每个面,然后模拟魔方的旋转,但是太复杂了,不仅要存储每个面的上下左右,而且每个面的每个格子也要抽象出来,因为不同的格子相邻的格子是不一样的,旋转的时候要改变其相邻格子。

后来查了一下题解,发现一种很巧妙的方法,用置换群的方法。

每次旋转,只要旋转方式一样,固定位置的格子一定会被替换到另一固定位置,只要将种旋转方式用置换群抽想出来就可以了,一共6种旋转方式,垂直上下旋转,水平左右旋转和垂直左右旋转。

代码

package exam1.q2;

import java.util.Scanner;

public class Main {

	private static int[][] subs = {
			{0, 1, 11 ,5, 4, 16, 12, 6, 2, 9, 10, 17, 13, 7, 3, 15, 14, 8, 18, 19, 20, 21, 22, 23},
			{0, 1, 8, 14, 4, 3, 7, 13, 17, 9, 10, 2, 6, 12, 16, 15, 5, 11, 18, 19, 20, 21, 22, 23},
			{0, 7, 2, 13, 4, 5, 6, 17, 14, 8, 10, 11, 12, 19, 15, 9, 16, 21, 18, 23, 20, 1, 22, 3},
			{0, 21, 2, 23, 4, 5, 6, 1, 9, 15, 10, 11, 12, 3, 8, 14, 16, 7, 18, 13, 20, 17, 22, 19},
			{2, 0, 3, 1, 6, 7, 8, 9, 23, 22, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 5, 4},
			{1, 3, 0, 2, 23, 22, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 9, 8}
	};
	private static int[][] faces = {
		    {0, 1, 2, 3},
		    {4, 5, 10, 11},
		    {6, 7, 12, 13},
		    {8, 9, 14, 15},
		    {16, 17, 18, 19},
		    {20, 21, 22, 23}
		};
	
	private static long ans;
	private static void substitute(int[] cube, int step) {
		long perf = perfect(cube);
		if(perf > ans)
			ans = perf;
		if(step == 5 ) {
			return;
		}
		for(int i = 0; i < 6; i ++) {
			substitute(rotate(cube, subs[i]), step + 1);
		}
	}
	
	private static int[] rotate(int[] cube, int sub[]) {
		int[] rotated = new int[24];
		
		for(int i = 0; i < 24; i++) {
			rotated[i] = cube[sub[i]];
		}
		
		return rotated;
	}
	
	private static long perfect(int[] cube){
		long perf = 0;
		for(int[] f: faces){
			long t = 1;
			for(int n: f) {
				t *= cube[n];
			}
			perf += t;
		}
		return perf;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		
		int total = 24;
		int[] cube = new int[24];
		for(int i = 0; i < total; i++) {
			cube[i] = sc.nextInt();
		}
		substitute(cube, 0);
		System.out.println(ans);
		sc.close();
	}

}
复制代码

第三题:推箱子

题目

有一个推箱子的游戏, 一开始的情况如下图:

上图中, ‘.’ 表示可到达的位置, ‘#’ 表示不可到达的位置,其中 S 表示你起始的位置, 0表示初始箱子的位置, E表示预期箱子的位置,你可以走到箱子的上下左右任意一侧, 将箱子向另一侧推动。如下图将箱子向右推动一格;

..S0.. -> …S0.

注意不能将箱子推动到’#’上, 也不能将箱子推出边界;

现在, 给你游戏的初始样子, 你需要输出最少几步能够完成游戏, 如果不能完成, 则输出-1。
输入描述
第一行为2个数字,n, m, 表示游戏盘面大小有n 行m 列(5< n, m < 50); 后面为n行字符串,每行字符串有m字符, 表示游戏盘面;
输出描述
一个数字,表示最少几步能完成游戏,如果不能,输出-1;

输入例子1:

3 6
.S#..E
.#.0..
……

输出例子1:

11

思路

简单四维BFS,注意要同时记录人的位置和箱子位置

代码

package exam1.q3;

import java.util.LinkedList;
import java.util.Scanner;

public class Main {

	private static class Status{
		int x, y;
		int bx, by;
		int st;
		public Status(int x, int y, int bx, int by, int st) {
			super();
			this.x = x;
			this.y = y;
			this.bx = bx;
			this.by = by;
			this.st = st;
		}
		
	}
	
	private static final int[][] step = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; 
	
	private static int n, m;
	
	private static int pushBox(char[][] map) {
		
		int px = -1, py = -1;
		int bx = -1, by = -1;

		boolean[][][][] visited = new boolean[n][m][n][m];
		
		for(int i = 0; i < n; i++) {
			for(int j = 0; j < m; j++) {
				if(map[i][j] == 'S') {
					px = i;
					py = j;
				}
				else if(map[i][j] == '0') {
					bx = i;
					by = j;
				}
			}
		}
		int st = 0;
		Status s = new Status(px, py, bx, by, st);
		LinkedList<Status> queue = new LinkedList<>();
		queue.addLast(s);
		visited[px][py][bx][by] = true;
		
		while(!queue.isEmpty()) {
			s = queue.pollFirst();
			px = s.x;
			py = s.y;
			bx = s.bx;
			by = s.by;
			st = s.st;
			for(int i = 0; i< 4; i++) {
				int nextx = px + step[i][0];
				int nexty = py + step[i][1];
				if(nextx >= 0 && nextx < n && nexty >= 0 && nexty < m) {
					if(!(map[nextx][nexty] == '#') && !visited[nextx][nexty][bx][by]) {
						if(nextx == bx && nexty == by) {
							int nextbx = bx + step[i][0], nextby = by + step[i][1];
							if(nextbx >= 0 && nextbx < n && nextby >= 0 && nextby < m && !(map[nextbx][nextby] == '#')){
								Status news = new Status(nextx, nexty, nextbx, nextby, st + 1);
								queue.addLast(news);
								visited[nextx][nexty][nextbx][nextby] = true;
								if(map[nextbx][nextby] == 'E') {
									return st + 1;
								}
							}
						}
						else{
							Status news = new Status(nextx, nexty, bx, by, st + 1);
							queue.addLast(news);
							visited[nextx][nexty][bx][by] = true;
						}
					}
				}
			}
		}
		return -1;
		
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		
		n = sc.nextInt();
		m = sc.nextInt();
		char[][] map = new char[n][m];
		
		
		for(int i = 0; i < n; i++) {
			String tmp = sc.next();
			map[i] = tmp.toCharArray();
		}
		
		System.out.println(pushBox(map));
		sc.close();
		
	}
	
}
复制代码

第四题:房间分配

题目

有 n 个房间,现在 i 号房间里的人需要被重新分配,分配的规则是这样的:先让 i 号房间里的人全都出来,接下来按照 i+1, i+2, i+3, … 的顺序依此往这些房间里放一个人,n号房间的的下一个房间是1号房间,直到所有的人都被重新分配。

现在告诉你分配完后每个房间的人数以及最后一个人被分配的房间号 x,你需要求出分配前每个房间的人数。数据保证一定有解,若有多解输出任意一个解。
输入描述:
第一行两个整数 n, x (2<=n<=10^5, 1<=x<=n),代表房间房间数量以及最后一个人被分配的房间号; 第二行n个整数 a_i(0<=a_i<=10^9) ,代表每个房间分配后的人数
输出描述:
输出n个整数,代表每个房间分配前的人数。

输入例子1:

3 1
6 5 1

输出例子1:

4 4 4

思路

知道最后一个人被分配的房间号 x,求最后一次被分配人原来的房间的人数 num,设最后一次被分配人原来的房间号为 startx,

(x + n) % – startx = num % n

原来的房间一定是当前人数最少的房间之一,因为在最后一次分配前其人数被置为 0,在分配完成之后,其人数一定是 num / n。

但是人数最少的房间不一定唯一,因为可能在最后一次分配前还有其他人数为 0 的房间。还需要判断当前位置与 startx 之间的所有房间的人数是否都大于最小值。

代码

package exam1.q4;

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		
		int n, x;
		n = sc.nextInt();
		x = sc.nextInt();
		long[] a = new long[n];
		long min = Long.MAX_VALUE;
		int startx = -1;

		for(int i = 0; i < n; i++) {
			a[i] = sc.nextInt();
			if(a[i] < min) {
				min = a[i];
			}
		}
		x--;
		sc.close();
		for(int i = 0; i < n; i++) {
			if(a[i] == min) {
				int tmp = x - i;
				if(tmp < 0)
					tmp += n;
				
				boolean f = true;
				for(int j = 1; j <= tmp; j++) {
					if(a[(i + j) % n] < min + 1){
						f = false;
						break;
					}
				}
				if(f){
					startx = i;
					break;
				}
			}
		}
		
		
		
		long remain = 0;
		if(x < startx) 
			remain = x + n - startx;
		else
			remain = x - startx;
		
		long round = min;
		for(int i = 0; i < n; i++)
			a[i] -= round;
			
		for(int i = 1; i <= remain; i++) 
			a[(startx + i) % n] -= 1;

		a[startx] = round * n + remain;
		for(int i = 0; i < n; i++) 
			System.out.print(a[i] + " ");
		
		System.out.println();
		
	}

}
复制代码

第五题:跳房子

题目

存在 n + 1 个房间,每个房间依次为房间 1 2 3…i,每个房间都存在一个传送门,i房间的传送门可以把人传送到房间 pi(1<=pi<=i),现在路人甲从房间 1 开始出发(当前房间 1 即第一次访问),每次移动他有两种移动策略:
A. 如果访问过当前房间 i 偶数次,那么下一次移动到房间i+1;
B. 如果访问过当前房间 i 奇数次,那么移动到房间pi;
现在路人甲想知道移动到房间n+1一共需要多少次移动;
输入描述
第一行包括一个数字 n(30%数据1 <= n <= 100,100%数据 1 <= n <= 1000),表示房间的数量,接下来一行存在 n 个数字 pi(1 <= pi <= i), pi 表示从房间 i 可以传送到房间 pi。
输出描述
输出一行数字,表示最终移动的次数,最终结果需要对1000000007 (10e9 + 7) 取模。

输入例子1:

2
1 2

输出例子1:

4

例子说明1:

开始从房间1 只访问一次所以只能跳到p1即 房间1, 之后采用策略A跳到房间2,房间2这时访问了一次因此采用策略B跳到房间2,之后采用策略A跳到房间3,因此到达房间3需要 4 步操作。

思路:

注意 pi(1 <= pi <= i),即第奇数次到达某一房间只能往前面的房间跳,同时也意味着,要跳到某个从未到达过的房间 i,必须经过偶数次 i – 1,同时 i – 1 之前的所有房间也必须经过了偶数次。

该条件保证了可以利用子结构的最优解

令 dp[i]代表第二次到达房间i经过的步骤。首先考虑第一次到达第 i 个房间,则一定是经过了两次房间 i – 1,即步数为 dp[i – 1] + 1;

同时,由于这次是第一次到达房间 i,接下来会跳到pi,此时的步数更新为 dp[i – 1] + 2。要想再次到达房间 i,则一定要再两次经过房间 i – 1。而此时前 i – 1 个房间的状态为:

除了第 pi 个房间经过了奇数次,其他房间都是偶数次,与第一次到达第 pi 个房间时的状态一致。而第一次到达pi时经过的步数为 dp[pi – 1] + 1;

此时要再次到达第i个房间,必须再次两次经过 i – 1,从此时的状态到两次经过 i – 1 的状态所需的步数,等于从初始状态到第二次到达房间 i – 1 时的状态所需要的步数减去从初始状态到第一次到达房间 pi 的状态所需要的步数,即 dp[i – 1] – (dp[pi – 1] + 1),再走一步,第二次到达房间 i。

综上,第二次到达房间 i 的步数的状态转移方程为为:

dp[i] = dp[i – 1] + 1 + 1 + dp[i – 1] – (dp[pi – 1] + 1) +1 = dp[i – 1] * 2 – dp[pi – 1] + 2

代码

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		long mod = 1000000007;
		int n = sc.nextInt();
		int[] next = new int[n + 1];
		for(int i = 1; i <= n; i++) {
			next[i] = sc.nextInt();
			
		}
		long[] dp = new long[n + 1];
		if(n == 1)
			System.out.println(1);
		else {

			for(int i = 1; i <= n; i++) {
				dp[i] = ((dp[i - 1] * 2) % mod - dp[next[i] - 1] + 2) % mod;
			}
		}
		System.out.println((dp[n]) % 1000000007);
		sc.close();
	}

}
复制代码

网易校招编程题

第一题:整理房间

题目

又到了周末,小易的房间乱得一团糟。 他希望将地上的杂物稍微整理下,使每团杂物看起来都紧凑一些,没有那么乱。 地上一共有 n 团杂物,每团杂物都包含 4 个物品。第 i 物品的坐标用(ai,bi)表示,小易每次都可以将它绕着(xi, yi)逆时针旋转 90°,这将消耗他的一次移动次数。如果一团杂物的 4 个点构成了一个面积不为 0 的正方形,我们说它是紧凑的。 因为小易很懒,所以他希望你帮助他计算一下每团杂物最少需要多少步移动能使它变得紧凑
输入描述:
第一行一个数 n(1 <= n <= 100),表示杂物的团数。 接下来 4n 行,每4行表示一团杂物,每行 4 个数 ai, bi,xi, yi, (-104 <= xi, yi, ai, bi <= 104),表示第 i 个物品旋转的它本身的坐标和中心点坐标。
输出描述:
n行,每行1个数,表示最少移动次数。

输入例子1 :

4

1 1 0 0
-1 1 0 0
-1 1 0 0
1 -1 0 0
1 1 0 0
-2 1 0 0
-1 1 0 0
1 -1 0 0
1 1 0 0
-1 1 0 0
-1 1 0 0
-1 1 0 0
2 2 0 1
-1 0 0 -2
3 0 0 -2
-1 1 -2 0

输出例子1:

1
-1
3
3

例子说明1:

对于第一团杂物,我们可以旋转第二个或者第三个物品1次。

思路

由于一共四个点,每个点只有四种状态,故一共16种状态,搜索即可。由于是最少移动次数,故采用广度优先搜索。

需要注意的是如何计算一个点围绕另一个点旋转顺时针旋转90度。

我们在草稿纸上画一下,可以简单地得出点A(x1, y1)绕点B(x2, y2)逆时针旋转90度的公式为:

X1 = x2 – y1 + y2;
Y2 = x1 – x2 + y2;

但同时我们也可以总结一下A绕B旋转任意角度的公式,在碰到类似题目的时候可以防止重新推导。

点A(x1, y1)绕点B(x2, y2)顺时针旋转θ度:

x = (x1 – x2) cosθ – (y1 – y2) sinθ + x2
y = (y1 – y2) cosθ +(x1– x2) sinθ + y2

代码

package tideTheRoom;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {

	private static class Point implements Cloneable {
		long x;
		long y;
		long a;
		long b;
		int cnt;

		public Point(long x, long y, long a, long b, int cnt) {
			super();
			this.x = x;
			this.y = y;
			this.a = a;
			this.b = b;
			this.cnt = cnt;
		}

		public void rotate() {
			if (this.cnt == 3)
				return;
			long tx = a - y + b;
			long ty = x - a + b;
			this.x = tx;
			this.y = ty;
			this.cnt++;
		}

		@Override
		public Point clone() {
			Object o = null;
			try {
				o = super.clone();
			} catch (CloneNotSupportedException e) {
			}
			return (Point) o;
		}
	}

	private static boolean check(Point[] p) {
		long[] dist = new long[6];
		int cnt = 0;
		for (int i = 0; i < 3; i++) {
			for (int j = i + 1; j < 4; j++) {
				dist[cnt++] = (p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y);
			}
		}
		Arrays.sort(dist);
		if (dist[0] == dist[1] && dist[0] == dist[2] && dist[0] == dist[3] && dist[4] == dist[5]
				&& !(dist[0] == dist[4]))
			return true;
		return false;
	}

	private static int bfs(Point[] p) {
		boolean[][][][] visited = new boolean[4][4][4][4];
		LinkedList<Point[]> que = new LinkedList<>();
		que.addLast(p);
		visited[0][0][0][0] = true;
		if (check(p))
			return 0;
		while (!que.isEmpty()) {
			Point[] f = que.pollFirst();

			for (int i = 0; i < 4; i++) {
				Point[] tmp = new Point[4];
				for (int j = 0; j < 4; j++) {
					tmp[j] = f[j].clone();
				}
				tmp[i].rotate();
				if (visited[tmp[0].cnt][tmp[1].cnt][tmp[2].cnt][tmp[3].cnt])
					continue;
				if (check(tmp)) {
					return tmp[0].cnt + tmp[1].cnt + tmp[2].cnt + tmp[3].cnt;
				}
				que.addLast(tmp);
				visited[tmp[0].cnt][tmp[1].cnt][tmp[2].cnt][tmp[3].cnt] = true;
			}
		}
		return -1;
	}

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);

		int n = sc.nextInt();
		while ((n--) != 0) {
			Point[] p = new Point[4];
			for (int i = 0; i < 4; i++) {
				long x = sc.nextLong();
				long y = sc.nextLong();
				long a = sc.nextLong();
				long b = sc.nextLong();
				p[i] = new Point(x, y, a, b, 0);
			}
			System.out.println(bfs(p));
		}

		sc.close();
	}
}
复制代码

第二题:小易的字典

题目:

小易在学校中学习了关于字符串的理论, 于是他基于此完成了一个字典的项目。 小易的这个字典很奇特, 字典内的每个单词都包含n个’a’和m个’z’, 并且所有单词按照字典序排列。 小易现在希望你能帮他找出第k个单词是什么。
输入描述:
输入包括一行三个整数n, m, k(1 <= n, m <= 100, 1 <= k <= 109), 以空格分割。 输出描述:
出第k个字典中的字符串,如果无解,输出-1。

输入示例1:

2 2 6

输出示例1:

zzaa

示例说明:

字典中的字符串依次为aazz azaz azza zaaz zaza zzaa

思路

整体上是二分的思想。

首先,所有的组合数目的总数为𝐶𝑛𝑚+𝑛,从最高位开始,第一个字符是a还是z将整个字符串划分为两个部分,如果第一个是a,则整个字符串在前𝐶𝑛−1𝑚+𝑛−1部分,否则,在𝐶𝑛−1𝑚+𝑛−1+1到𝐶𝑛𝑚+𝑛部分。所以,要查找第k个字符串,首先判断第一个字符,如果𝑘<=𝐶𝑛−1𝑚+𝑛−1,则说明第一个字符为a,在前𝑘>𝐶𝑛−1𝑚+𝑛−1部分查找由𝑛个a和𝑚个z组成的第k个字符串,即在后𝑛–1个字符串中查找有𝑛–1个a和𝑚个z组成的字符串,否则,在𝐶𝑛−1𝑚+𝑛−1+1到𝐶𝑛𝑚+𝑛部分部分查找,即在后𝑛–1个字符组成的字符串中查找由𝑛个a和𝑚–1个z组成的第𝑘–𝐶𝑛−1𝑚+𝑛−1个字符串,这就找到了划分子问题的方法。按照这个方法逐步往后查找,直到n或者m为0,说明,剩余的字符都为z或者a。

这里求组合数需要一定的技巧,具体参考求组合数

代码

package dictionary;
 
import java.util.Scanner;
import static java.lang.Math.log;
 
import java.math.BigInteger;
 
import static java.lang.Math.exp;
 
public class Main {
 
	public static long comb(int m, int n, long target) {// 计算假设a确定之后,a之后的部分排列组合数
		if (m == 0 || n == 0)
			return 1;
		long sum = m + n;
		long k = 1;
		n = Math.min(m, n);// C(m+n) n=C(m+n) m 取最小即可
		for (int i = 0; i < n; i++) {
			k *= sum - i;
			k /= (i + 1);
			if (k > target)// 防止大数。如果k>target 则只进行list.add("a")和m--//a的个数减1。
							// 没有target -= k;因此不影响
				break;
		}
		return k;
	}
 
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
 
		int n = sc.nextInt();
		int m = sc.nextInt();
		int k = sc.nextInt();
 
		int tn = n, tm = m;
		StringBuilder sb = new StringBuilder();
		while (tn > 0 && tm > 0) {
			long c = comb(tn - 1, tm, k);
			// System.out.println(c);
 
			if (k <= c) {
				sb.append('a');
				tn--;
			} else {
				sb.append('z');
				k -= c;
				tm--;
			}
		}
		if (k != 1)
			System.out.println(-1);
		else {
			while (tn > 0) {
				sb.append('a');
				tn--;
			}
			while (tm > 0) {
				sb.append('z');
				tm--;
			}
			System.out.println(sb.toString());
		}
		sc.close();
	}
 
}
复制代码

刷了一些LeetCode题

第一题:LeetCode 42 Trapping Rain Water 和 LeetCode 407 Trapping Rain Water II

先刷到的LeetCode 407,是三维的情况,后来发现LeetCode 42,是二维的情况

在一开始做407时,用了一种的不断切割底部,遍历每次切割后高度为0的面积的方法,发现虽然能解决该问题,但是时间复杂度很高,最坏为

,导致不能AC

由于在想和写上述方法的过程中耗费了大量时间,有没有想到其他太好的方法,我就去找了下题解。

在找题解的过程中,发现有一道简化版的题目,做该题会对407的解法有启发作用。

题目

LeetCode 42

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
Example:
Input:[0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

题意

给 n 个柱子作为隔板,问柱子之间最多能存多少水

思路

根据木桶效应,能装多少水是由左右两边最高的柱子中的最矮柱子来决定的。

我一开始的做法是用两个数组 maxHeightLeft 和 maxHeightRight 分别记录位置 i 左边的最高柱子和右边的最高柱子。然后遍历从 1 到 n – 2,

如果 min(maxHeightLeft[i], maxHeightRight[i]) > height[i] 的话,答案ans += min(maxHeightLeft[i], maxHeightRight[i]) – height[i]

时间复杂度为

代码

import java.lang.Math;

public class Solution {
    public int trap(int[] height) {
        int len = height.length;
    	int[] maxHeightLeft = new int[len];
    	int[] maxHeightRight = new int[len];


    	for(int i = 1; i < len; i ++){
    		if(height[i - 1] > maxHeightLeft[i - 1])
    			maxHeightLeft[i] = height[i - 1];
    		else
    			maxHeightLeft[i] = maxHeightLeft[i - 1];
    	}
    	for(int i = len - 2; i >= 0; i --) {
    		if(height[i + 1] > maxHeightRight[i + 1])
    			maxHeightRight[i] = height[i + 1];
    		else
    			maxHeightRight[i] = maxHeightRight[i + 1];
    	}
    	
    	int sum = 0;
    	for(int i = 1; i < len - 1; i ++){
    		int shortEdge = Math.min(maxHeightLeft[i], maxHeightRight[i]);
    		if(shortEdge > height[i])
    			sum += shortEdge - height[i];
    	}
    	return sum;
    }
}
复制代码

LeetCode 407 Trapping Rain Water II

Given an m x n matrix of positive integers representing the height of each unit cell in a 2D elevation map, compute the volume of water it is able to trap after raining.

Note: Both m and n are less than 110. The height of each unit cell is greater than 0 and is less than 20,000.

Example:
Given the following 3×6 height map: [
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
Return 4.

The above image represents the elevation map[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]before the rain.
After the rain, water is trapped between the blocks. The total volume of water trapped is 4.

思路
利用优先队列(小顶堆)

首先将边缘的柱子全部入队,对中元素将整个矩阵围起来,作为最外层。而次外层的某个位置如果能存住水的话,其容量大小一定与其最紧靠的外一层位置的柱子高度相关

于是每次将围住矩阵的优先队列中的元素,从最小的元素往里扩展,并用扩展到的位置的高度来更新已遍历的外层位置的最高高度

代码

import java.util.Comparator;
import java.util.PriorityQueue;
import static java.lang.Math.max;

public class Solution1 {

	private final static int[][] steps = new int[][] { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } };

	private class Point {
		int x;
		int y;
		int height;

		public Point(int x, int y, int height) {
			super();
			this.x = x;
			this.y = y;
			this.height = height;
		}

	}

	private class Comparator1 implements Comparator<Point> {

		@Override
		public int compare(Point o1, Point o2) {
			if (o1.height > o2.height)
				return 1;
			return -1;
		}

	}

	public int trapRainWater(int[][] heightMap) {
		int ans = 0;
		int lenx = heightMap.length;
		if (lenx < 3)
			return 0;
		int leny = heightMap[0].length;
		if (leny < 3)
			return 0;
		boolean[][] visited = new boolean[lenx][leny];

		PriorityQueue<Point> que = new PriorityQueue<>(new Comparator1());
		for (int i = 0; i < lenx; i++) {
			que.add(new Point(i, 0, heightMap[i][0]));
			visited[i][0] = true;
			que.add(new Point(i, leny - 1, heightMap[i][leny - 1]));
			visited[i][leny - 1] = true;
		}
		for (int i = 1; i < leny - 1; i++) {
			que.add(new Point(0, i, heightMap[0][i]));
			visited[0][i] = true;
			que.add(new Point(lenx - 1, i, heightMap[lenx - 1][i]));
			visited[lenx - 1][i] = true;
		}
		int maxHeight = -1;

		while (!que.isEmpty()) {
			Point cur = que.poll();

			maxHeight = max(maxHeight, cur.height);
			for (int i = 0; i < 4; i++) {
				int nextX = cur.x + steps[i][0];
				int nextY = cur.y + steps[i][1];
				if (nextX >= 0 && nextX < lenx && nextY >= 0 && nextY < leny && !visited[nextX][nextY]) {
					//System.out.println(nextX + " " + " " + nextY + " " + maxHeight + " " + heightMap[nextX][nextY]);
					if (heightMap[nextX][nextY] < maxHeight) {

						ans += maxHeight - heightMap[nextX][nextY];
					}
					visited[nextX][nextY] = true;
					que.add(new Point(nextX, nextY, heightMap[nextX][nextY]));
				}
			}
		}
		return ans;
	}
}
复制代码

第二题:leetcode 207 Course Schedule

题目

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair:[0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: 2, [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

Note:

The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented. You may assume that there are no duplicate edges in the input prerequisites.

思路

明显的拓扑排序,复习一下,顺便复习了一下图的链式前向星表示法

代码

package leetcode_207_course_schedule;

import java.util.HashSet;
import java.util.Stack;

public class Solution {

	private class Edge {
		int next;
		int to;

		public Edge(int next, int to) {
			super();
			this.next = next;
			this.to = to;
		}
	};

	int[] head;
	Edge[] edges;
	int cntE;

	public void add(int u, int v) {
		Edge e = new Edge(head[u], v);
		edges[++cntE] = e;
		head[u] = cntE; // 第一条边为当前边

	}

	public boolean canFinish(int numCourses, int[][] prerequisites) {
		Stack<Integer> stack = new Stack<>();
		int len = prerequisites.length;
		HashSet<Integer> set = new HashSet<>();

		int[] indegree = new int[numCourses];
		head = new int[numCourses];
		edges = new Edge[len + 1];

		for (int i = 0; i < len; i++) {
			indegree[prerequisites[i][1]]++;
			add(prerequisites[i][0], prerequisites[i][1]);
		}

		for (int i = 0; i < numCourses; i++) {
			set.add(i);
		}

		for (int i = 0; i < numCourses; i++) {
			if (indegree[i] == 0)
				stack.add(i);
		}

		while (!stack.empty()) {
			int cur = stack.pop();
			set.remove(cur);
			for (int i = head[cur]; i != 0; i = edges[i].next) {
				indegree[edges[i].to]--;
				if (indegree[edges[i].to] == 0)
					stack.push(edges[i].to);
			}
		}
		if (set.size() == 0)
			return true;
		else
			return false;
	}

}
复制代码

第三题:LeetCode 871 Minimum Number of Refueling Stops

之前面试一道百度的题,大致是:开车从起点到终点,中间有若干个加油站,汽车的油箱容量无限,求能到达终点的最少加油次数;如果没有办法到达终点,输出-1。我在网上找到了一道类似的题目,就是就是LeetCode 871。

题目

A car travels from a starting position to a destination which is target miles east of the starting position.

Along the way, there are gas stations. Each station[i] represents a gas station that is station[i][0] miles east of the starting position, and has station[i][1] liters of gas.

The car starts with an infinite tank of gas, which initially has startFuel liters of fuel in it. It uses 1 liter of gas per 1 mile that it drives.

When the car reaches a gas station, it may stop and refuel, transferring all the gas from the station into the car.

What is the least number of refueling stops the car must make in order to reach its destination? If it cannot reach the destination, return -1.

Note that if the car reaches a gas station with 0 fuel left, the car can still refuel there. If the car reaches the destination with 0 fuel left, it is still considered to have arrived.

Example 1:

Input: target = 1, startFuel = 1, stations = []
Output: 0
Explanation: We can reach the target without refueling.

Example 2:

Input: target = 100, startFuel = 1, stations = [[10,100]]
Output: -1
Explanation: We can’t reach the target (or even the first gas station).

Example 3:

Input: target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
Output: 2
Explanation:
We start with 10 liters of fuel.
We drive to position 10, expending 10 liters of fuel. We refuel from 0 liters to 60 liters of gas. Then, we drive from position 10 to position 60 (expending 50 liters of fuel),
and refuel from 10 liters to 50 liters of gas. We then drive to and reach the target. We made 2 refueling stops along the way, so we return 2.

Note:

1 <= target, startFuel, stations[i][1] <= 10^9
0 <= stations.length <= 500 0 < stations[0][0] < stations[1][0] < … < stations[stations.length-1][0] < target

思路

如果需要加油,如果能够预料到在哪一个加油站到达之前缺油,一定是在该加油站之前经过的加油站中油量最多的加油站加油最划算(一次加油加得更多),所有用一个堆维护所有的经过的加油站,保证堆头元素一定是已经经过但没有在该站加过油的油量最多的加油站。当遇到在某个站之前会缺油,就从堆头取加油站,并使得加油次数加一;当所有已经过的加油站都取完且加完油,还是不能到达下一个加油站,说明之前所有的加油站的油量加上初始油量都不能支持到达下一个加油站,返回-1。

代码

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

public class Solution {
	public class GasStation {
		public int pos;
		public int gas;

		public GasStation(int pos, int gas) {
			super();
			this.pos = pos;
			this.gas = gas;
		}

	}

	class GasStationComparator1 implements Comparator<GasStation> {
		@Override
		public int compare(GasStation o1, GasStation o2) {
			return o1.pos - o2.pos;
		}
	}

	class GasStationComparator2 implements Comparator<GasStation> {
		@Override
		public int compare(GasStation o1, GasStation o2) {
			return o2.gas - o1.gas;
		}
	}

	public int minRefuelStops(int target, int startFuel, int[][] stations) {
		int num_GS = stations.length;
		List<GasStation> list = new ArrayList<>();
		for (int[] gs : stations) {
			list.add(new GasStation(gs[0], gs[1]));
		}
		list.add(new GasStation(target, 0));
		list.sort(new GasStationComparator1());
		int gas = startFuel;
		PriorityQueue<GasStation> heap = new PriorityQueue<>(new GasStationComparator2());
		int cnt = 0;

		for (int i = 0; i < num_GS + 1; i++) {
			GasStation gs = list.get(i);
			
			
			if (gs.pos > gas) {
				while (gs.pos > gas && !heap.isEmpty()) {
					//System.out.println(heap.peek().pos + " " + heap.peek().gas);
					gas += heap.poll().gas;
					
					cnt++;
				}
				if (gs.pos > gas && heap.isEmpty()) {
					return -1;
				}
			}
			heap.add(gs);
		}
		return cnt;
	}
}
复制代码

结语

以上是我这段时间校招中积累的一些题,最近感冒严重,暂时先更新这些。各位小伙伴们加油,有不争取的地方也欢迎大家指正。

我在参加掘金技术征文,活动进行中,详情请戳👉(15) 秋招求职时,写文就有好礼相送 | 掘金技术征文