俄罗斯方块

思路分析

  1. 读取输入数据:
    • 15×10的游戏网格
    • 4×4的板块形状
    • 起始列号
  2. 处理板块:
    • 提取板块中所有方块的位置
    • 根据起始列号计算板块在网格中的水平位置
  3. 模拟下落过程:
    • 从顶部开始尝试下落
    • 检查是否能继续下落(不超出边界且不与其他方块重叠)
    • 找到最终的落点位置
  4. 更新网格:
    • 将板块的方块添加到网格中

动画演示: 重点看几个变量的变化

详细的内容在代码的中都有讲解了:

参考代码

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取15*10的游戏网格
        int[][] gameBoard = new int[20][20];
        for (int i = 1; i <= 15; i++) {
            for (int j = 1; j <= 10; j++) {
                gameBoard[i][j] = scanner.nextInt();
            }
        }
        // 读取4*4的板块形状
        int[][] pattern = new int[5][5];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                pattern[i][j] = scanner.nextInt();
            }
        }
        // 读取起始劣号(1-7)
        int startCol = scanner.nextInt();
        // 提取板块中所有方块的位置
        List<int[]> blocks = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (pattern[i][j] == 1) {
                    blocks.add(new int[]{i, j});
                }
            }
        }
        // 计算板块在网格中的起始列
        int y = startCol;
        // 起始行
        int x = 1; // 从顶部开始
        // 尝试下落到下一行, 看看能不能移动
        while (canMoveTo(x + 1, y, blocks, gameBoard)) {
            // 可以继续下落
            x++;
        }

        // 将板块添加到网格中
        for (int[] block : blocks) {
            // 这个是在板块中的位置
            int patternX = block[0];
            int patternY = block[1];
            // 加上基础的位置, 就是在网格中的位置
            int gridX = x + patternX;
            int gridY = y + patternY;
            gameBoard[gridX][gridY] = 1;
        }
        // 输出结果
        for (int i = 1; i <= 15; i++) {
            for (int j = 1; j <= 10; j++) {
                System.out.print(gameBoard[i][j] + " ");
            }
            System.out.println();
        }

    }


    private static boolean canMoveTo(int x, int y, List<int[]> blocks, int[][] gameBoard) {
        for (int[] block : blocks) {
            // 这个是在板块中的位置
            int patternX = block[0];
            int patternY = block[1];
            // 加上基础的位置, 就是在网格中的位置
            int gridX = x + patternX;
            int gridY = y + patternY;
            // 检查是否超出底部, 或者网格中的这个位置已经有方块了
            if (gridX > 15 || gameBoard[gridX][gridY] == 1) {
                return false;
            }
        }
        // 可以继续下落
        return true;
    }
}

窗户

思路步骤:

  1. 读取 N 和 M
  2. 初始化窗口列表 windows(按输入顺序添加,先输入的为底层)
  3. 对于每次点击 (x, y):
    1. 如果 (x, y) 不在任何窗口中:
      1. 输出 "IGNORED"
    2. 如果 (x, y) 在某个窗口中:
      1. 将该窗口移动到最上方
      2. 输出该窗口的编号

数组实现

import java.util.Scanner;

public class Main {
    static class Window {
        int id;
        int x1, x2, y1, y2;

        public Window(int id, int x1, int x2, int y1, int y2) {
            this.id = id;
            this.x1 = x1;
            this.x2 = x2;
            this.y1 = y1;
            this.y2 = y2;
        }
    }

    static int n, m;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Window[] windows = new Window[15];
        n = scanner.nextInt();
        m = scanner.nextInt();
        for (int i = 1; i <= n; i++) {
            int x1 = scanner.nextInt();
            int y1 = scanner.nextInt();
            int x2 = scanner.nextInt();
            int y2 = scanner.nextInt();
            windows[i] = new Window(i, x1, x2, y1, y2);
        }
        while (m-- > 0) {
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            // 找到点击到的窗口
            int index = findWindow(x, y, windows);
            if (index == 0) {
                // 没有点击到窗口
                System.out.println("IGNORED");
                continue;
            }
            Window window = windows[index];
            // 找到了窗口
            System.out.println(window.id);
            // 将窗口放到最上方
            for (int i = index; i < n; i++) {
                windows[i] = windows[i + 1];
            }
            windows[n] = window;
        }
    }

    private static int findWindow(int x, int y, Window[] windows) {
        int index = 0;
        for (int i = n; i >= 1; i--) {
            if (windows[i].x1 <= x && x <= windows[i].x2 &&
                    windows[i].y1 <= y && y <= windows[i].y2) {
                index = i;
                break;
            }
        }
        return index;
    }

}

链表实现

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
    static class Window {
        int id;
        int x1, x2, y1, y2;

        public Window(int id, int x1, int x2, int y1, int y2) {
            this.id = id;
            this.x1 = x1;
            this.x2 = x2;
            this.y1 = y1;
            this.y2 = y2;
        }
    }

    static int n, m;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        LinkedList<Window> windowsList = new LinkedList<>(); // 使用链表存储窗口
        n = scanner.nextInt();
        m = scanner.nextInt();
        for (int i = 1; i <= n; i++) {
            int x1 = scanner.nextInt();
            int y1 = scanner.nextInt();
            int x2 = scanner.nextInt();
            int y2 = scanner.nextInt();
            windowsList.addFirst(new Window(i, x1, x2, y1, y2)); // 插入头部
        }
        while (m-- > 0) {
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            boolean found = false;
            Iterator<Window> iterator = windowsList.iterator();

            // 从链表头部(最上方)开始遍历
            while (iterator.hasNext()) {
                Window w = iterator.next();
                if (x >= w.x1 && x <= w.x2 && y >= w.y1 && y <= w.y2) {
                    // 找到点击的窗口
                    iterator.remove();       // 从当前位置移除
                    windowsList.addFirst(w); // 移到链表头部(最上方)
                    System.out.println(w.id);
                    found = true;
                    break;
                }
            }

            if (!found) {
                System.out.println("IGNORED");
            }
        }
    }
}

二进制王国

思路

  1. 题目目标
  • 给定 N 个由 '0''1' 组成的字符串(代表家庭)
  • 要将这些字符串拼接成一个长字符串
  • 目标是:拼接后的结果字典序最小
  1. 错误直觉 & 正确
  • ❌ 错误做法:直接对字符串数组进行自然排序(Arrays.sort(strings)
    • 例如:"11""110",虽然 "11" < "110",但拼接时 "11011"要比 "11110"更小
  • ✅ 正确策略:贪心思想 —— 比较两个字符串拼接后的结果(自定义比较函数)
    我们要定义一种新的比较方式:
    如果 s1 + s2 < s2 + s1(字典序),则 s1 应该排在 s2 前面
    否则 s2 排在前面

参考代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        String[] strings = new String[n];
        for (int i = 0; i < n; i++) {
            strings[i] = scanner.next();
        }
        Arrays.sort(strings, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
        for (int i = 0; i < n; i++) {
            System.out.print(strings[i]);
        }
    }
}

额外知识点:自定义排序(Comparator)

🎯 这是算法竞赛中的高频技巧!

  1. 为什么要用自定义排序?
  • 默认排序(如字符串自然序)不满足题目要求
  • 我们需要根据特定逻辑决定元素之间的先后关系
  1. Java 中如何实现?

使用 Arrays.sort(T[], Comparator<T>)这两个参数其实是 Arrays.sort(数组, 比较器);

这里的“比较器”就是一个规则说明书,告诉 sort 函数:给你两个元素,你怎么判断谁应该排在前面?

简单来说, 比较器是一个叫compareTo会给你两个参数 a, b根据返回值决定排序
compareTo 返回值怎么看?记住口诀:
负数:前面的小(a 排前)
正数:后面的小(b 排前)

🛠 方法一:使用匿名内部类(传统写法,逻辑清晰)

Arrays.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        // 我们自定义的规则:
        // 如果 s1+s2 < s2+s1,说明 s1 应该排在 s2 前面
        String order1 = s1 + s2;  // s1 在前
        String order2 = s2 + s1;  // s2 在前
        return order1.compareTo(order2); // 如果order1比order2字典序小, 就会返回小于0的数
    }
});

💡 方法二:Lambda 表达式(简化写法)

上面的写法逻辑清晰,但代码较长。Java 8 之后提供了更简洁的写法:

Arrays.sort(strings, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));

这行代码和上面的匿名类是完全等价的!

拆解来看:

  • (s1, s2) → 就是 compare(String s1, String s2) 的参数
  • -> → 意思是“执行下面的操作”
  • (s1 + s2).compareTo(s2 + s1) → 就是我们写的比较逻辑(同时也是返回值)

所以 Lambda 只是“语法糖”:
它让代码更短,但逻辑和匿名内部类一模一样!

三国游戏

思路

题目目标
  • 有三个国家:魏(X)、蜀(Y)、吴(Z),初始士兵数都为 0。
  • n 个独立事件,每个事件发生会分别给 X、Y、Z 加分。
  • 游戏结束后,如果某个国家的士兵数 大于其他两国之和,则该国获胜。
    • 比如:X > Y + Z → 魏国获胜
  • 问:最多可以发生多少个事件,使得至少有一个国家获胜?
  • 如果无论如何都没有国家能赢,输出 -1

✅ 注意:我们不是要让某国一定赢,而是要在“能让某国赢”的前提下,尽可能多地发生事件!

关键转化 —— “获胜条件”如何判断?

原始条件:X > Y + Z

我们可以变形:X - Y - Z > 0

这个差值我们称为 “魏国的净贡献”。

💡 启发:

  • 每个事件对魏国的“净贡献” = A[i] - B[i] - C[i]
  • 如果我们选择了一组事件,它们的净贡献总和 > 0,那么魏国就赢了!

✅ 所以问题变成了:选尽可能多的事件,使得它们的净贡献总和 > 0

贪心策略 —— 如何选事件最多?

我们要选最多的事件,同时保证总净贡献 > 0。

贪心思想:

  • 优先选“净贡献大”的事件,这样可以用尽量少的“负贡献”事件来凑数量。
  • 具体做法:
    1. 把所有事件按“净贡献”从大到小排序
    2. 从最大的开始累加
    3. 只要累计和 > 0,就可以继续选
    4. 一旦累计和 ≤ 0,就不能再选了(否则不满足获胜条件)

举例说明(魏国视角):

事件 A B C 净贡献 (A-B-C)
1 1 2 1 1-2-1 = -2
2 2 3 0 2-3-0 = -1
3 2 2 7 2-2-7 = -7

排序后:[-1, -2, -7](升序),我们从后往前加:

  • -1 → sum = -1 ❌ 不大于 0 → 一个都不能选!

这里所有净贡献都是负的,加起来肯定 ≤ 0,所以魏国不可能赢。

上面是魏国的情况。我们还要看蜀国和吴国是否可能赢。

  • 蜀国赢:Y > X + ZY - X - Z > 0
  • 吴国赢:Z > X + YZ - X - Y > 0

所以我们对每个国家:

  1. 计算每个事件对该国的“净贡献”
  2. 排序,从大到小累加
  3. 统计最多能选多少个事件,使得总净贡献 > 0

最后取三个结果中的最大值。

✅ 如果最大值是 0,说明没人能赢,输出 -1

验证样例

样例输入:

3
1 2 2   ← A (魏)
2 3 2   ← B (蜀)
1 0 7   ← C (吴)
蜀国视角(Y > X+Z):

净贡献 = B - A - C

  • 事件1: 2-1-1 = 0
  • 事件2: 3-2-0 = 1
  • 事件3: 2-2-7 = -7

排序:[-7, 0, 1]从后往前:

  • 加 1 → sum=1 > 0 → count=1
  • 加 0 → sum=1 > 0 → count=2
  • 加 -7 → sum=-6 ≤ 0 → 停止
    → 蜀国最多选 2 个事件(事件2 和 事件1)
吴国视角(Z > X+Y):

净贡献 = C - A - B

  • 事件1: 1-1-2 = -2
  • 事件2: 0-2-3 = -5
  • 事件3: 7-2-2 = 3

排序:[-5, -2, 3]

  • 加 3 → sum=3 > 0 → count=1
  • 加 -2 → sum=1 > 0 → count=2
  • 加 -5 → sum=-4 ≤ 0 → 停止
    → 吴国最多 2 个事件

✅ 最大值 = max(0, 2, 2) = 2 → 输出 2

参考代码

数据类型注意:用 long

题目中 A[i], B[i], C[i] ≤ 10^9n ≤ 10^5,总和最大可达 10^9 * 10^5 = 10^14,远超 int 范围(约 2×10⁹),必须使用 long 存储贡献和累计和!

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取事件数量
        int n = scanner.nextInt();

        // 读取三个国家的得分数组
        long[] A = new long[n]; // 魏国X
        long[] B = new long[n]; // 蜀国Y
        long[] C = new long[n]; // 吴国Z

        for (int i = 0; i < n; i++) {
            A[i] = scanner.nextLong();
        }
        for (int i = 0; i < n; i++) {
            B[i] = scanner.nextLong();
        }
        for (int i = 0; i < n; i++) {
            C[i] = scanner.nextLong();
        }

        // 分别计算三个国家能获胜的最大事件数
        int resultX = calculateMaxEvents(n, A, B, C, 0); // 魏国X
        int resultY = calculateMaxEvents(n, A, B, C, 1); // 蜀国Y
        int resultZ = calculateMaxEvents(n, A, B, C, 2); // 吴国Z

        // 取三个结果中的最大值
        int maxResult = Math.max(resultX, Math.max(resultY, resultZ));

        // 如果最大值是0,说明没有国家能获胜,输出-1
        System.out.println(maxResult > 0 ? maxResult : -1);
    }

    /**
     * 计算某个国家能获胜的最大事件数
     * @param n 事件总数
     * @param A 魏国得分数组
     * @param B 蜀国得分数组
     * @param C 吴国得分数组
     * @param country 国家标识:0-魏,1-蜀,2-吴
     * @return 该国家能获胜的最大事件数
     */
    private static int calculateMaxEvents(int n, long[] A, long[] B, long[] C, int country) {
        // 计算每个事件对该国家的纯贡献
        long[] contributions = new long[n];

        for (int i = 0; i < n; i++) {
            switch (country) {
                case 0: // 魏国X:X > Y + Z  => X - Y - Z > 0
                    contributions[i] = A[i] - B[i] - C[i];
                    break;
                case 1: // 蜀国Y:Y > X + Z  => Y - X - Z > 0
                    contributions[i] = B[i] - A[i] - C[i];
                    break;
                case 2: // 吴国Z:Z > X + Y  => Z - X - Y > 0
                    contributions[i] = C[i] - A[i] - B[i];
                    break;
            }
        }

        // 将纯贡献从大到小排序
        Arrays.sort(contributions);

        // 从大到小累加贡献,直到总和<=0为止
        long sum = 0;
        int count = 0;

        // 从最大的贡献开始累加(因为数组是升序排列,所以从后往前遍历)
        for (int i = n - 1; i >= 0; i--) {
            sum += contributions[i];
            if (sum > 0) {
                count++; // 如果总和仍然大于0,可以继续选择事件
            } else {
                break; // 一旦总和<=0,就不能再选择更多事件了
            }
        }

        return count;
    }
}

阶乘求和

思路

题目要求:

S = 1! + 2! + 3! + ... + 202320232023!的末尾9位数字。

✅ 末尾9位 = 对 10^9 取模,即 S % 1000000000

关键观察 —— 阶乘末尾的“0”越来越多!

我们先看几个小例子:

  • 5!=120 → 末尾1个0
  • 10!=3628800 → 末尾2个0
  • 15 → 末尾更多0……

👉 为什么阶乘末尾会有0?

  • 因为 10=2×5,每多一对因子 2 和 5,就多一个末尾0。
  • 在阶乘中,因子2的个数远多于因子5,所以末尾0的个数由因子5的个数决定。
什么时候阶乘的末尾至少有9个0?

我们来数一数从 ​ 1 ​ x 的阶乘中,有多少个因子5:

数字 是否贡献因子5 贡献个数
5 1
10 1
15 1
20 1
25 是(25=5×5) 2
30 1
35 1
40 1

累计到 ​ 40!

  • 总共贡献了:1+1+1+1+2+1+1+1 = 9 个因子5
  • 所以 ​ 40! 至少有 9 个末尾0

✅ 这意味着:​ 40! ​ 10^9 的倍数!

因此:​ 40! \mod 10^9 = 0 \ 41! = 41 \times 40! \Rightarrow 41! \mod 10^9 = 0 \ \vdots \ 202320232023! \mod 10^9 = 0

大发现!只需计算前40项!

因为从 ​ 40! 开始,所有阶乘对 ​ 10^9 取模都是 0,不会影响最终结果的末尾9位。

所以我们只需要计算:​ S \mod 10^9 = (1! + 2! + \cdots + 39!) \mod 10^9

这个计算量非常小,完全可以暴力模拟!

注意点

注意点 说明
✅ 不要直接算大阶乘! ​ 202320232023! 是天文数字,根本无法存储。必须利用数学性质优化。
✅ 末尾0的个数由因子5决定 这是经典结论,记住:每5个数贡献1个5,每25个数额外再贡献1个……但本题只需数到40即可。
✅ 一旦阶乘能被​ 10^9 整除,后续都为0 这是本题最关键的剪枝思想!从40开始的所有项都可以忽略。
✅ 边算边取模,防止溢出 虽然我们只算到40,但 facsum 仍可能很大,必须用 long 并及时 % MOD
✅ MOD = 1_000_000_000 写成 1000000000 容易数错位数,Java 中可用 1_000_000_000 提高可读性。

参考代码

public class Main {
    public static void main(String[] args) {
      // 2 * 5 == 10
      // 4 * 5 其实就是2*2*5==10
      // 而对于阶乘来说 1*2*3*4*5 到这里就会多一个0, 然后继续*6*7*8*9*10又会多一个0, 也就是变成了xx00
      // 我们要求的是某个数字x%1e9
      // 考虑一下如果某个数字x!末尾大于9个0, 也就是以9个0接尾
      // 这个数x!可以被表示成a * 1e9, 然后这个数字再取余, x*1e9 % 1e9==0
      // 所以如果某个数字x!末尾产生了9个0, 这个数字取余后的结果就是0
      // 现在要找的就是这个产生了9个0的x!的x到底是谁, 简单推一下:
      // 阶乘:1*2*3*..5..10..15..20..25..30..35..40
      // 产生0的个数:..1..1...1...1...2...1...1...1
      // 到这里就已经有9个0了, 也就是40!末尾有9个0, 40!%1e9==0, 换句话说40!%1e9这一项对于结果没有影响, 后面同理, 41, 42等等的阶乘%1e9都对结果没有影响, 那么我们只需要求1到40的这些就行了: 1!%1e9 + 2!%1e9 + ... 40!%1e9
      // 这就算的很快了, 直接输出结果
        int MOD = 1000000000;
        long sum = 0;
        long fac = 1;
        for (int i = 1; i <= 40; i++) {
            fac = (fac * i) % MOD;
            sum = (sum + fac) % MOD;
        }
        System.out.println(sum);
    }
}

被替换的身份证

思路步骤

每人两张牌,浅梦先手,谁先出完谁赢。

浅梦能赢的情况只有三种:

  1. 她有 王炸(MF 或 FM) → 一招秒杀
  2. 她有 对子(两张一样) → 一次性出完
  3. 她的 最大单牌 ≥ 沐风的最大单牌,且沐风没有王炸
    → 她先出最大牌,沐风无法响应,她再出第二张赢

其他所有情况,沐风都能赢。

参考代码

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt(); // 读取测试用例数量

        // 创建一个 map, 用来表示每张牌的大小顺序
        // 数字越小表示牌越小, 方便比较
        Map<Character, Integer> rank = new HashMap<>();
        rank.put('3', 3);
        rank.put('4', 4);
        rank.put('5', 5);
        rank.put('6', 6);
        rank.put('7', 7);
        rank.put('8', 8);
        rank.put('9', 9);
        rank.put('X', 10);  // X 代表 10
        rank.put('J', 11);  // J (Jack)
        rank.put('Q', 12);  // Q (Queen)
        rank.put('K', 13);  // K (King)
        rank.put('A', 14);  // A (Ace)
        rank.put('2', 15);  // 2 比 A 大
        rank.put('M', 16);  // M (Black Joker)
        rank.put('F', 17);  // F (Red Joker) —— 最大

        // 处理 T 组测试数据
        while (T-- > 0) {
            String qm = sc.next(); // 浅梦的两张牌
            String mf = sc.next(); // 沐风的两张牌

            // 情况1: 浅梦有王炸(MF 或 FM)-> 直接获胜
            boolean isQMWangZha = (qm.equals("MF") || qm.equals("FM"));

            // 情况2: 浅梦有对子(两张牌一样)-> 直接出完获胜
            boolean isQMPair = (qm.charAt(0) == qm.charAt(1));

            // 如果是王炸或对子, 浅梦直接赢
            if (isQMWangZha || isQMPair) {
                System.out.println("ShallowDream");
                continue; // 跳过后续判断
            }

            // 情况3: 浅梦出单张, 看能否压制沐风
            // 先找出浅梦两张牌中较大的那张
            char qmCard1 = qm.charAt(0);
            char qmCard2 = qm.charAt(1);
            int maxQm = Math.max(rank.get(qmCard1), rank.get(qmCard2));

            // 再找出沐风两张牌中较大的那张
            char mfCard1 = mf.charAt(0);
            char mfCard2 = mf.charAt(1);
            int maxMf = Math.max(rank.get(mfCard1), rank.get(mfCard2));

            // 判断沐风是否有王炸
            boolean isMFWangZha = (mf.equals("MF") || mf.equals("FM"));

            // 如果浅梦的最大单牌 >= 沐风的最大单牌, 且沐风没有王炸
            // 那么浅梦先出最大牌, 沐风无法响应, 浅梦下一轮出完获胜
            if (maxQm >= maxMf && !isMFWangZha) {
                System.out.println("ShallowDream");
            } else {
                // 其他所有情况, 沐风都能赢
                System.out.println("Joker");
            }
        }
    }
}

小蓝的漆房

思路步骤

目标:把一排房子涂成同一种颜色,每天只能涂一个长度为 k 的连续区间。

问:最少需要多少天?

核心策略:枚举最终颜色 + 贪心覆盖
  • 颜色种类最多只有 60 种(1~60),我们可以枚举每一种颜色作为最终目标颜色
  • 对每种颜色,计算:从左到右,最少需要多少天才能把整条走廊都变成这种颜色
  • 然后取所有颜色中的最小天数,就是答案
贪心策略(对某一目标颜色):

从左往右扫描:

  • 如果当前房子已经是目标颜色 → 不用管,跳过
  • 如果当前房子不是目标颜色 → 必须涂!
    • 从这个房子开始,向右涂 k 个房子(即一个区间)
    • 涂完后,天数 +1
    • 然后继续往后扫描

✅ 为什么贪心是对的?
因为越往左越早处理,能覆盖更多未达标的位置,不会浪费操作。

参考代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt(); // 测试用例数量

        while (T-- > 0) {
            int n = sc.nextInt();    // 房子数量
            int k = sc.nextInt();    // 每天能涂的长度
            int[] colors = new int[n]; // 每个房子的初始颜色

            for (int i = 0; i < n; i++) {
                colors[i] = sc.nextInt();
            }

            // 记录所有可能的目标颜色(1 到 60)
            // 我们尝试让整条走廊变成 color 1, 2, ..., 60, 看看哪种最省天数
            int minDays = Integer.MAX_VALUE;

            // 枚举每一种可能的最终颜色(1 到 60)
            for (int color = 1; color <= 60; color++) {
                // 模拟从左到右涂色的过程
                // 使用一个数组来表示当前每间房子的颜色(模拟涂色结果)
                int[] currentColors = Arrays.copyOf(colors, n);

                int days = 0; // 涂色天数
                // 从左到右扫描每间房子
                for (int i = 0; i < n; i++) {
                    // 如果当前房子已经是目标颜色, 不需要涂
                    if (currentColors[i] == color) {
                        continue;
                    }
                    // 如果当前房子不是目标颜色, 必须涂!
                    // 从位置 i 开始, 向右涂 k 个房子
                    // 注意: 不能越界
                    for (int j = i; j < i + k && j < n; j++) {
                        currentColors[j] = color;
                    }
                    // 涂一次, 天数 +1
                    days++;
                }
                // 更新最小天数
                minDays = Math.min(minDays, days);
            }
            // 输出当前测试用例的最少天数
            System.out.println(minDays);
        }
    }
}

一个敲代码的