今日頭條2018校園招聘後端開發工程師(第三批)程式設計題
昨天做了下頭條的後端開發工程師的程式設計題,這編碼量大啊,兩個小時,三個程式設計題,一個改錯題,一個設計題,說實話,很考技術含量,而且程式設計題中有兩個還特別考細心編碼,如果兩個小時能做三個題,確實非常不錯了,寫下解題報告吧,根據自己截的圖和別人截的圖把題還原下,並給出自己的解法,自己不確定是不是最優的,如果有不同意見可以一起討論.
第一題:推箱子
題目:
給你一個矩陣,裡面有‘#’,表示障礙,‘.’表示空地,‘S’表示人開始的位置,‘E’表示箱子的預期位置,‘0’表示箱子的初始位置,你的任務是把箱子從‘0’推到‘E’.
注意不能將箱子推到‘#’上,也不能將箱子推出邊界;
現在給你遊戲的初始樣子,你需要輸出最少幾步能夠完成遊戲,如果不能完成,則輸出-1.
輸入描述:
第一行為2個數字,
n,m ,表示遊戲盤面大小有n 行m 列(5<n,m<50 );後面為n 行字串,每行字串有m 字元,表示遊戲盤面.
輸出描述:
一個數字,表示最少幾步能完成遊戲,如果不能,輸出-1.
樣例輸入:
36
.S#..E
.#.0..
......
樣例輸出:
11
解析:
首先可以看出是一個
bfs 問題,推箱子的時候首先人要站到箱子緊挨的四個方向的格子中,才能把箱子推向某一個方向,因此,兩個b ,第一個fsbfs 搜尋箱子的位置,第二個bfs 搜尋人的位置到箱子緊挨的四個方向的格子要走的步數,大致方向就是這樣,下面說下細節.
- 到終點時,箱子可以從四個方向推到終點,因此要維護一個最小值,當然了,箱子可能並不一定可以從四個方向推到終點,這個你自己去判一下就好了;
- 我用了優先佇列做優化,原因是人到箱子的四個方向的步數大小不一,因此我們優先選擇總步數最小的擴充套件;
- 在第二個
bfs 中,要把箱子當成障礙物;- 走過的地方不能再走,但是這裡位置的唯一性要由人的位置和箱子的位置共同確定,因此要開一個4維陣列,不過我們通過
hash 可以降到2維;- 我們把方向按:上 - 0,右 - 1, 下 - 2,左 - 3編號,為的就是在編碼的時候可以方便統一地求人要走的那個位置和箱子即將推到的那個位置.
程式碼:
#include <bits/stdc++.h>
using namespace std;
const int dirx[] = {-1, 0, 1, 0};
const int diry[] = {0, 1, 0, -1};
int n, m;
struct Point {
int x, y;
Point() {}
Point(int x, int y) : x(x), y(y) {}
bool operator == (const Point &other) const {
return x == other.x && y == other.y;
}
};
struct Node {
Point peo, box;
int step;
Node() {}
Node(Point peo, Point box, int step = 0) :
peo(peo), box(box), step(step) {}
bool operator < (const Node &other) const {
return step > other.step;
}
};
struct Peo {
Point point;
int step;
Peo() {}
Peo(Point point, int step = 0) :
point(point), step(step) {}
};
bool check(int x, int y)
{
return x >= 0 && x < n && y >= 0 && y < m;
}
int bfs(Point src, Point des, Point box, vector<string> &mp)
{
queue<Peo> que;
vector<vector<bool> > used(n, vector<bool>(m, false));
que.push(src);
used[src.x][src.y] = true;
int ret = -1;
while (!que.empty()) {
auto now = que.front();
que.pop();
if (now.point == des) {
ret = now.step;
break;
}
for (int k = 0; k < 4; k++) {
int tx = now.point.x + dirx[k];
int ty = now.point.y + diry[k];
if (check(tx, ty) && mp[tx][ty] == '.' && !(Point(tx, ty) == box) && !used[tx][ty])
que.emplace(Point(tx, ty), now.step + 1), used[tx][ty] = true;
}
}
return ret;
}
int main()
{
// freopen("in", "r", stdin);
while (cin >> n >> m) {
string str;
vector<string> mp;
Point initS, initE, initZ;
for (int i = 0; i < n; i++) {
cin >> str;
mp.push_back(str);
for (int j = 0; j < str.size(); j++) {
if (str[j] == 'S') {
initS = Point(i, j);
mp[i][j] = '.';
break;
}
}
for (int j = 0; j < str.size(); j++) {
if (str[j] == 'E') {
initE = Point(i, j);
mp[i][j] = '.';
break;
}
}
for (int j = 0; j < str.size(); j++) {
if (str[j] == '0') {
initZ = Point(i, j);
mp[i][j] = '.';
break;
}
}
}
vector<vector<bool> > used(n * m + 1000, vector<bool>(n * m + 1000, false));
priority_queue<Node> que;
que.emplace(initS, initZ);
used[initS.x * n + initS.y][initZ.x * n + initZ.y] = true;
int ans = 0x3f3f3f3f;
int cnt = 0;
for (int k = 0; k < 4; k++)
if (check(initE.x + dirx[k], initE.y + diry[k]) && mp[initE.x + dirx[k]][initE.y + diry[k]] == '.')
++cnt;
while (!que.empty()) {
auto now = que.top();
que.pop();
if (now.box == initE) {
cnt--;
ans = min(now.step, ans);
if (cnt == 0)
break;
else
continue;
}
for (int k = 0; k < 4; k++) {
int tmpSx = now.box.x + dirx[k];
int tmpSy = now.box.y + diry[k];
int tmpZx = now.box.x + dirx[(k + 2) % 4];
int tmpZy = now.box.y + diry[(k + 2) % 4];
if (!check(tmpSx, tmpSy) || !check(tmpZx, tmpZy))
continue;
if (mp[tmpSx][tmpSy] != '.')
continue;
if (mp[tmpZx][tmpZy] != '.')
continue;
if (used[now.box.x * m + now.box.y][tmpZx * m + tmpZy])
continue;
int step = bfs(now.peo, Point(tmpSx, tmpSy), now.box, mp);
if (step == -1)
continue;
que.emplace(now.box, Point(tmpZx, tmpZy), step + now.step + 1);
used[now.box.x * m + now.box.y][tmpZx * m + tmpZy] = true;
}
}
cout << (ans == 0x3f3f3f3f ? -1 : ans) << endl;
}
return 0;
}
第二題:房間
題目:
有
n 個房間,現在i 號房間裡的人需要被重新分配,分配的規則是這樣的:先讓i 號房間的人全部出來,接下來按照i+1,i+2,i+3,... 的順序依次往這些房間裡放一個人,n 號房間的下一個房間是1號房間,直到所有人被重新分配。現在告訴你分配完後每個房間的人數以及最後一個人被分配的房間號
x ,你需要求出分配前每個房間的人數,資料保證一定有解,若有多解輸出任意一個解。
輸入描述:
第一行兩個整數
n,x(2<=n<=105,1<=x<=n) ,代表房間數量以及最後一個人被分配到的房間號;
第二行n 個整數ai(0<=ai<=109) ,代表每個房間分配後的人數。
樣例輸入:
31
651
樣例輸出:
444
解析:
首先,如果所有房間都有人,這個問題很好考慮,找最小值,例如最小值是3,長度為4,那麼就迴圈了3圈,然後從最後一個分配到的房間向前數,數到最小值數了多少下。例如樣例中,第三個房間的人肯定是被請出去的,那麼第三個房間的原本人數就是
1×3+1=4 ,第一個房間的原本人數就是6−1−1=4 ,第二個房間原本的人數是5−1 ,那麼我們就得到了一個一般的解法:先找最小值,然後所有房間的人數先減去最小值,再從最小值這個地方先後到最後一個分配的房間要再減一,最後最小值這個地方的人數就是最小值乘長度加上最後一個人分配的地方向前到這最小值這個地方的距離減一.但是現在存在有的房間可能沒人,這就意味著最小值可能有很多個,那麼就要分兩種情況了,一、最後一個分配的房間裡的人數就是最小值,這種情況這個房間裡的人就是先前被請出去了,也就是迴圈了
theMin 圈,很好計算;二、最後一個分配的房間裡的人數不是最小值,那麼先前被請出去的房間就是從最後一個分配的房間向前數第一個遇到的最小值的房間,自己畫一個圖就理解了.
程式碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main()
{
int n, k;
while (~scanf("%d%d", &n, &k)) {
vector<LL> arr;
LL theMin = 0x3f3f3f3f3f;
for (int i = 0; i < n; i++) {
LL x;
scanf("%lld", &x);
arr.push_back(x);
theMin = min(theMin, x);
}
if (arr[k - 1] == theMin) {
for (vector<LL>::size_type i = 0; i < arr.size(); i++) {
if (i == k - 1)
printf("%lld", 1LL * theMin * n);
else
printf("%lld", arr[i] - theMin);
putchar(i == arr.size() - 1 ? '\n' : ' ');
}
} else {
int i, d;
for (i = k - 1, d = 0; theMin != arr[(i + n) % n]; i--, d++)
arr[(i + n) % n] = arr[(i + n) % n] - theMin - 1;
arr[(i + n) % n] = 1LL * theMin * n + d;
if ((i + n) % n != k) {
for (--i; (i + n) % n != k; i--)
arr[(i + n) % n] = arr[(i + n) % n] - theMin;
arr[(i + n) % n] = arr[(i + n) % n] - theMin;
}
for (vector<LL>::size_type i = 0; i < arr.size(); i++)
printf("%lld%c", arr[i], i == arr.size() - 1 ? '\n' : ' ');
}
}
return 0;
}
附加題:二階魔方
題目:
二階魔方又叫小魔方,是2*2*2的立方體結構,每一面都有4個塊,共有24個塊,每次操作可以將一面逆時針或者順時針旋轉
90。 ,如將上面逆時針旋轉90。 操作如下:
Zero在小魔上做了一些改動,用數字替換每個塊上面的顏色,稱之為數字魔方。魔方上每一面的優美度就是這個面上4個數字的乘積,而魔方的總優美度就是6個面優美度的總和。現在Nero有一個數字魔方,他想知道這個魔方在操作步超過5次的前提下能達到的最大優美度是多少。
魔方展開後每一塊的序號如下圖:
輸入描述:
輸入一行包含24個數字,按序號順序給出魔方每一塊上面的數字。所有數字的範圍為
[−100,100] 。
輸出描述:
輸出一行包括一個數字,表示最大優美度。
樣例輸入:
2−3−237−6−6−79−5−9−3−214−9−1−10−5−5−10−482
樣例輸出:
8281
解析:
模擬題,最主要的是寫出6個移動位置的置換群,仔細點寫,我一開始寫錯好幾次,太多了,本來想用程式碼來寫,但是想了下,還不如手寫呢,最後我是通過下面這個
debug
函式才找出來我哪個地方寫錯了,如果這個函式輸出的全是1,那麼數字上就沒錯,但是不保證你的置換方式有沒有錯.
void debug()
{
for (int i = 0; i < 6; i++) {
int a[24];
memset(a, 0, sizeof(a));
cout << i << ":" << endl;
for (int j = 0; j < 24; j++)
a[mp[i][j]]++;
for (int j = 0; j < 24; j++)
printf("%d ", a[j]);
puts("");
}
}
還要注意一點,這裡順時針逆時針只要考慮一個就好了,因為順時針移動三次可以得到逆時針!
時間複雜度
O(125) 可以接受,不過這個題如果說在15步內那就不是這麼簡單的了,首先,15步去暴搜是不行的,太多了,那麼怎麼辦呢,這個時候我們就要考慮魔方的“上帝之數”了,二階魔方在任意狀態11步內是可以還原的,那麼也就是說,15步可以從任意一個狀態到另一個任意的狀態了,那麼這個問題就變成了,24個數字分成6組,求這6個組的乘積最大和了,但是要注意一點,有些數字是永遠不可能出現在同一面的,這個要考慮到,比如一條稜上的兩個數字。這樣子考慮不用去模擬魔方轉動了,但是好像這個問題變難了,貪心是肯定搞不了的,我也不知道做了.
程式碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mp[6][24] = {
// {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
{0, 1, 2, 3, 4, 5, 6, 7,