程序员老鬼

在楼梯睡觉被领导抓到了。。

今天看到一个网友发帖吐槽:在公司楼梯上睡觉被老板抓到了,真的是笑到不行。
这哥们儿周一上班,显然是睡得太晚,结果眼皮都睁不开了,于是“聪明”地想到一个好地方——楼梯。
毕竟,这种地方人少,也没人打扰,想着悄悄补个觉,反正老板也没那么早出现嘛。结果睡得正香,被老板发现了,尴尬得不行。
Image
我想,那位同事当时的心情,一定是比写出一个死循环还要糟糕。
至于评论里说的“递根烟过去就行了”,哈哈,这倒是个有趣的提议!
不过说实话,老板如果看到你递烟,估计还会觉得你是借此机会想搞点“深度交流”。倒不如直接站起来,拍个肩膀,笑着说“领导,刚才真的太困了,想给大脑充电一下”。
毕竟,面对不确定的情况就得灵活应对嘛。

算法题:棋盘上的战舰

最近看到一个有意思的算法题:棋盘上的战舰。
题目是这样描述的:给定一个棋盘,棋盘上的每个位置可能是海洋,也可能是战舰的一部分。战舰的组成方式是垂直或水平排列,不会出现角落相接或斜着的战舰。任务是计算出棋盘上有多少艘战舰。
对我来说,这种类型的题目挺有挑战性的,不仅考察我们对数组和循环的理解,还能让我们思考如何高效地进行空间和时间上的优化。更有趣的是,这类题目很多时候都能通过一种比较直观的方法来解答,但却也有很多潜在的坑,稍不留神就可能陷进去。让我们先来简单理解一下问题吧。

思路

题目给定了一个二维数组,代表棋盘,每个位置上有两个可能的值:‘X’代表战舰的一部分,‘.’代表海洋。我们的目标是找出战舰的数量。一个战舰由一系列连续的‘X’组成,而且必须是水平或者垂直排列,不能斜着。要注意的是,如果一个战舰的某个部分已经被检测过,那么它的其他部分就不需要再被计算了。

解题步骤

  1. 遍历棋盘:我们首先需要遍历整个二维数组。每次我们遇到一个‘X’时,都需要判断它是否是一个新的战舰的开始。为了避免重复计数,我们只关心那些位于战舰的起始位置的‘X’。
  2. 标记已计算的战舰:为了避免重复统计同一艘战舰,可以采用从上至下、从左至右的扫描方式。这样,当我们遇到一个‘X’时,如果它的上方或左边已经有‘X’,那么它就不是一个新战舰的一部分,可以跳过。
  3. 检测新的战舰:为了实现这一点,我们只需要判断当前的‘X’位置的左边和上边是否存在战舰(即‘X’)。如果它们的位置为空(即是海洋‘.’),那么当前的‘X’就是一个新的战舰的起始位置。

Java代码实现

根据上述的思路,我们可以用Java来实现这个算法。下面是我的代码实现:
public class Battleship {
    public int countBattleships(char[][] board) {
        int count = 0;

                // 遍历每一行
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {

                                // 如果当前位置是战舰的一部分
                if (board[i][j] == 'X') {
                    // 判断当前的'X'是不是一个新的战舰
                    if (i > 0 && board[i-1][j] == 'X') {
                        continue;  // 上面已经有'X',说明已经属于一个战舰,不算新战舰
                    }
                    if (j > 0 && board[i][j-1] == 'X') {
                        continue;  // 左边已经有'X',说明已经属于一个战舰,不算新战舰
                    }
                    // 如果当前的'X'没有被上方或左方的'X'覆盖,说明是新战舰的起点
                    count++;
                }
            }
        }

                return count;
    }

    public static void main(String[] args) {
        Battleship bs = new Battleship();
        char[][] board = {
            {'X', '.', 'X', 'X'},
            {'.', 'X', '.', '.'},
            {'.', '.', 'X', 'X'},
            {'X', '.', '.', 'X'}
        };
        System.out.println("战舰数量: " + bs.countBattleships(board));  // 输出应该是3
    }
}

解释代码

  1. 双重循环遍历棋盘:我们通过双重for循环遍历二维数组board。board[i][j]代表当前位置。
  2. 判断新战舰的起始位置:每当遇到一个‘X’,我们检查它的上方和左方是否有‘X’。如果上方或左方已经有‘X’,说明这个‘X’是属于一个已有战舰的部分,不是新战舰。反之,我们就认为这个‘X’是一个新战舰的起点,增加计数。
  3. 返回结果:遍历完整个棋盘后,count即为战舰的数量。

优化

这个方法的时间复杂度是O(m * n),其中m是棋盘的行数,n是列数。由于每个元素最多检查一次,上下左右的边界检查都是常数时间操作,所以时间复杂度是线性的。

可能遇到的坑

其实,这类题目在面试中是很常见的,往往看似简单,但要真正理解清楚哪些‘X’应该被计算,哪些不应该,往往需要一些经验。
比如,别忘了检查棋盘的边界!如果你的代码没有检查到边界条件,程序可能会抛出ArrayIndexOutOfBoundsException,尤其是在检查上方或左边是否是‘X’时,最好先确保i > 0和j > 0,避免数组越界。

小结

通过这个题目,我们不仅复习了二维数组的遍历技巧,还加深了对“标记已计算”的思路理解。如果把它放在实际工作中,我觉得它可以作为一个经典的“重复计算优化”的练习,帮助我们在面对类似问题时快速找到合适的解决方案。
-END-
ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学找我领取。
Image
以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。