算法课外思考题八


问题一

问题描述

给出策略打印旋转矩阵

问题分析

$\left[\begin{array}{cccc}a_{11} & a_{12} & \cdots & a_{1 n} \ a_{21} & a_{22} & \cdots & a_{2 n} \ a_{31} & a_{32} & \cdots & a_{3 n} \ \cdots & \cdots & & \cdots \ a_{m 1} & a_{m 2} & \cdots & a_{m n}\end{array}\right]$

对于上述矩阵,假定采用顺时针进行旋转打印

对打印顺序进行分析,顺时针旋转一圈,打印的是在外面的一层元素

因此对矩阵进行分层,从外向内划分依次得到以下分层:

$[1,1,1,1,1,1,1]$
$[1,2,2,2,2,2,1]$
$[1,2,3,3,3,2,1]$
$[1,2,2,2,2,2,1]$
$[1,1,1,1,1,1,1]$

采用左上$(top,left)$、左下$(bottom,left)$、右上$(top,right)$、右下$(top,right)$四个点定位各层的位置,将打印层分为上下左右四个片区分别进行打印,遍历完当前层的元素后left++、top++、right–、bottom–,进入下一层开始遍历,最终得到整个旋转矩阵。

算法实现

#include<iostream>
#include<vector>
using namespace std;

void PrintMatrix(vector<vector<int>>& matrix) {
	int r = matrix.size();
	int c = matrix[0].size();
	int top = 0, bottom = r - 1, left = 0, right = c - 1;
	while (left <= right && top <= bottom) {
		for (int i = left; i <= right; i++) {
			cout << matrix[top][i] << " ";
		}
		for (int i = top + 1; i <= bottom; i++) {
			cout << matrix[i][right] << " ";
		}
		if (left < right && top < bottom) {
			for (int i = right - 1; i > left; i--) {
				cout << matrix[bottom][i] << " ";
			}
			for (int i = bottom; i > top; i--) {
				cout << matrix[i][left] << " ";
			}
		}
		top++;
		left++;
		right--;
		bottom--;
	}
	cout << endl;
}

int main() {
	vector<vector<int>> matrix = { {1,2,3},{4,5,6},{7,8,9} };
	vector<vector<int>> matrix1 = { {1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16} };
	PrintMatrix(matrix);
	PrintMatrix(matrix1);
}

在这个问题中,每一元素都要被遍历一次,因此算法的时间复杂度是$O(r*c)$,r为矩阵行数,c为矩阵列数,本算法并不需要开辟额外的空间,故空间复杂度为$O(1)$。

附加思考

顺时针旋转矩阵$90°$

vector<vector<int>> RotateMatrix(vector<vector<int>>& matrix) {
	vector<vector<int> > rotate;
	vector<int> temp;
	int r = matrix.size(), c = matrix[0].size();
	for (int j = 0; j < c; j++) {
		for (int i = r - 1; i >= 0; i--) {
			temp.push_back(matrix[i][j]);
		}
		rotate.push_back(temp);
		temp.clear();
	}
	return rotate;
}

“之”字形打印矩阵

使用两个指针分别从00位置开始,一个指针沿右下的路线抵达矩阵右下角元素,另一个沿下右的路线抵达矩阵右下角,两指针确定的直线上再根据奇偶性判断打印方向

void ZhiPrint(vector<vector<int>>& matrix) {
	int r = matrix.size();
	int c = matrix[0].size();
	int ar = 0, ac = 0;
	int br = 0, bc = 0;
	int n;
	do {
		n = ar + ac;
		if ( n % 2 == 0) {
			for (int i = ar; i <= br; i++) {
				cout << matrix[i][n - i] << " ";
			}
		}
		else {
			for (int i = br; i >= ar; i--) {
				cout << matrix[i][n - i] << " ";
			}
		}
		if (br < r) {
			br++;
		}
		else {
			bc++;
		}
		if (ac < c) {
			ac++;
		}
		else {
			ar++;
		}
	} while (ar != br && ac != bc);
	cout << matrix[r - 1][c - 1] << endl;
}

问题二

问题描述

给出策略返回一个m*n矩阵中从[1,1]到[m,n]的和最小的一条路径。

问题分析

已知在某一点时想要确定路径下一个元素的位置,路径方向只能是向下/向右,因此网格的第一行/第一列上的元素下一位置只能是该元素的右边/左边,然后确定剩下路径

对于每一待定元素可由其左边/上边的元素到达,比较两数的大小,确定更小的那一个,然后依次得到不同路径,直至抵达[m,n],再通过回溯得到最小路径。

  • 当 $i>0$ 且 $j=0$ 时, $d p[i][0]=d p[i-1][0]+\operatorname{grid}[i][0]$
  • 当 $i=0$ 且 $j>0$ 时, $d p[0][j]=d p[0][j-1]+\operatorname{grid}[0][j]$
  • 当 $i>0$ 且 $j>0$ 时, $d p[i][j]=\min (d p[i-1][j], d p[i][j-1])+\operatorname{grid}[i][j]$

算法实现

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

int MinPath(vector<vector<int>>& matrix) {
	int r = matrix.size(), c = matrix.size();
	auto dp = vector<vector<int> >(r, vector<int>(c));
	dp[0][0] = matrix[0][0];
	for (int i = 1; i < r; i++) {
		dp[i][0] = dp[i - 1][0] + matrix[i][0];
	}
	for (int i = 1; i < c; i++) {
		dp[0][i] = dp[0][i - 1] + matrix[0][i];
	}
	for (int i = 1; i < r; i++) {
		for (int j = 1; j < c; j++) {
			dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + matrix[i][j];
		}
	}
	return dp[r - 1][c - 1];
}

int main() {
	vector<vector<int> > matrix = { {1,3,1},{1,5,1},{4,2,1} };
	cout << MinPath(matrix) << endl;
}

需要遍历整个数组,时间复杂度是$O(mn)$

重新开辟了一个大小相同的向量空间,空间复杂度是$O(mn)$,在空间方面可以进行优化,直接在原矩阵上进行dp操作,不使用额外空间,改进到$O(1)$


文章作者: J&Ocean
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 J&Ocean !
评论
  目录