面试题 16.22. 兰顿蚂蚁

题目

一只蚂蚁坐在由白色和黑色方格构成的无限网格上。开始时,网格全白,蚂蚁面向右侧。 每行走一步,蚂蚁执行以下操作。

(1) 如果在白色方格上,则翻转方格的颜色,向右(顺时针)转 90 度,并向前移动一个单位。 (2) 如果在黑色方格上,则翻转方格的颜色,向左(逆时针方向)转 90 度,并向前移动一个单位。

编写程序来模拟蚂蚁执行的前 K 个动作,并返回最终的网格。

网格由数组表示,每个元素是一个字符串,代表网格中的一行,黑色方格由’X’表示,白色方格由’_‘表示, 蚂蚁所在的位置由’L’, ‘U’, ‘R’, ‘D’表示,分别表示蚂蚁左、上、右、下 的朝向。 只需要返回能够包含蚂蚁走过的所有方格的最小矩形。

示例 1:
输入: 0
输出: [“R”]

示例 2:
输入: 2
输出: [ “_X”, “LX” ]

示例 3: 输入: 5
输出: [ “U”, “X”, “XX” ]

说明:

  • K <= 100000

题解

enum BlockColor {
    /**
     * 黑色块
     */
    BLACK("X") {
        @Override
        BlockColor overturn() {
            return WHITE;
        }
    },
    /**
     * 白色块
     */
    WHITE("_") {
        @Override
        BlockColor overturn() {
            return BLACK;
        }
    };

    String color;

    BlockColor(String color) {
        this.color = color;
    }

    /**
     * 方格翻转
     * @return {@link BlockColor}
     */
    abstract BlockColor overturn();
}

enum AntDirection {
    /**
     * 蚂蚁左朝向
     */
    LEFT('L') {
        @Override
        void moveClockwise(Meta meta) {
            meta.row--;
            meta.antDirection = UP;
        }

        @Override
        void moveAnticlockwise(Meta meta) {
            meta.row++;
            meta.antDirection = DOWN;
        }
    },
    /**
     * 上
     */
    UP('U') {
        @Override
        void moveClockwise(Meta meta) {
            meta.column++;
            meta.antDirection = RIGHT;
        }

        @Override
        void moveAnticlockwise(Meta meta) {
            meta.column--;
            meta.antDirection = LEFT;
        }
    },
    /**
     * 右
     */
    RIGHT('R') {
        @Override
        void moveClockwise(Meta meta) {
            meta.row++;
            meta.antDirection = DOWN;
        }

        @Override
        void moveAnticlockwise(Meta meta) {
            meta.row--;
            meta.antDirection = UP;
        }
    },
    /**
     * 下
     */
    DOWN('D') {
        @Override
        void moveClockwise(Meta meta) {
            meta.column--;
            meta.antDirection = LEFT;
        }

        @Override
        void moveAnticlockwise(Meta meta) {
            meta.column++;
            meta.antDirection = RIGHT;
        }
    };

    char direction;

    AntDirection(char direction) {
        this.direction = direction;
    }

    void move(Meta meta) {
        // 当前块白色顺时针移动
        if (meta.blockColor == BlockColor.WHITE) {
            this.moveClockwise(meta);
        } else {
            // 当前块为黑色逆时针移动
            this.moveAnticlockwise(meta);
        }
    }

    /**
     * 顺时针移动
     */
    abstract void moveClockwise(Meta meta);

    /**
     * 逆时针移动
     */
    abstract void moveAnticlockwise(Meta meta);
}

static class Meta {
    // 方块颜色哈希缓存
    Map<Integer, Map<Integer, BlockColor>> paths = new HashMap<>(8);
    // 初始方向
    AntDirection antDirection = AntDirection.RIGHT;
    BlockColor blockColor;
    // 当前所在行列位置
    int row = 0, column = 0;
    // 行列最大最小值
    int minRow = 0, maxRow = 0, minColumn = 0, maxColumn = 0;

    Meta() {
        // 初始化至少一行
        paths.put(0, new HashMap<>());
    }

    void move() {
        // 当前块颜色
        BlockColor color = paths.getOrDefault(this.row, new HashMap<>(4)).getOrDefault(column, BlockColor.WHITE);
        this.blockColor = color;
        paths.merge(this.row, new HashMap<>(4), (o, n) -> o);
        paths.get(this.row).put(this.column, color.overturn());
        // 前进
        this.antDirection.move(this);
        // 更新行列最大值
        this.minRow = Integer.min(this.row, this.minRow);
        this.maxRow = Integer.max(this.row, this.maxRow);
        this.minColumn = Integer.min(this.column, this.minColumn);
        this.maxColumn = Integer.max(this.column, this.maxColumn);
    }

    List<String> result() {
        // 行字符个数
        int width = this.maxColumn - this.minColumn + 1;

        List<String> collect =
            IntStream.rangeClosed(this.minRow, this.maxRow)
                .mapToObj(row ->
                    Optional.ofNullable(this.paths.get(row))
                        .filter(it -> !it.isEmpty())
                        .map(columns ->
                            IntStream.rangeClosed(this.minColumn, this.maxColumn)
                                .mapToObj(column ->
                                    Optional.ofNullable(columns.get(column))
                                        .map(it -> it.color)
                                        .orElse(BlockColor.WHITE.color)
                                )
                                .collect(Collectors.joining(""))
                        )
                        .orElseGet(() -> String.join("", Collections.nCopies(width, BlockColor.WHITE.color)))
                )
                .collect(Collectors.toList());

        // 将当前行列位置替换为方向
        int replaceRow = this.row - this.minRow;
        StringBuilder sb = new StringBuilder(collect.get(replaceRow));
        sb.setCharAt(this.column - this.minColumn, this.antDirection.direction);
        collect.set(replaceRow, sb.toString());

        return collect;
    }
}

public List<String> printKMoves(int K) {
    Meta meta = new Meta();
    while (K > 0) {
        meta.move();
        K--;
    }

    return meta.result();
}