題目
有一個二維數組(n*n),寫程序實現從右上角到左下角沿主對角線方向打印。
給定一個二位數組arr及題目中的參數n,請返回結果數組。
測試樣例:
[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]],4
返回:
[4,3,8,2,7,12,1,6,11,16,5,10,15,9,14,13]
解題
1. 解題思路A
- 利用廣搜思想(我也很想知道爲什麼我會想到廣搜=。=)
如下圖所示,從右上角的4出發,先往左訪問3,再往下訪問8;然後從4的左兒子3出發,先往左訪問2,再往下訪問7;再從4的下兒子出發,往左訪問7再往下訪問12,以此類推(藍色→橙色→綠色→紅色→黃色…)
那麼就有一個問題,有可能會重複訪問,例如上述的7在訪問3的時候被加入隊列,但在訪問8的時候又一次被加入隊列,顯然是需要剪枝的。這裏列一下整個隊列的添加情況應該是這樣的:
[4, 3, 8, 2, 7, (7), 12, 1, 6, (6), 11, (11), 16, 5, (5), 10, (10), 15, (15), 9, (9), 14, (14), 13]
- 可以看到可能會重複訪問的點都在每一次加入兒子節點後的隊列尾部,而剪枝後的隊列順序就是題目要求的打印順序,所以只需要維護一個隊列,每次從隊列裏pop一個元素就將其加入結果容器中,並在每次訪問兒子節點時都要判斷是否和隊尾元素重複
- 代碼如下,佔用內存5048k,運行時間232ms
class Printer {
public:
vector<int> arrayPrint(vector<vector<int> > arr, int n) {
queue<pair<int, int>> q;
q.push({ 0, n-1 });
vector<int> path;
pair<int, int> back_node;
while(!q.empty()){
// 訪問隊列最後一個點的座標
back_node = { q.back().first, q.back().second };
// 取出最先加入隊列的座標和值
pair<int, int> node = q.front();
q.pop();
// 將當前訪問點的值加入路徑隊列
path.push_back(arr[node.first][node.second]);
// 將當前點左邊的點加入隊列
if(node.second-1>=0){
if(back_node.first!=node.first || back_node.second!=node.second-1){
q.push({ node.first, node.second-1 });
}
}
// 將當前點下邊的點加入隊列
if(node.first+1<n){
if(back_node.first!=node.first+1 || back_node.second!=node.second){
q.push({ node.first+1, node.second });
}
}
}
return path;
}
};
2. 解題思路B
- 提交答案通過後看了別人的解題思路=。=
- 哇,爲什麼代碼可以這麼簡潔,認真思考後,得出一個教訓:不要急着解題,把題意認真思考過先,打印結果都沒完整看過一遍就想當然以爲是廣搜了。
- 實現別人的解題思路,代碼如下,佔用內存8196k,運行時間328ms(理解不能爲什麼內存佔用比廣搜還多?)
class Printer {
public:
vector<int> arrayPrint_other(vector<vector<int> > arr, int n) {
vector<int> result;
int startX=0, startY=n-1;
while(startX<n && startY<n){
int i=startX, j=startY;
while(i<n && j<n){
result.push_back(arr[i][j]);
i++;
j++;
}
if(startY>0) startY--;
else startX++;
}
return result;
}
};
- 其實思路很簡單,如下圖所示,以黃色覆蓋的點爲每一次訪問的起點,按藍色線順序,先是y座標依次減1,碰到邊界後x座標依次加1;然後對於每一個起點,座標x和y都+1按橙色線順序訪問(代碼裏用i和j分別迭代),主要就是邊界判斷問題了=。=