校招中我,細細品味了這些題... | 掘金技術徵文

jxy370發表於2018-10-11

本人北京某高校計算機碩士,總覺得本科畢業還是不久前的事情,轉眼間研究生也要畢業了,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) 秋招求職時,寫文就有好禮相送 | 掘金技術徵文

校招中我,細細品味了這些題... | 掘金技術徵文

相關文章