由于考试报告是空白,只分享下 T 2 T2 T2的解题思路吧。虽然叫C站,这次题目直接以python列表的形式输入和输出,输入C选手勉强能忍,进行下字符串分隔进行,至于输出,就让人完全没有使用C作答的想法了,而且一级列表不够还给了二级列表,可见,选题的负责人大概率是不懂编程的。按照标准的数组输入输出,不管是C还是python都可以很快处理,偏偏选择了这种python风格的输入输出。
这次 T 2 T2 T2题目还行,出自Leetcode,看不见考试报告,就直接用leetcode的原题来分析下吧。
给你一个
m x n
的矩阵matrix
,请你返回一个新的矩阵answer
,其中answer[row][col]
是matrix[row][col]
的秩。每个元素的 秩 是一个整数,表示这个元素相对于其他元素的大小关系,它按照如下规则计算:
- 秩是从 1 开始的一个整数。
- 如果两个元素 p和 q 在同一行或者同一列,那么:
- 如果
p < q
,那么rank(p) < rank(q)
- 如果
p == q
,那么rank(p) == rank(q)
- 如果
p > q
,那么rank(p) > rank(q)
- 秩 需要越 小 越好。
题目保证按照上面规则
answer
数组是唯一的。
题目出自leetcode 1632. 矩阵转换后的秩。
题目意思很清晰,首先不考虑重复元素的情况。如果是一维数组,那么我们只需要对数组进行排序,每个元素的秩 k k k表示这个元素在数组中是第 k k k小的。排序过程中我们无意中维护了一组偏序关系,即有序的数组里前面元素都不大于后面的元素。对于矩阵而言,我们是否能够对元素所在行和所在列的元素分别进行排序后,将每个元素的秩设置为其行秩和列秩中的较大者呢?
比如
4 1
3 2
以上面的矩阵为例,元素4在行序列中的秩为2,在列序列中的秩也是2,如果我们设置 r a n k ( 4 ) = 2 rank(4)=2 rank(4)=2,结果就不对了。矩阵中最小的元素是1,秩为1,2和1在同一列,秩为2,3和2在同一行,秩为3,4和3在同一列,秩为4,显然不是前面设置的2。之所以不能简单的取行秩和列秩的较大者,是因为偏序关系具有传递性。
为了维护矩阵整体的偏序关系,我们可以建个图来维护,比如第一行可以建立1到4的有向边,那么最后就可以将矩阵转换为一个有向图。
1节点的入度为0,表示同一行同一列没有比它小的元素,我们就可以使用拓扑排序来求出每个元素的秩,首先 r a n k ( 1 ) = 1 rank(1)=1 rank(1)=1,去掉1指向的边,之后2号节点的入度变成了0,其秩为1的秩加上1,即 r a n k ( 2 ) = 2 rank(2)=2 rank(2)=2,再消去2指向的边,3入度变成0,得到 r a n k ( 3 ) = 3 rank(3)=3 rank(3)=3,最后得到 r a n k ( 4 ) = 4 rank(4)=4 rank(4)=4。
显然在同一行(列)没有相等元素的情况下,我们只需要将矩阵转换为有向图,再做次拓扑排序就可以解决本题了,但是加上存在相等元素且同一行(列)相等元素的秩相同的条件后,问题就稍微复杂了。由于相等元素的秩相等,所以同行(列)相等元素在矩阵转换为图的时候可以视为一个节点,这就要用到图论里面常见的缩点技巧了,将同一行、同一列的相等元素缩小为一个节点,再建立图,就可以得到一个DAG,做下拓扑排序就可以求解了。
举个例子,第一行第一列元素可能与第一行第三列元素相等,第一行第三列元素又与第三行第三列相等,这些点都需要缩为一个节点,所以可以选择其中的一个点作为标志,这就很符合并查集的语义了,选择一个节点作为集合的根节点,这个节点代表着这个集合。
所以本题的求解思路就是:
这题的难度不仅在于求解思路,还在于编码难度,比较考验编码功底。下面说下几个编码的技巧:
节点的编号:由于只有同一行(列)相等的元素才能缩为一个节点,所以存在缩点后DAG中不同节点的值相等的情况,因此不能使用元素的值作为节点的编号,直接使用矩阵的行号和列号作为节点的编号也不合适,可以将二维矩阵映射为一维数组,将映射后的下标作为有向图节点的编号。
如何将同一行(列)元素加入相同的集合,并且构造偏序关系的有向图呢?由于我们使用下标作为编号,所以需要对值和下标做个逆映射,同一行可能存在相等的元素,一些人会想到将值映射为一个vector,这就加大了编码的难度了。其实我们在遍历一行元素时,只需要记录每个元素第一次出现的位置,当遍历到某个元素之前出现过,就可以将这个元素的编号加入到之前出现过元素的集合里了。
做好逆映射和维护好并查集后,就可以开始建立有向图了,遍历到一行(列),就可以对其进行排序,遍历下有序序列,如果某个元素不等于上一个元素,就将建立一条上个元素到该元素的有向边。最后进行拓扑排序,在拓扑排序过程中顺带求解下矩阵的秩。
最后要注意下:尽管建立并查集和建立有向图的操作都需要逐行逐列遍历元素,但是我们不能将两个操作合并到一个迭代里,比如给一行元素建立并查集后立刻建立偏序关系。这是因为我们需要将矩阵中要缩的点都缩完才能进行建图,否则的话开始节点A在一个有向图里,后面遇见节点B在另一个有向图里,发现这两个节点在一个集合里,再进行合并就很困难了。
详细解法可以参考下面代码,思路还是比较清晰的。
class Solution {
public:
const static int N = 250005;
int n, m;
int idx,h[N],e[N],ne[N];
int fa[N];
int ind[N];
queue<int> q;
vector<int> res;
unordered_map<int,int> m_row[505];
unordered_map<int,int> m_col[505];
int to(int x,int y) {
return x * m + y;
}
int find(int x) {
if (fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
void add(int a,int b) {
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
vector<vector<int>> matrixRankTransform(vector<vector<int>>& matrix) {
n = matrix.size();
m = matrix[0].size();
for (int i = 0; i < n * m;i++) {
fa[i] = i;
}
memset(h,-1,sizeof h);
//逐行缩点
for(int i = 0;i < n;i++) {
vector<int> t = matrix[i];
for (int j = 0;j < m;j++) {
int v = to(i, j);
if(m_row[i].count(t[j])) {
int a = find(v), b = find(m_row[i][t[j]]);
fa[a] = b;
} else {
m_row[i][t[j]] = v;
}
}
}
//逐列缩点
for (int i = 0; i < m;i++) {
for(int j = 0;j < n;j++) {
int v = to(j, i);
auto &t = matrix[j][i];
if (m_col[i].count(t)) {
int a = find(v), b = find(m_col[i][t]);
fa[a] = b;
} else {
m_col[i][t] = v;
}
}
}
//逐行添加偏序关系
for(int i = 0;i < n;i++) {
vector<int> t = matrix[i];
sort(t.begin(), t.end());
for (int j = 1;j < m;j++) {
if (t[j] == t[j - 1]) continue;
int a = find(m_row[i][t[j-1]]), b = find(m_row[i][t[j]]);
add(a, b);
ind[b]++;
}
}
//逐列添加偏序关系
for (int i = 0; i < m;i++) {
vector<int> t;
for (int j = 0;j < n;j++) {
t.push_back(matrix[j][i]);
}
sort(t.begin(), t.end());
for(int j = 1;j < n;j++) {
if (t[j] == t[j - 1]) continue;
int a = find(m_col[i][t[j-1]]), b = find(m_col[i][t[j]]);
add(a, b);
ind[b]++;
}
}
res.resize(N);
for (int i = 0;i < n * m; i++) {
fa[i] = find(i);
if(fa[i] == i && !ind[i]) {
q.push(i);
res[i] = 1;
}
}
while(q.size()) {
int u = q.front();
q.pop();
for (int i = h[u];~i;i=ne[i]) {
int j = e[i];
ind[j]--;
if (!ind[j]) {
res[j] = res[u] + 1;
q.push(j);
}
}
}
vector<vector<int> > rank;
for (int i = 0;i < n;i++) {
vector<int> t;
for(int j = 0;j < m;j++) {
int v = to(i, j);
t.push_back(res[fa[v]]);
}
rank.push_back(t);
}
return rank;
}
};