第212场周赛。
1631. 最小体力消耗路径(二分+搜索)
很典型的题目,题目最后要求返回的答案是一个最大值,且我们很容易发现任何小于这个最大值的数都是无法被满足的,任何大于这个最大值的数都是可以被满足的。且判断在给定数值时,能否满足这个事情是比较容易的(BFS或者DFS)。因此我们应该想到用二分+搜索的方法。
另外一个思路是采用并查集,我们构建起来相邻的边,并且排序。从小到大进行连线,如同构造一个最小生成树,但是这里判断的条件是是否原点和最后的终点联通。
class Solution {
//方法一 二分+BFS
public int minimumEffortPath(int[][] heights) {
int max = 0;
int min = 10000000;
int n = heights.length;
int m = heights[0].length;
if (m == 1 && n == 1) return 0;
for (int i = 0; i<n;i++){
for (int j = 0; j<m;j++){
min = Math.min(min, heights[i][j]);
max = Math.max(max, heights[i][j]);
}
}
int l = 0;
int r = max-min;
while (l<=r){
int mid = (l+r)/2;
if (check(heights, mid)){
r = mid-1;
}
else l = mid+1;
}
return l;
}
public boolean check(int[][] heights, int min){
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{0,0});
int n = heights.length;
int m = heights[0].length;
int[][] v = new int[n][m];
v[0][0] = 1;
int[][] dis = new int[][]{{0,1}, {1,0}, {-1,0}, {0,-1}};
int[][] dis = {{0,1}, {1,0}, {-1,0}, {0,-1}};
while (!queue.isEmpty()){
int[] cur = queue.poll();
if (cur[0] == n-1 && cur[1] == m-1) return true;
for (int[] d:dis){
int newx = cur[0]+d[0];
int newy = cur[1]+d[1];
if (0<=newx && newx<n && 0<=newy && newy<m && v[newx][newy] == 0 && Math.abs(heights[newx][newy]-heights[cur[0]][cur[1]])<=min){
v[newx][newy] = 1;
queue.offer(new int[]{newx, newy});
}
}
}
return false;
}
======================================================================================
// 方法二:并查集
public int minimumEffortPath(int[][] heights) {
int n = heights.length;
int m = heights[0].length;
UF uf = new UF(n*m);
List<Edge> edges = new ArrayList<>();
for (int i = 0; i<n;i++){
for (int j = 0; j<m;j++){
if (i+1<n) {
edges.add(new Edge(i*m+j, (i+1)*m+j, Math.abs(heights[i][j]-heights[i+1][j])));
}
if (j+1<m) edges.add(new Edge(i*m+j, i*m+j+1, Math.abs(heights[i][j]-heights[i][j+1])));
}
}
int ans = 0;
Collections.sort(edges, (a, b)->a.value-b.value);
for (Edge e:edges){
int a = e.v;
int b = e.w;
uf.union(a,b);
ans = Math.max(ans, e.value);
if (uf.isUnion(0, n*m-1)) break;
}
return ans;
}
class Edge{
int v;
int w;
int value;
Edge(int v, int w, int value) {
this.v = v;
this.w = w;
this.value = value;
}
}
class UF{
int[] fa;
int[] sz;
public UF(int n){
fa = new int[n];
for (int i = 0; i<n;i++)fa[i] = i;
sz = new int[n];
for (int i = 0; i<n; i++)sz[i] = 1;
}
public int find(int x){
if(fa[x] == x)return fa[x];
fa[x] = find(fa[x]);
return fa[x];
}
public boolean isUnion(int i, int j){
return find(i) == find(j);
}
public void union(int x, int y){
int xfa = find(x);
int yfa = find(y);
if (xfa == yfa) return;
if(sz[xfa]>=sz[yfa]){
sz[xfa] += sz[yfa];
fa[yfa] = xfa;
}else{
sz[yfa] += sz[xfa];
fa[xfa] = yfa;
}
}
}
}
1632. 矩阵转换后的秩(求秩问题,并查集的使用)
首先我们先来简化问题,如果不考虑需要让数值相等的点的秩相同这个条件。我们如何求解这个问题。
方法是我们 首先对矩阵的元素排序,得到从小到大的队列。然后我们维护一个每一行和每一列的最大的位置,xmax
表示这x一行中最大的数字在哪一列,ymax
表示这y一列中最大的数字在哪一行。这样我们每次加入新的数字只需要满足val[cur] = max(ymax(y), xmax(x))+1
,然后修改xmax(x) = y, ymax(y) = x
。
但是题目特别要求的了同一行和同列的数字相同的位置的秩相同。这样我们就需要对这些相同位置的点进行合并处理了,统一作为一个整体,每次修改都是统一的。这里我们用到了并查集。
对于二维数组的问题,一个很重要的思想就是要把二维数组铺平,变成一维数组。
class Solution {
public int[][] matrixRankTransform(int[][] matrix) {
int n = matrix.length;
int m = matrix[0].length;
int sum = n*m;
UF uf = new UF(sum);
int[] xmax = new int[n]; // 维护最大的行位置
int[] ymax = new int[m]; // 维护最大的列位置
Arrays.fill(xmax, -1);
Arrays.fill(ymax, -1);
// !!!!==========!!!
//这里必须是Integer,因为Comparator接口要求是一个类
Integer[] index = new Integer[sum];
int[] val = new int[sum]; // 存储每次修改之后的秩
int[][] ans = new int[n][m];
for (int i = 0; i<n;i++){
for (int j = 0; j<m;j++){
index[i*m+j] = i*m+j;
}
}
Arrays.sort(index, (Integer i, Integer j) -> matrix[i/m][i%m]-matrix[j/m][j%m]);
/*
Arrays.sort(index, new Comparator<Integer>(){
// 注意拼写,Comparator 需要重写的方法是compare 并且要求了类,因此必须Integer
//@override
public int compare(Integer i, Integer j){
return matrix[i/m][i%m]-matrix[j/m][j%m];
}
});
*/
for (int i = 0; i<sum; i++){
int cur = index[i];
int x = cur/m;
int y = cur%m;
int valcur = 1;
if (xmax[x]!=-1){
int k = xmax[x]; // 当前行的最大列
int maxindex = x*m+k; // 坐标转换
if (matrix[x][y] == matrix[x][k]){
// 这里一定要是根节点作为索引。
valcur = val[uf.find(maxindex)]; // 坐标相等,这里需要与根节点一样
uf.union(cur, maxindex); // 链接
}
else {
valcur = val[uf.find(maxindex)]+1;
}
}
if (ymax[y]!=-1){
int k = ymax[y];
int maxindex = k*m+y;
if (matrix[x][y] == matrix[k][y]){
valcur = Math.max(val[uf.find(maxindex)], valcur); // 进行比较
uf.union(cur, maxindex);
}
else {
valcur = Math.max(val[uf.find(maxindex)]+1, valcur);
}
}
// 更新最大行和最大列的信息,并且更新当前节点的根节点的数值。
xmax[x] = y;
ymax[y] = x;
val[uf.find(cur)] = valcur;
}
for (int i = 0; i<n;i++){
for (int j = 0; j<m; j++){
int cur = i*m+j;
ans[i][j] = val[uf.find(cur)];
}
}
return ans;
}
class UF{
int[] fa;
int[] sz;
public UF(int n){
fa = new int[n];
for (int i = 0; i<n;i++)fa[i] = i;
sz = new int[n];
for (int i = 0; i<n; i++)sz[i] = 1;
}
public int find(int x){
if(fa[x] == x)return fa[x];
fa[x] = find(fa[x]);
return fa[x];
}
public boolean isUnion(int i, int j){
return find(i) == find(j);
}
public void union(int x, int y){
int xfa = find(x);
int yfa = find(y);
if (xfa == yfa) return;
if(sz[xfa]>=sz[yfa]){
sz[xfa] += sz[yfa];
fa[yfa] = xfa;
}else{
sz[yfa] += sz[xfa];
fa[xfa] = yfa;
}
}
}
}