0
点赞
收藏
分享

微信扫一扫

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】


 目录

​​一,题目描述​​

​​英文描述​​

​​中文描述​​

​​示例与说明​​

​​二,解题思路​​

​​图优化+DFS​​

​​图优化+并查集​​

​​三,AC代码​​

​​Java​​

​​图优化+DFS​​

​​图优化+并查集​​

​​四,解题过程​​

​​第一博​​

​​第二搏​​

​​第三搏​​

​​第四搏​​

一,题目描述

英文描述

On a 2D plane, we place n stones at some integer coordinate points. Each coordinate point may have at most one stone.

A stone can be removed if it shares either the same row or the same column as another stone that has not been removed.

Given an array stones of length n where stones[i] = [xi, yi] represents the location of the ith stone, return the largest possible number of stones that can be removed.

中文描述

n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。

如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。

给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。

示例与说明

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_中等

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_leetcode_02

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_leetcode_03

 


二,解题思路

参考官方题解​​@力扣官方题解【移除最多的同行或同列石头】​​

图优化+DFS

直接双重循环遍历两个石子是否为联通关系(同行/同列),显然时间代价是非常高的。

为了更快速的判断两点是否联通,可以借助hash表的力量。

  1. 采用hash表(key为行/列,value为石子编号)记录行/列中的石子编号。为了区分是行还是列,可以采用10001+列号表示新的列(数据规模是10000);
  2. LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_leetcode_04

  3. 遍历hash表,连接同一行/列中的点,即可获得联通关系;
  4. LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_并查集与DFS_05

  5. DFS获得连通分量数目;

整体来看,步骤1时间复杂度为O(N)。步骤2中,由于每个点最多只能与其他四个点有关联关系,所以看上去是双重循环,时间复杂度其实为O(N)。步骤3,DFS不重复的遍历图中每个点,时间复杂度为O(N)。

图优化+并查集

一个石子的行和列上所有的石子均为联通关系,所以可以将这个行和列看作是联通的。因此如果多个石子属于同一个联通集合时,他们的行和列也可以组成一个集合。不同集合的行和列绝对不会重合。

这样便可以将行/列看作独立的数字进行合并

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_leetcode_06

最后只需要查看石子所涉及到的行/列(即father不为-1)被分为几个集合,即可获得联通分量的个数

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_构建图_07

 

三,AC代码

Java

图优化+DFS

class Solution {
public int removeStones(int[][] stones) {
Map<Integer, List<Integer>> record = new HashMap<>();
List<List<Integer>> edge = new ArrayList<>();
for (int i = 0; i < stones.length; i++) {
edge.add(new ArrayList<>());
}
// 存储每行/列中的石头的编号(列用10001+列号表示,这样就可以在一个key为Integer的hash表中存储所有位置信息)
for (int i = 0; i < stones.length; i++) {
if (!record.containsKey(stones[i][0])) {
record.put(stones[i][0], new ArrayList<Integer>());
}
record.get(stones[i][0]).add(i);
if (!record.containsKey(stones[i][1] + 10001)) {
record.put(stones[i][1] + 10001, new ArrayList<Integer>());
}
record.get(stones[i][1] + 10001).add(i);
}
// 同行/列的石头构成联通关系
for (Map.Entry<Integer, List<Integer>> entry : record.entrySet()) {
List<Integer> list = entry.getValue();
for (int i = 1; i < list.size(); i++) {
edge.get(list.get(i - 1)).add(list.get(i));
edge.get(list.get(i)).add(list.get(i - 1));
}
}
boolean[] visited = new boolean[stones.length];
int num = 0;
for (int i = 0; i < stones.length; i++) {
if (visited[i] == false) {
dfs(edge, visited, i);
num++;
}
}
return stones.length - num;
}
void dfs (List<List<Integer>> edge, boolean[] visited, int i) {
visited[i] = true;
List<Integer> list = edge.get(i);
for (int x : list) {
if (visited[x] == false){
dfs(edge, visited, x);
}
}
}
}

图优化+并查集

class Solution {
int findFather(int[] father, int x) {
if (father[x] != x) {
father[x] = findFather(father, father[x]);
}
return father[x];
}
void unionSet(int[] father, int a, int b) {
int fa = findFather(father, a);
int fb = findFather(father, b);
if (fa != fb) {
father[fa] = father[fb];
}
}
public int removeStones(int[][] stones) {
int n = stones.length;
int[] father = new int[20005];// 保证访问不会越界
Arrays.fill(father, -1);
for (int i = 0; i < n; i++) {
// 行/列号存在,则将其父节点设置为行/列号
if (father[stones[i][0]] == -1) {
father[stones[i][0]] = stones[i][0];
}
if (father[stones[i][1] + 10001] == -1) {
father[stones[i][1] + 10001] = stones[i][1] + 10001;
}
unionSet(father, stones[i][0], stones[i][1] + 10001);// 将行和列合并
}
int num = 0;
for (int i = 0; i < father.length; i++) {
if (father[i] == i) num++;
}
return n - num;
}

}

四,解题过程

第一博

一开始没有注意到"最多"这个词的含义,很快就敲出来了,很快啊。

大致思路是每添加一个石子,就在对应的行和列加一。

然后再次遍历数组时,每当石子所在行和列记录不为1就将对应的行和列记录-1。

class Solution {
public:
int removeStones(vector<vector<int>>& stones) {
vector<vector<int>> record(stones.size(), vector<int>(2, 0));
int ans = 0;
for (int i = 0; i < stones.size(); i++) {
record[stones[i][0]][0]++;
record[stones[i][1]][1]++;
}
for (int i = stones.size() - 1; i >= 0; i--) {
if (record[stones[i][0]][0] != 1 || record[stones[i][1]][1] != 1) {
record[stones[i][0]][0]--;
record[stones[i][1]][1]--;
ans++;
}
}
return ans;
}
};

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_leetcode_08

先删除[1, 1],然后[0, 1],[1, 0]就不存在同行或同列的石子了。 而实际上,最多可以删除两个石子(比如先删除[0, 1],再删除[1, 1])

第二搏

第一博中很明显不能满足“最多删除”的条件。

经过观察可以发现处于同行或同列的石子可以看作是一个集合,通过合适的删除顺序,这个集合中最少可只剩一个石子。

因此只要知道这样的集合有多少个,就可以计算出最终答案。

很直观的想到了连通分量这个概念。关于联通分量的求解,可以借助DFS以及并查集来处理。

以往我们在求解联通分量时,题目中一般都会给出任意两点的联通方式(比如边的集合等),但这里只给出了点图,并没有直接给出联通关系。因此就需要自己动手了!

最简单粗暴的形式就是双重循环遍历stones数组,若两个石子间存在关联(行数或列数相同),则将其连接起来,这里采用邻接表的形式来存储(比如石子1与[2, 5, 8]相关联,则edge[1] = [2, 5, 8])

class Solution {
public int removeStones(int[][] stones) {
List<List<Integer>> edge = new ArrayList<>();// 存储点之间的邻接关系
for (int i = 0; i < stones.length; i++) {
edge.add(new ArrayList<>());
}
// 创建图的邻接表形式(一个点和哪些点相关联)
for (int i = 0; i < stones.length; i++) {
for (int j = i + 1; j < stones.length; j++) {
if (stones[i][0] == stones[j][0] || stones[i][1] == stones[j][1]) {
edge.get(i).add(j);
edge.get(j).add(i);// !!!必须为无向图,否则节点的访问顺序会影响最终计算出的连通分量个数
}
}
}
boolean[] visited = new boolean[stones.length];
Arrays.fill(visited, false);
int num = 0;// 记录联通分量个数
for (int i = 0; i < visited.length; i++) {
if (visited[i] == false) {
dfs(edge, visited, i);
num++;
}
}
return stones.length - num;
}
void dfs (List<List<Integer>> edge, boolean[] visited, int i) {
visited[i] = true;
List<Integer> list = edge.get(i);
for (int x : list) {
if (visited[x] == false){
dfs(edge, visited, x);
}
}
}
}

双重循环,时间O(N^2)。邻接表形式,空间最坏O(N^2)

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_并查集与DFS_09

第三搏

第二搏中暴力双重循环判断两个点是否联通。时间复杂度为O(N^2),效率较低。

有没有更快捷的判断两点是否联通的方法?

  1. 采用hash表记录行/列中的石子编号。为了区分是行还是列,可以采用10001+列号表示列(数据规模是10000)
  2. 遍历hash表,连接同一行/列中的点,即可获得联通关系。
  3. DFS获得连通分量数目;

整体来看,步骤1时间复杂度为O(N)。步骤2中,由于每个点最多只能与其他四个点有关联关系,所以看上去是双重循环,时间复杂度其实为O(N)。步骤3,DFS不重复的遍历图中每个点,时间复杂度为O(N)。

class Solution {
public int removeStones(int[][] stones) {
Map<Integer, List<Integer>> record = new HashMap<>();
List<List<Integer>> edge = new ArrayList<>();
for (int i = 0; i < stones.length; i++) {
edge.add(new ArrayList<>());
}
// 存储每行/列中的石头的编号(列用10001+列号表示,这样就可以在一个key为Integer的hash表中存储所有位置信息)
for (int i = 0; i < stones.length; i++) {
if (!record.containsKey(stones[i][0])) {
record.put(stones[i][0], new ArrayList<Integer>());
}
record.get(stones[i][0]).add(i);
if (!record.containsKey(stones[i][1] + 10001)) {
record.put(stones[i][1] + 10001, new ArrayList<Integer>());
}
record.get(stones[i][1] + 10001).add(i);
}
// 同行/列的石头构成联通关系
for (Map.Entry<Integer, List<Integer>> entry : record.entrySet()) {
List<Integer> list = entry.getValue();
for (int i = 1; i < list.size(); i++) {
edge.get(list.get(i - 1)).add(list.get(i));
edge.get(list.get(i)).add(list.get(i - 1));
}
}
boolean[] visited = new boolean[stones.length];
int num = 0;
for (int i = 0; i < stones.length; i++) {
if (visited[i] == false) {
dfs(edge, visited, i);
num++;
}
}
return stones.length - num;
}
void dfs (List<List<Integer>> edge, boolean[] visited, int i) {
visited[i] = true;
List<Integer> list = edge.get(i);
for (int x : list) {
if (visited[x] == false){
dfs(edge, visited, x);
}
}
}
}

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_并查集与DFS_10

经过多次测试,时间确实优于第二搏中的方法。

第四搏

按照官方给出的题解,转换一种思路。

一个石子的行和列上所有的石子均为联通关系,所以可以将这个行和列看作是联通的。因此如果多个石子属于同一个联通集合时,他们的行和列也可以组成一个集合。不同集合的行和列绝对不会重合。

并查集可以完美解决这个问题

LeetCode_UnionFind_947. Most Stones Removed with Same Row or Column 移除最多的同行或同列石头【连通分量,构建图】【java】【中等】_中等_11

 

举报

相关推荐

0 条评论