LeetCode——419.甲板上的战舰

本文介绍了一种通过遍历二维数组来计算战舰数量的方法。该方法通过寻找每个战舰的根节点并去除重复计数,实现了准确计数。文章还提供了一种优化方案,减少对象创建并进一步简化算法。

通过万岁!!!

  • 题目:这个题目的意思就是,给你一个甲板(char数组表示),上面放了战舰。战舰的形状是1k的,或者k1,也就是一个战舰只能栈一行k列,或者k行一列。然后让输出有多少个战舰。其中战舰与战舰之间是有空相隔的,就是两个战舰,不管是什么形状。不可能左右旁边格子内还有战舰。
  • 思路:把这个板子理解成一个图,其一次就表示,有多少个子图,这里其实称为子图也可以,称为树也可以,因为每一个都是1k或者k1的。那么问题就相对简单了;首先定义一个point的数组(与甲板一样大);然后遍历这个图(甲板),如果这个战舰(自己是"X")的左边和上边都是"."那么就表示这个是根节点,我们就记录这个点的横纵坐标到point数组中。如果上面或者左边有值,那么就用上面或者左边的值来填到这里面。上面的意思就是找到每一个"X"的根节点并记录。然后再遍历一下point,去掉重复的就可以了。
  • 技巧:
    • 就是找图的子图,这里用到方法是记录每个节点的根节点(使用Point数组)。然后统计不一样根节点的个数(使用HashSet)。
    • 这里我们需要用到一个point的类,java中是有的,但是LeetCode中却没办法用,那就自己写一个内部类。
    • 自己写了这个内部类以后,因为我们需要用到HashSet,也就是说,我们需要用到这个类对象的hashCode,因此还需要重写这个类的hashCode和equals方法。
    • 至于什么要重写hashCode方法,主要是我们需要用到hashCode,而不重写的话,一样的对象的hashCode可能也是不同的。换句话说,hashSet集合判断两个对象是不是相同,首先调用hashCode,返回的是一个16进制的东西称为哈希值。然后如果两个哈希值不相同,则认为两个对象一定不同。而如果不重写hashCode方法,则会出现一样的对象不一样的哈希值的情况。
    • 那么为什么还需要equals方法,其实equals方法才是真正判断两个对象是不是相同的,但是直接调用的话,每次都需要比较而当里面的元素较多的时候,就比较耗时了。因为我们可以先用hashCode进行一个初步判断,也就是哈希值不相同,则认为两个对象一定不同。但是有可能两个对象明明不认同,但是输出的hashCode确实相同的。这叫哈希冲突,这时候再调用equals方法去判断是不是真的相同。

伪代码

先定义内部类MyPoint,里面有两个参数x,y分别表示这个点的更纵坐标,并且写一个具有着两个参数的构造方法,然后重写hashCode和equals方法。这里重写我们直接使用idea等ide的工具,直接生成就可以。
然后定义MyPoint数组root,大小与甲板大小一致
初始化0,0位置的root
然后初始化第一行和第一列,因为这两个是没有上边和左边的元素的。
然后从1开始遍历,两层for循环
    如果这个位置是"X",那么说明需要有根节点的
        如果这个位置左边的root不是空,那么这个节点的根节点与左边的相同。
        如果这个位置上边的root不是空,那么这个节点的根节点与上边的相同。
    否则这个节点自己是根节点,那么new一个新的MyPioint对象,传入当前坐标即可
最后for遍历root,如果不是空,就放到set中,这只为了去重
return set的大小即可。

java代码

class Solution {
    public int countBattleships(char[][] board) {
        class MyPoint {
            int x;
            int y;

            public MyPoint(int x, int y) {
                this.x = x;
                this.y = y;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                MyPoint myPoint = (MyPoint) o;
                return x == myPoint.x && y == myPoint.y;
            }

            @Override
            public int hashCode() {
                return Objects.hash(x, y);
            }
        }

        int m = board.length;
        int n = board[0].length;
        MyPoint root[][] = new MyPoint[m][n];

        if (board[0][0] == 'X') {
            root[0][0] = new MyPoint(0, 0);
        }
        for (int i = 1; i < m; i++) {// 第0列
            if (board[i][0] == 'X') {
                if (root[i - 1][0] == null) {
                    root[i][0] = new MyPoint(i, 0);
                } else {
                    root[i][0] = root[i - 1][0];
                }
            }
        }
        for (int i = 1; i < n; i++) {// 第0行
            if (board[0][i] == 'X') {
                if (root[0][i - 1] == null) {
                    root[0][i] = new MyPoint(0, i);
                } else {
                    root[0][i] = root[0][i - 1];
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (board[i][j] == 'X') {
                    if (root[i][j - 1] != null) {
                        root[i][j] = root[i][j - 1];
                    } else if (root[i - 1][j] != null) {
                        root[i][j] = root[i - 1][j];
                    } else {
                        root[i][j] = new MyPoint(i, j);// 自己是根节点
                    }
                }
            }
        }
        HashSet<MyPoint> points = new HashSet<>();
        for (int i = 0; i < root.length; i++) {
            for (int j = 0; j < root[0].length; j++) {
                if (root[i][j] != null)
                    points.add(root[i][j]);
            }
        }
        return points.size();
    }
}
  • 上面的方法是基本方法,不是很难,但是我们可以优化一下,我们不需要创建什么Point对象。但是本质还是不变的,并且可以少去最后一次的for。
  • 我们还是定义一个root数组,这里用int就行了。因为里面存放的是第几个战舰。而我们遍历到一个X的时候,只需要判断左边和上边是不是有战舰,如果有,就将root左边和上边的求和等于当前的。注意,这里左边和上边一定只可能有一个是战舰。左边和上边要是没有,那么当前就是战舰的根节点,我们再root里面记录是第几个就行了。所以还需要一个ans记录当前是第一个战舰了。也就是之间代码中new的时候,我们就让ans++即可。
class Solution {
    public int countBattleships(char[][] board) {
        int m = board.length;
        int n = board[0].length;
        int root[][] = new int[m][n];
        int ans = 0;
        if (board[0][0] == 'X') {
            root[0][0] = ++ans;
        }
        for (int i = 1; i < m; i++) {// 第0列
            if (board[i][0] == 'X') {
                if (root[i - 1][0] == 0) {
                    root[i][0] = ++ans;
                } else {
                    root[i][0] = root[i - 1][0];
                }
            }
        }
        for (int i = 1; i < n; i++) {// 第0行
            if (board[0][i] == 'X') {
                if (root[0][i - 1] == 0) {
                    root[0][i] = ++ans;
                } else {
                    root[0][i] = root[0][i - 1];
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (board[i][j] == 'X') {
                    if (root[i][j - 1] != 0 || root[i - 1][j] != 0) {// 上面和左边最多只有一个是战舰
                        root[i][j] = root[i][j - 1] + root[i - 1][j];
                    } else {
                        root[i][j] = ++ans;// 自己是根节点
                    }
                }
            }
        }
        return ans;
    }
}
  • 总结:这个题目首先要读懂题目的意思,读不懂就多读几遍。然后做题技巧的话,主要是理解题目意思。接着就是利用编程语言的工具了,工具不能用就自己创造工具。第二种方法就是第一种方法的改进,不需要new对象,并且少了一次for。当然这个方法还可以不定义新的数组,直接使用所给数组,这样空间复杂度会再次降低。
  • 进阶版本:一次扫描算法,并只使用 O(1) 额外空间,并且不修改 board 的值来解决这个问题。还没有想到好的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值