叠盘子排序 | 豆包MarsCode AI刷题

46 阅读6分钟

具体描述

image.png

代码

public class Main {
    public static String solution(int[] plates, int n) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (i < n) {
            int j = i;
            // Find the end of the consecutive increasing sequence
            while (j + 1 < n && plates[j + 1] == plates[j] + 1) {
                j++;
            }
            // If the sequence has at least 3 elements, append as range
            if (j - i >= 2) {
                sb.append(plates[i]).append("-").append(plates[j]);
                i = j + 1;
            } else {
                // Otherwise, append individual numbers
                for (int k = i; k <= j; k++) {
                    sb.append(plates[k]);
                    if (k < j) sb.append(",");
                }
                i = j + 1;
            }
            if (i < n) sb.append(",");
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{-3, -2, -1, 2, 10, 15, 16, 18, 19, 20}, 10).equals("-3--1,2,10,15,16,18-20"));
        System.out.println(solution(new int[]{-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20}, 20).equals("-6,-3-1,3-5,7-11,14,15,17-20"));
        System.out.println(solution(new int[]{1, 2, 7, 8, 9, 10, 11, 19}, 8).equals("1,2,7-11,19"));
    }
}

思路

问题要求将一系列整数(盘子序号)按照一定的规则归类,其中每一组包含连续递增的序列,且序列的长度必须至少为3。若不满足这个条件,单独列出这个盘子。

具体的解决思路如下:

  1. 遍历数组,查找连续的递增子序列。

  2. 记录子序列的起始和结束位置

    • 当发现一个连续递增的子序列长度超过等于3时,将该子序列用起始和结束元素表示,如 -3--1
    • 否则,直接将这些元素单独列出。
  3. 构建最终结果:使用 StringBuilderStringBuffer 拼接结果,确保最终输出的格式正确。

  4. 注意边界条件,例如数组为空,或者没有任何连续递增的子序列等。

解法分析

  • 遍历和识别连续序列

    • 遍历数组,对于每个元素,查看其下一个元素是否是递增的(即 plates[j+1] == plates[j] + 1)。
    • 如果是,继续向后搜索,直到序列结束。
    • 如果连续子序列的长度小于3,则单独列出这些元素。
  • 拼接字符串

    • 如果连续子序列的长度大于等于3,将其格式化为 start-end(例如:-3--1)。
    • 否则,直接将这些元素添加到结果中,并确保以逗号分隔。
  • 时间复杂度和空间复杂度分析

    • 时间复杂度:由于我们只需要一次遍历整个数组,时间复杂度是 O(n) ,其中 n 是数组的长度。
    • 空间复杂度:主要是用于保存结果字符串的空间,因此空间复杂度是 O(n) ,用于存储输出字符串。

分步解析

步骤 1: 初始化变量

首先,我们需要一些变量来存储最终结果并帮助我们遍历数组:

StringBuilder sb = new StringBuilder();
int i = 0;
  • StringBuilder sb:用来存储最终的结果字符串。StringBuilderString 更高效,因为它支持动态扩展并减少了不必要的字符串拼接操作。
  • int i = 0:用来遍历数组 plates 的索引。每次找到一个连续的递增序列,我们都将 i 更新为新的索引位置。

步骤 2: 遍历盘子数组

我们使用 while 循环遍历整个盘子序列,直到处理完所有盘子:

while (i < n) {
    int j = i;
  • int j = i:这里 j 用来查找连续递增序列的结束位置。从 i 开始,我们尝试找到一个连续递增的序列。

步骤 3: 查找连续递增序列

接下来,我们使用一个嵌套的 while 循环查找连续递增的序列:

while (j + 1 < n && plates[j + 1] == plates[j] + 1) {
    j++;
}
  • plates[j + 1] == plates[j] + 1:检查 plates[j + 1] 是否等于 plates[j] 加1,这意味着盘子序号是连续递增的。
  • j++:如果是递增的,继续向后检查下一个盘子。

j 将指向当前递增序列的最后一个盘子。

步骤 4: 判断序列长度并处理

在找到了一个连续递增的子序列后,我们需要判断这个子序列的长度,并决定是用区间表示还是单独列出。

情况 1:序列长度大于等于 3

如果递增序列的长度大于或等于3,我们将它表示为一个区间 start-end(例如 -3--1):

if (j - i >= 2) {
    sb.append(plates[i]).append("-").append(plates[j]);
    i = j + 1;
}
  • j - i >= 2:判断序列的长度。如果长度大于等于3(即 j - i >= 2),则我们认为它是一个有效的连续序列。
  • sb.append(plates[i]).append("-").append(plates[j]):将序列的起始和结束盘子编号按区间格式拼接成字符串。
  • i = j + 1:更新 ij + 1,跳到下一个盘子位置。

情况 2:序列长度小于 3

如果递增序列的长度小于3,我们将这些盘子单独列出:

else {
    for (int k = i; k <= j; k++) {
        sb.append(plates[k]);
        if (k < j) sb.append(",");
    }
    i = j + 1;
}
  • for (int k = i; k <= j; k++):遍历当前序列中的所有盘子。
  • sb.append(plates[k]):将当前盘子编号添加到结果字符串中。
  • if (k < j) sb.append(","):在每个盘子后面添加逗号,除了最后一个盘子。
  • i = j + 1:更新 i,继续处理下一个位置。

步骤 5: 处理逗号

我们需要在每个序列或盘子后加上逗号,除非它是最后一个元素:

if (i < n) sb.append(",");
  • if (i < n) sb.append(","):如果还有后续元素,则在当前结果后面添加一个逗号。

步骤 6: 返回结果

当遍历完所有盘子后,返回最终构建的字符串:

return sb.toString();

完整实现

public class Main {
    public static String solution(int[] plates, int n) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (i < n) {
            int j = i;
            while (j + 1 < n && plates[j + 1] == plates[j] + 1) {
                j++;
            }
            if (j - i >= 2) {
                sb.append(plates[i]).append("-").append(plates[j]);
                i = j + 1;
            } else {
                for (int k = i; k <= j; k++) {
                    sb.append(plates[k]);
                    if (k < j) sb.append(",");
                }
                i = j + 1;
            }
            if (i < n) sb.append(",");
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{-3, -2, -1, 2, 10, 15, 16, 18, 19, 20}, 10)); // "-3--1,2,10,15,16,18-20"
        System.out.println(solution(new int[]{-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20}, 20)); 
        System.out.println(solution(new int[]{1, 2, 7, 8, 9, 10, 11, 19}, 8)); // "1,2,7-11,19"
    }
}

时空复杂度

时间复杂度

  • 时间复杂度

    • 我们通过一次遍历数组来查找连续递增的子序列。因此时间复杂度为 O(n) ,其中 n 是输入数组的长度。
  • 空间复杂度

    • 使用 StringBuilder 拼接结果,最坏情况下需要存储整个数组的序列,所以空间复杂度为 O(n)

空间复杂度

  • 空间使用Counter对象的大小与棋子种类数成正比。如果有 K 种不同棋子,空间复杂度为 O(K)。
  • 输入数据:输入数组本身占用 O(N)。
  • 总复杂度:O(N+K),通常 K≪N。

总结

解决这类问题时,核心是将复杂问题拆解成更小、更易处理的子问题。在本题中,关键是将连续递增的序列从整个数组中提取出来,然后根据序列长度的不同采取不同的策略。这种分治法的思想可以让问题变得更加简洁明了。例如,当连续的盘子序号小于3时,我们不需要做太多处理;当它们满足条件时,我们就把这些盘子作为一个区间输出。