矩陣中從左上角到右下角最短路徑(五種方法)
阿新 • • 發佈:2018-12-24
題目:給定一個n*m的矩陣,矩陣中元素非負,從左上角到右下角找一條路徑,使得路徑上元素之和最小,每次只能向右或者向下走一個方格。如下圖所示:最短路徑是圖中綠色部分的元素。
方法一(轉換為圖中的最短路徑):我們可以把矩陣中的每個方格當做圖中的一個頂點,相鄰的方格之間有一條邊,每個方格最多有兩條出邊,(當前方格到右側方格有一條出邊,當前方格到下側方格有一條出邊)。我們把矩陣中的最短路徑轉換為圖中的最短路徑,使用Dijstra演算法來做此題,我們再次使用最簡單的Dijstra演算法,沒有進行優化。圖中總共有n*m個點,因為每一次都需要找到一個最小值,找最小值得代價為o(n*m),總共需要找o(n*m)個最小值,所以時間複雜度為o(n*n*m*m)。
方法二(動態規劃):時間複雜為o(n*m)和空間複雜度為o(n*m)。典型的動態規劃問題,假設當前已經開始計算s[i][j],那麼s[i][j]只可能從s[i-1][j]+grid[i][j]或者s[i][j-1]+grid[i][j]計算得到,也就是s[i][j] = min(s[i-1][j],s[i][j-1])+grid[i][j]。我們需要一個o(n*m)額外空間儲存已經計算的s[i][j]的值,我們只需要訪問一遍陣列即可。因此時間複雜度為o(n*m),空間複雜度為o(n*m)。我們需要特殊處理矩陣中第一行和第一列。因為第一行沒有s[i-1][j]元素,只有s[i][j-1]元素。第一列沒有s[i][j-1]元素,只有s[i-1][j]元素。程式碼如下:struct Node { int val; int row; int col; Node(){} Node(int v, int r, int c) :val(v), row(r), col(c) {} friend bool operator<(const Node &lhs, const Node &rhs); }; bool operator<(const Node &lhs, const Node &rhs) { return lhs.val > rhs.val; } struct Vertex { int dis; bool visited; Vertex(){} Vertex(int d, bool v) :dis(d), visited(v){} }; class Solution { public: Node findMinVal(vector<vector<Vertex>> ve) { Node res = Node(INT_MAX, 0, 0); for (int i = 0; i < ve.size(); ++i) { for (int j = 0; j < ve[0].size(); ++j) { if (!ve[i][j].visited && res.val > ve[i][j].dis) { res.col = j; res.row = i; res.val = ve[i][j].dis; } } } return res; } int minPathSum(vector<vector<int>>& grid) { int sum = 0; int rows = grid.size(); int cols = grid[0].size(); vector<vector<Vertex>> ve; ve.resize(rows); for (int i = 0; i < rows; ++i) { ve[i].resize(cols); for (int j = 0; j < cols; ++j) { ve[i][j] = Vertex(INT_MAX, false); } } ve[0][0].dis = grid[0][0]; while (true) { Node res = findMinVal(ve); int row = res.row; int col = res.col; sum = res.val; if (row == rows - 1 && col == cols - 1) break; ve[row][col].visited = true; if (row + 1 < rows && sum + grid[row + 1][col] < ve[row + 1][col].dis) { ve[row + 1][col].dis = sum + grid[row + 1][col]; } if (col + 1 < cols && sum + grid[row][col + 1] < ve[row][col + 1].dis) { ve[row][col + 1].dis = sum + grid[row][col + 1]; } } return sum; } };
方法三(動態規劃):時間複雜度為o(n*m),空間複雜度為o(m),此方法需要2*m額外空間。當我們求s[i][j]時,s[i-2]行的元素我們就不再需要,我們只需要s[i-1]行中的元素,我們把s[i-1]行中的元素儲存在pre陣列中,陣列的大小為m。我們把s[i]儲存在cur陣列中,當s[i]行的元素計算完畢以後,我們交換pre和cur陣列。因為需要pre陣列和cur陣列,且陣列的大小都為m,所以我們需要2*m大小的額外空間。int minPathSum(vector<vector<int>>& grid) { //當grid為空時,返回0 if (grid.empty()) return 0; //獲取grid的行數和列數 int rows = grid.size(); int cols = grid[0].size(); //額外陣列data,大小和grid一樣 vector<vector<int>> data(rows,vector<int>(cols,grid[0][0])); //處理data第一列 for (int i = 1; i < rows; ++i) { data[i][0] = data[i - 1][0] + grid[i][0]; } //處理data第一行 for (int i = 1; i < cols; ++i) { data[0][i] = data[0][i - 1] + grid[0][i]; } //處理data非第一行和第一列的元素 for (int i = 1; i < rows; ++i) { for (int j = 1; j < cols; ++j) { data[i][j] = min(data[i][j - 1], data[i - 1][j]) + grid[i][j]; } } //返回最短路徑值 return data[rows - 1][cols - 1]; }
int minPathSum(vector<vector<int>>& grid)
{
//當grid為空時,返回0
if (grid.empty())
return 0;
//獲得grid行數和列數
int rows = grid.size();
int cols = grid[0].size();
//儲存s[i-1]行中的元素
vector<int> pre(cols, grid[0][0]);
//儲存當前行的元素
vector<int> cur(cols);
//初始化pre
for (int i = 1; i < cols; ++i)
{
pre[i] = pre[i - 1] + grid[0][i];
}
//獲得s[i][j]
for (int i = 1; i < rows; ++i)
{
cur[0] = grid[i][0] + pre[0];
for (int j = 1; j < cols; ++j)
{
cur[j] = min(cur[j - 1], pre[j]) + grid[i][j];
}
swap(cur, pre);
}
return pre[cols - 1];
}
方法四(動態規劃):時間複雜度為o(n*m),空間複雜度為o(m),需要m大小的額外空間,注意此方法和方法三的區別,方法三需要2*m大小的額外空間,此方法只需要m大小的額外空間,在方法三中我們儲存當前行s[i]中的元素,假設我們當前計算s[i][j],我們只需要知道s[i][j-1]的值即可,不需要儲存s[i]行中的元素。每次計算s[i][j]時,我們需要更新pre[j]的值。int minPathSum(vector<vector<int>>& grid)
{
//當grid為空時,返回0
if (grid.empty())
return 0;
//獲得grid行數和列數
int rows = grid.size();
int cols = grid[0].size();
//儲存s[i-1]行中的元素
vector<int> pre(cols, grid[0][0]);
//儲存s[i][j-1]中的元素
int cur = grid[0][0];
//初始化pre
for (int i = 1; i < cols; ++i)
{
pre[i] = pre[i - 1] + grid[0][i];
}
//獲得s[i][j]的最小值
for (int i = 1; i < rows; ++i)
{
cur = grid[i][0] + pre[0];
pre[0] = cur;
for (int j = 1; j < cols; ++j)
{
cur = min(cur, pre[j]) + grid[i][j];
pre[j] = cur;
}
}
return pre[cols - 1];
}
方法五:上述四種方法都不要改變原矩陣中元素的值,當我們面試的時候,可以問一下面試官是否可以改變原矩陣中的元素,如果可以改變原矩陣中的值,我們可以再次提高一下空間效率,時間複雜度為o(n*m),空間複雜度為o(1)。每次計算s[i][j]時,我們把grid[i][j]更新為此值,因為再之後的訪問過程中不需要再次訪問grid[i][j]。int minPathSum(vector<vector<int>>& grid)
{
if (grid.empty())
return 0;
int rows = grid.size();
int cols = grid[0].size();
for (int i = 1; i < cols; ++i)
{
grid[0][i] = grid[0][i - 1] + grid[0][i];
}
for (int i = 1; i < rows; ++i)
{
grid[i][0] = grid[i - 1][0] + grid[i][0];
}
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
return grid[rows - 1][cols - 1];
}