字節跳動-2020秋招-筆試題剖析【5道算法題】,限時120分鐘。
讓我們一起來看看這些題吧!
題一:模型文件去重
【題目描述】
抖音上不同的用戶類型我們有不同的用戶模型文件。
我們有一個模型配置文件,裏面有很多的不同的用戶類型和他們對應的模型文件。我們需要找出每個模型對應的是哪些用戶類型。
給定一行輸入,格式是a b
a表示這個用戶的用戶類型,b表示這個用戶對應的模型文件。
請你輸出每個模型文件對應的用戶類型。
注意1:每個模型文件可能對應多個用戶類型,用戶類型之間用空格作爲切分。
注意2: 如果有多個用戶類型輸出,用戶類型之間的排序按照字母表排序。
注意3: 如果有多個模型輸出,模型輸出的順序按照模型文件在輸入數據中順序,即從上到下。
【輸入描述】
輸入第1行: 用戶類型 N(表示有多少個 用戶類型)
接下來的N行:用戶類型 模型文件
【輸出描述】
模型文件用戶類型1 用戶類型2
【示例】
輸入:
1
abc 1.txt
輸出:
1.txt abc
【解決思路及要點】
- 一個模型對應多個用戶,用戶需要按照字典序進行排序。針對這一點,可以把用戶數據存入Set集合,並用哈希表把模型和用戶的Set集合進行關聯。
- 有多個模型的話模型輸出的順序按照模型文件在輸入數據中順序,可以把模型再存入List集合。
- 注意一下輸出的格式。
【解決代碼】
- 比較簡單,沒有註釋。
public class Solution1 {
public static void main(String[] args) {
List<String> model=new ArrayList<>();
Map<String,Set<String>> table=new HashMap<>();
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
String a=new String();
String b=new String();
while(n-->0){
a=sc.next();
b=sc.next();
if(!table.containsKey(b)){
model.add(b);
table.put(b, new HashSet<String>());
}
table.get(b).add(a);
}
for(String m:model){
System.out.print(m);
for(String consumer:table.get(b)){
System.out.print(" "+consumer);
}
System.out.print("\n");
}
}
}
【題目剖析】
- 這個題不難,只需要注意一下用戶的排序,模型的順序,以及輸出的格式就可以了。
- 這個題應該是可以在短時間內AC的。
題二:穿越沙漠的補給次數
【題目描述】
旅行者穿越沙漠的過程中需要不斷地消耗攜帶的飲用水,到達終點前會經過幾個綠洲,每個綠洲均設有水分補給站可以爲旅行者提供水分補給並收取一定的費用。
沿途共有n個補給站,每個補給站收取的費用都一樣,但是提供的水量不盡相同。起點到終點的距離爲D公里,postion[i]表示第i個補給站距離起點的距離,單位爲公里,supply[i]表示第i 個補給站可以提供的水量,單位爲升。
假設旅行者在起點時攜帶了W升的水,每行走1公里需要消耗1升的水量,身上可攜帶的水量沒有上限,且攜帶的水量多少不會對體能消耗產生影響,鑑於每次進補給站花費的錢都是一樣多,期望用最少的補給次數到達終點,請幫忙計算最少的補給次數。
【輸入描述】
第一行輸入整數D和W, D表示起點到終點的距離,W表示初始攜帶的水量
第二行輸入數組postion,長度爲N,分別表示N個補給站分別距離起點的距離
第三行輸入數組supply,長度爲N, 分別表示N個補給站分別可以供給的水量
數據範圍:1 <= D, W<=10^8, 0<=N<=1000, 0<position[i],supply[i]<D
【輸出描述】
輸出一個整數表示最少的補給次數,若無法到達終點則返回-1
【示例】
輸入:
10 4
1 4 7
6 3 5
輸出:
1
說明:
每行輸入用空格隔開。起點到終點共10公里,初始時攜帶4升水,途徑3個補給站。共需補給一次:只需在第1個補給站補給一次獲得6升水,即可走完全程。
【解決思路及要點】
- 貪心的思想。
- 先假設用完身上所有的水,能走到一個位置,然後再去考慮去這個位置之前的水站加水,爲了保證最少的加水次數,應該到能加到最多水的水站加水。
- 加完水後,需要標記,防止以後重複從這取水。
【解決代碼】
public class Solution2 {
public static int minCount(int D,int W,int[] position,int[] supply){
int ans=0;
int n=position.length;
//標記數組,true代表已經取過水了
boolean[] flag=new boolean[n];
//當前所在位置
int currPos=0;
while(currPos<D){
//每次都把水喝完
currPos+=W;
W=0;
//如果走完,直接返回
if(currPos>=D){
return ans;
}
//記錄能獲得最多水的下標
int maxWater=-1;
for(int i=0;i<n;i++){
//還未達到相應的補給站
if(position[i]>currPos){
break;
}
//如果如果還沒從該水站取水,就看在這裏取水能否得到最大的水量
if(!flag[i]&&supply[i]>W){
W=supply[i];
maxWater=i;
}
}
//此時無水,並且沒有可以取水的水站,無法到達
if(maxWater==-1){
return -1;
}
flag[maxWater]=true;
ans++;
}
return ans;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int D=sc.nextInt();
int W=sc.nextInt();
sc.nextLine();
String s = sc.nextLine();
String[] s1 = s.split(" ");
int n=s1.length;
int[] position=new int[n];
int[] supply=new int[n];
for (int i = 0; i < n; i++) {
position[i]=Integer.valueOf(s1[i]);
}
s = sc.nextLine();
String[] s2 = s.split(" ");
for (int i = 0; i < n; i++) {
supply[i]=Integer.valueOf(s2[i]);
}
System.out.println(minCount(D,W,position,supply));
}
}
【題目剖析】
- 初看這個題感覺這個應該是dp的題,事實上確實也能用dp比較快速的解決,不過需要有大量dp的經驗,能夠立馬提取出這個dp的模型,不然總是會在狀態轉移方程裏迷惑。
- 貪心的思想就比較直接,要使用最少的補給,肯定是前往補給多的補給站加水。
- 事實上有貪心思想後,其實是比較疑惑的,這樣做是不是一定正確呢?我感覺貪心思路要麼是以前遇到過類似的,要麼就要大膽的嘗試,用理論證明貪心短時間內還是比較困難。
- 個人使用貪心的感覺是:先是用題目的數據,自己在大腦裏去嘗試用一下這種思想,發現沒什麼漏洞,和dp的思路也比較相近,就果斷的使用了。
- 這道題可能不是很難,但是如果不熟悉dp或者貪心的話,可能無法在短時間內做出,也算是一個有區分度的題目了吧。
題三:走迷宮
【題目描述】
給定一個迷宮,找到最快從起點到達重點的路徑所需要的步數。
假設迷宮如下,假定左上角座標爲(0,0),右下角座標爲 (3,2)
1 0 -1 1
-2 0 -1 -3
2 2 0 0
-2是迷宮的起點,座標爲(0,1)
-3是迷宮的終點,座標爲(3,1)
-1代表障礙物,不能行走
1和2代表傳送門,傳送門由正整數標示,只會成對出現。站在傳送門上,能僅用一步就傳送到相同數字的另一個傳送門的位置:1只能傳送到1,2只能傳送到2。站在傳送門上也可以選擇不傳送。
從起點到終點有若干種走法,舉例如下:
(0,1)->(1,1)->(1,2)->(2,2)->(3,2)->(3,1),共花費5步
或者
(0,1)->(0,0)-傳送>(3,0)->(3,1),共花費3步
經檢驗3步是所需的最少步數,最後結果返回3
【輸入描述】
每一行輸入都是用空格隔開的整數
第一行給出迷宮地圖的長和寬,均爲正整數
之後每一行的每一個數字,都代表迷宮的一格
-2表示起點,-3表示終點,-1表示不可通過的障礙物,0表示可通過的道路,大於0的正整數代表傳送門,並且保證成對出現,在傳送門上,可以僅用一步傳送到另一個相同數字的傳送門的位置。
迷宮大小<=200*200
【輸出描述】
輸出最少要多少步能夠從起點走到終點。
輸出-1如果沒有任何辦法從起點走到終點。
【示例】
輸入:
4 3
1 0 -1 1
-2 0 -1 -3
2 2 0 0
輸出:
3
說明:
(0,1)->(0,0)-傳送>(3,0)->(3,1) ,共花費3步
【解決思路及要點】
- 經典的走迷宮問題,只不過加入了傳送門,處理的思路也差不多。
- 採用BFS。
- 題目對迷宮的信息有些大,所以我們需要對迷宮裏的點,當前的狀態,傳送門進行封裝,達到減少代碼量,並且方便理清思路的目的。
- 具體做法在代碼中有完整的註釋。
【解決代碼】
public class Solution3 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
//獲取長寬
String[] dimension = sc.nextLine().split(" ");
int width = Integer.valueOf(dimension[0]);
int height = Integer.valueOf(dimension[1]);
//迷宮
Square[][] map = new Square[width][height];
//一個y對應底圖的一列
int y = 0;
//起始點和終點
Square start = null;
Square end = null;
//傳送門對應的哈希表
Map<Integer, WarpGate> warpGate = new HashMap<>();
//獲取地圖,同時初始化相關的類
while (y<height) {
String line = sc.nextLine();
String[] squares = line.split(" ");
for (int x = 0; x < width; x++) {
map[x][y] = new Square(x, y, Integer.valueOf(squares[x]));
if (map[x][y].type == Square.SQUARE_START) {
start = map[x][y];
} else if (map[x][y].type == Square.SQUARE_END) {
end = map[x][y];
} else if (map[x][y].isWarpGate()) {
//建立傳送門
int gateNumber = map[x][y].type;
WarpGate wg = warpGate.get(gateNumber);
if (wg == null) {
wg = new WarpGate();
warpGate.put(gateNumber, wg);
}
wg.buildWarpGate(map[x][y]);
}
}
y++;
}
if (start == null || end == null) {
throw new RuntimeException("未發現起始點或者終點!");
}
//迷宮創建成功,開始運算
System.out.println(solve(start, end, map, warpGate));
}
//走迷宮核心代碼
public static int solve(Square start, Square end, Square[][] map, Map<Integer, WarpGate> warpGate) {
//標誌是否訪問過
Set<State> visited = new HashSet<>();
//BFS隊列
Set<State> queue = new LinkedHashSet<>();
//放入起始點
queue.add(new State(start, 0));
while(!queue.isEmpty()) {
Iterator<State> currentIt = queue.iterator();
//獲取當前當前走的點
State current = currentIt.next();
currentIt.remove();
//走到終點
if (current.square.equals(end)) {
return current.step;
}
//更新標誌
visited.add(current);
//尋找下一個要走的點
current.nextStates(map, queue, visited, warpGate);
}
return -1;
}
//迷宮中的一個點
public static class Square {
public final int x;
public final int y;
public final int type;//這個點的類型
//題目中可能的類型
public static final int SQUARE_START = -2;
public static final int SQUARE_END = -3;
public static final int SQUARE_PATH = 0;
public static final int SQUARE_BLOCK = -1;
public Square(int x,int y,int type){
this.x = x;
this.y = y;
this.type = type;
}
//提供專門判斷是否是傳送門的方法
public boolean isWarpGate() {
return type > 0;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
//傳送門類
public static class WarpGate{
public Square s1;
public Square s2;
//獲取傳送到的點,不是s1或者s2就返回null
public Square getOtherSide(Square s) {
if (s.equals(s1)) {
return s2;
} else if (s.equals(s2)) {
return s1;
} else {
return null;
}
}
//建立一個傳送門
public void buildWarpGate(Square s) {
if (s1 == null) {
s1 = s;
} else if (s2 == null) {
s2 = s;
} else {
throw new RuntimeException("建立傳送門異常!");
}
}
}
//一個點的狀態
public static class State{
public final Square square;
public int step;
public State(Square square, int step) {
this.square = square;
this.step = step;
}
//更新到下一個未訪問過的點
public void nextStates(Square[][] map, Set<State> toVisit,
Set<State> visited, Map<Integer, WarpGate> warpGate){
//左邊的點存在,嘗試加入隊列
if (square.x > 0) {
Square left = map[square.x - 1][square.y];
addUnvisitedStateToList(left, toVisit, visited);
}
//右邊的點存在,嘗試加入隊列
if (square.x < map.length - 1) {
Square right = map[square.x + 1][square.y];
addUnvisitedStateToList(right, toVisit, visited);
}
//下邊的點存在,嘗試加入隊列
if (square.y > 0) {
Square up = map[square.x][square.y - 1];
addUnvisitedStateToList(up, toVisit, visited);
}
//上邊的點存在,嘗試加入隊列
if (square.y < map[0].length - 1) {
Square down = map[square.x][square.y + 1];
addUnvisitedStateToList(down, toVisit, visited);
}
//嘗試走傳送門
WarpGate gate = warpGate.get(square.type);
if (gate != null) {
addUnvisitedStateToList(gate.getOtherSide(square), toVisit, visited);
}
}
private void addUnvisitedStateToList(Square s, Set<State> toVisit, Set<State> visited) {
State resultState = new State(s, this.step + 1);
//滿足條件的點加入隊列
if (s.type != Square.SQUARE_BLOCK && !visited.contains(resultState) && !toVisit.contains(resultState)) {
toVisit.add(resultState);
}
}
}
}
【題目剖析】
- 這個題應該是這場比試區分度最大的一個題了,因爲真的很考驗編碼能力。
- 採用BFS的思路大家應該一般都很熟悉,主要是如何把一些細節考慮完善,如何封裝裏面的一些信息。
- 稍有不慎,出了點錯誤,再想改正就比較麻煩了。
- 在這種迷宮問題裏,debug的時間消耗是非常大的,所以這就這就非常考驗平時的編碼能力,能一遍過,就已經很優秀了。
- 如果第一遍沒有過,那麼可以先做後面的題,這個題雖然說題型比較常見,但也不是那麼容易AC的。
題四:簡單變換
【題目描述】
春節在家的凱凱真的是太無聊了,他準備和他家的貓玩一個遊戲。
凱凱在黑板上寫下了兩個長度相等的數列a[1…n], b[1…n]。
現在他想讓他的貓判斷數列a能否通過一個操作變換成數列b。
這個操作是:在數列a中選擇一個區間l-r,對這個區間所有的數字加上一個k。
其中1<=l<=r<=n, k>=0。
你可以幫幫可憐的小貓做出這個判斷麼?
【輸入描述】
首先輸入一個數字t,表示有t組數據
每組數據的第一行爲一個數字n 表示數列的長度
接下來兩行每行有n個數字,分別爲數組a和數組b
t<=10
n<=100000
【輸出描述】
對於每組數據輸出YES 或者 NO
表示數列a能否通過對應的操作變換成數列b。
【示例】
輸入:
2
6
3 7 1 4 1 2
3 7 3 6 3 2
5
1 1 1 1 1
1 2 1 3 1
輸出:
YES
NO
說明:
對於第一個樣例可以對區間[1,4,1] 的每個數字加上2,即可把數列a轉換成數列b
對於第二個樣例沒法做相應的操作
【解決思路及要點】
- 思路很多,這裏採用差分數組的方法。
- 對數組做差,然後遍歷數組檢驗是否可以轉換。
- 注意轉換的那段區間應該是連續的。
- 具體討論在代碼中有詳細註釋。
【解決代碼】
public class Solution4 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int t=sc.nextInt();
while(t-->0){
int n=sc.nextInt();
int[] a=new int[n];
int[] b=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){
a[i] = b[i] - a[i];
}
//左右指針
int left=-1,right=-1;
int k=0;
//標記是否成功轉換
boolean flag=true;
for(int i=0;i<n;i++){
//a[i]屬於相等的部分
if(a[i]!=0){
//更新左指針
if(left==-1){
left=i;
}
//更新右指針
if (right == -1 || right == i - 1){
right=i;
}else if(right!=-1 && right!=i-1){
//此時若右指針不在初始位置並且不在i-1的位置,說明需要轉換的地方不連續,無法進行轉換操作
System.out.println("NO");
flag=false;
break;
}
}else if(a[i]<0){
//此時說明數組b需要加,對a操作不可能轉換
System.out.println("NO");
flag=false;
break;
}else if(a[i]>0){
//a數組要加,如果k不是初始值,也不和a[i]相等,也無法轉換
if(k!=0 && k!=a[i]){
System.out.println("NO");
flag=false;
break;
}
k=a[i];
}
}
if(flag){
System.out.println("YES");
}
}
}
}
【題目剖析】
- 這道題應該算是比較簡單的了,用差分數組可以很快的解決,不過要注意一下不滿足轉換條件的判斷。
- 總體來說,這道題一遍過的可能性還是非常大的。
- 應該也是能在短時間內解決的題目。
題五:優惠券
【題目描述】
你有n種無門檻優惠券,每種優惠券有一個面值ai。當一件商品的售價≥ai時,你可以出示這種優惠券直接抵扣。抵扣後,優惠券不會被回收,可以繼續使用。現在,你想要買m件商品,每件商品的售價是bi,請問你最少需要花費多少錢?
【輸入描述】
第一行兩個正整數n,m(1≤n,m≤10^6)
第二行n個正整數ai (0≤ai≤10^6),代表n種無門檻優惠券的面值 (不保證排序)
第三行m個正整數bi (0≤bi ≤10^6),代表m件商品的價格 (不保證排序)
【輸出描述】
輸出合理使用優惠券後,購買上述m件商品最少需要的花費。
【示例】
輸入:
3 4
50 100 200
99 199 200 300
輸出:
248
【解決思路及要點】
- 由於優惠券可以重複使用,那麼這個題就很簡單了。
- 只需要對優惠券數組進行排序,然後再用二分搜索找到最接近價格的優惠券即可。
- 注意一下:Java中BinarySearch返回的值的含義。
【解決代碼】
public class Solution5 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
int[] a=new int[n];
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
Arrays.sort(a);
long ans=0;
while(m-->0){
int price=sc.nextInt();
int index=Arrays.binarySearch(a,price);
//注意二分搜索的返回值
if(index<0){
index=-index-2;
}
ans+=price-a[index];
}
System.out.println(ans);
}
}
【題目剖析】
- 這道題非常簡單,需要看清題目,並且對相關API熟悉。
- 應該是可以在極短時間內做出的。
該次筆試題感悟:
- 首先對這些題目的難度大體排一下,應該是:一,五,四,二,三。
- 說明不一定一個一個挨着做。
- 熟悉的模型是裏面最有區分度的一個題,說明需要加強平時的硬編碼能力。
- 兩個小時的時間,一,四,五應該都是可以完美解決的,二可能需要花一點時間,三完全AC可能有些難度。
- 總體來說,這些題的難度還不是很大。
- 平時的積累一定要多回顧一下,不然碰到見過的模型,又忘了如何處理就比較麻煩了。
- 最後:好好積累,好好學!