leetcode 947. Most Stones Removed with Same Row or Column(去掉同行列最多的石头)

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.

Example 1:

Input: stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
Output: 5
Explanation: One way to remove 5 stones is as follows:

  1. Remove stone [2,2] because it shares the same row as [2,1].
  2. Remove stone [2,1] because it shares the same column as [0,1].
  3. Remove stone [1,2] because it shares the same row as [1,0].
  4. Remove stone [1,0] because it shares the same column as [0,0].
  5. Remove stone [0,1] because it shares the same row as [0,0].
    Stone [0,0] cannot be removed since it does not share a row/column with another stone still on the plane.

Constraints:

1 <= stones.length <= 1000
0 <= xi, yi <= 104
No two stones are at the same coordinate point.

给一个2D数组,其中的元素代表2D的位置,同一位置只会有一个石头,同行或同列的石头能被移走(前提是存在与它同行或同列的石头,如果只有它自己就不能移走),问最多可移走多少个石头。

思路:
问题转换:同行同列的石头阵可以被移走直到只剩下一个石头。那么把同行同列的石头全都归到一个group,这个group的个数就是最后剩下的石头数量。则被移走的石头数量=石头总数量 - group个数

任务就变成了把同行同列的石头归到一个group里,两种方法,DFS和Union-Find

1) DFS
可把 (row, col) 看作一条边,建立无向图
但是注意一个问题,就是(1,2) 和 (2,1)是两个位置,但会被认为是同一条边,这时需要把这两种边区分开,题中有这样一个限制条件0 <= xi, yi <= 104 ,所以可定义0~10000是row的区间,10001~20001是col的范围。也就是把col + 10001,但是实际上col+10000也通过了。

这样做就可以访问一个位置(row,col)时,把row行和col列的位置全都标记为访问过,再递归标记它们关联的行和列,为一个组。下一次再从未访问过的位置重新标记新的一组。这样就可找出一共有多少个组。

可能行和列的概念容易混淆,可以认为列也是要遍历的“行”,只不过列的index是从10000开始的。

这里标记访问过不是一个位置一个位置地标,而是标整行整列,表示这一行或列已经访问过(可把列理解为index从10000开始的“行”)。

//19ms
    public int removeStones(int[][] stones) {
        int groups = 0;
        HashMap<Integer, List<Integer>> graph = new HashMap<>();
        boolean[] visited = new boolean[20001];
        
        for(int[] stone : stones) {
            int row = stone[0];
            int col = stone[1] + 10000;
            if(!graph.containsKey(row)) {
                graph.put(row, new ArrayList<Integer>());
            }
            graph.get(row).add(col);
            
            if(!graph.containsKey(col)) {
                graph.put(col, new ArrayList<Integer>());
            }
            graph.get(col).add(row);
        }
        
        for(Integer key : graph.keySet()) {
            if(visited[key]) continue;
            dfs(key, graph, visited);
            groups ++;
        }
        return stones.length - groups;
    }
    
    void dfs(int rowCol, HashMap<Integer, List<Integer>> graph, boolean[] visited) {
        if(visited[rowCol]) return;
        visited[rowCol] = true;
        //行的话找同一列,列的话找同一行的stone,把同行或同列的标记为访问,为同一group
        for(Integer next : graph.get(rowCol)) {
            if(visited[next]) continue;
            dfs(next, graph, visited);
        }
    }

如果DFS深度过深,担心StackOverflowError,可用stack版DFS,和BFS差不多,只不过BFS用的queue储存节点,先进先访问。DFS用stack储存节点,后进的先访问,直到到达尽头。

    public int removeStones(int[][] stones) {
        int groups = 0;
        HashMap<Integer, List<Integer>> graph = new HashMap<>();
        boolean[] visited = new boolean[20001];
        
        for(int[] stone : stones) {
            int row = stone[0];
            int col = stone[1] + 10000;
            if(!graph.containsKey(row)) {
                graph.put(row, new ArrayList<Integer>());
            }
            graph.get(row).add(col);
            
            if(!graph.containsKey(col)) {
                graph.put(col, new ArrayList<Integer>());
            }
            graph.get(col).add(row);
        }
        
        for(Integer key : graph.keySet()) {
            if(visited[key]) continue;
            Stack<Integer> stack = new Stack<>();
            stack.push(key);
            visited[key] = true;
            while(!stack.isEmpty()) {
                int rowCol = stack.pop();
                for(Integer next : graph.get(rowCol)) {
                    if(visited[next]) continue;
                    visited[next] = true;
                    stack.push(next);
                }
            }
            groups ++;
        }
        return stones.length - groups;
    }

2)Union-Find
思路和上面一样,也是把同行同列的归为一个group,最后用石头总数-group个数。

还是把(row, col)看成一条边,也可理解为它们是关联的数字。还是row是0~10000区间,col是10001~20001区间。但是这里加了一个情况,就是一个点还没有被访问的时候,它的parent是0,访问过的点要么parent是它自己,要么是其他点。所以为了和0区别开,row的范围移到1~10001,col移到10002~20002。
所以row要加1,col要加10002。

parent:上面还有其他parent。
root:parent是它自己(最上层parent)。

统一把col的root设为row的root。这样做有什么用处?row和col有同一个root,当同一列的位置来的时候,可通过col找到这个root,当同一行的位置来的时候,可通过row找到同一root,这样就可达到把同行和同列的石头都归为一个group的效果。

当row和col未访问过,即parent==0时,把row的parent标记为它自己,col的parent标记为row。一个访问过一个没访问过时,标记root为访问过的root。
当有不同的root时,把col的root,重点强调是root,而不是简单的parent,标记为row的root

//1ms~7ms
class Solution {
    int group = 0;
    public int removeStones(int[][] stones) {
        int[] root = new int[20003];
        for(int[] stone : stones) {
            union(root, stone[0] + 1, stone[1] + 10002);
        }
        return stones.length - group;
    }
    
    void union(int[] root, int row, int col) {
        int rootR = find(root, row);
        int rootC = find(root, col);
        
        //已经是一组(不需要处理),或者都没被访问过
        if(rootR == rootC) {
            if(rootR == 0) {  //都没被访问过,新增group
                group ++;
                root[row] = row;
                root[col] = row;
            }
        } else {  //一个没被访问过(设定root,group不变),或者行列不在一组(改root为一组,减去一个group)
            if(rootR == 0) {  //row没被访问过
                root[row] = rootC;
            } else if(rootC == 0) {
                root[col] = rootR;
            } else {
                root[rootC] = rootR; //从源头上改root,不要只改最下面节点
                group --;
            }
        }
    }
    
    int find(int[] root, int node) {
        int p = node;
        while(p != root[p]) {
            p = root[p];
            root[p] = root[root[p]];
        }
        return p;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值