从数组中识别和收集连续范围[重复]

Posted

技术标签:

【中文标题】从数组中识别和收集连续范围[重复]【英文标题】:Identifying and collecting consecutive ranges from an array [duplicate] 【发布时间】:2021-10-26 09:31:46 【问题描述】:

任务

给定 int[] 数组,例如我必须识别连续范围并构建相应的 String

另一个例子是


注意事项

我可以假设数组已经有序并且范围内没有没有重叠

此外,数组可能为空


想法

我知道如何构建一个仅列出所有数字的字符串,例如4, 5, 6, 8, 14, 15, 16,但我不知道如何识别范围,尤其是如何在它们之间添加~

我认为正则表达式在这里可能有用,但我不确定。

【问题讨论】:

"我应该使用正则表达式吗..?" - 不,正则表达式用于查找字符串中的模式,并且您有一个数字数组。所以你需要做的是遍历你的数组,找到所有出现的连续数字,获取第一个和最后一个,将它们添加到字符串中,并在它们之间添加波浪号。 从左到右迭代并建立递增的范围。每次范围结束时,您都会将其结束并结束。 关于“我如何找到连续的数字?”:跟踪可以形成范围的第一个数字以及迭代的当前和最后一个数字。如果 "current" 和 "last" 之间的差 > 1,则 "last" 是上一个范围的结束,而 "current" 是下一个范围的开始。现在只需相应地构建输出并考虑 3 种可能的情况:1)范围的开始和结束数字相同 -> 只打印数字,2)开始和结束有 1 的差异 -> 决定是否打印 @987654325 @ 或 start~end, 3) 打印 start~end 其余部分 【参考方案1】:

试试这个。

static String summary(int[] input) 
    int length = input.length;
    if (length == 0) return "";
    StringBuilder sb = new StringBuilder();
    new Object() 
        int start = input[0];
        int end = input[0];
        String separator = "";

        void append() 
            sb.append(separator).append(start);
            if (start != end)
                sb.append("~").append(end);
            separator = ", ";
        

        void make() 
            for (int i = 1; i < length; ++i) 
                int current = input[i];
                if (current != end + 1) 
                    append();
                    start = current;
                
                end = current;
            
            append();
        
    .make();
    return sb.toString();


public static void main(String[] args) 
    System.out.println(summary(new int[] 4, 5, 6, 8, 14, 15, 16));
    System.out.println(summary(new int[] 13, 14, 15, 16, 21, 23, 24, 25, 100));

输出:

4~6, 8, 14~16
13~16, 21, 23~25, 100

【讨论】:

请注意,不鼓励仅使用代码回答。解释会很有帮助。 匿名的Object 有点奇怪,一个专门的课程会更好更干净。【参考方案2】:

说明

您可以简单地从左到右进行迭代并在此过程中保持范围。

为此,只需记住范围的开始每次迭代中的最后一个值。然后,您可以轻松确定范围是刚刚停止还是继续。如果停止,结束该范围并开始下一个

对于空数组的特殊情况,只需在方法的开头添加一个简单的if,单独处理即可。


构建范围

public static String buildRanges(int[] values) 
    if (values.length == 0) 
        return "";
    

    StringJoiner result = new StringJoiner(", ");

    int rangeStart = values[0];
    int lastValue = values[0];
    // Skip first value to simplify 'lastValue' logic
    for (int i = 1; i < values.length; i++) 
        int value = values[i];

        if (value != lastValue + 1) 
            // Range ended, wrap it up
            int rangeEnd = lastValue;
            result.add(rangeStart == rangeEnd
                ? Integer.toString(rangeStart)
                : rangeStart + "~" + rangeEnd);
            rangeStart = value;
        

        lastValue = value;
    

    // Conclude last range
    int rangeEnd = lastValue;
    result.add(rangeStart == rangeEnd
            ? Integer.toString(rangeStart)
            : rangeStart + "~" + rangeEnd);

    return result.toString();

简化

为了解决代码重复和提高可读性,我建议还引入一个辅助方法rangeToString

public static String rangeToString(int rangeStart, int rangeEnd) 
    return rangeStart == rangeEnd
        ? Integer.toString(rangeStart)
        : rangeStart + "~" + rangeEnd);

然后代码简化为:

public static String buildRanges(int[] values) 
    if (values.length == 0) 
        return "";
    

    StringJoiner result = new StringJoiner(", ");

    int rangeStart = values[0];
    int lastValue = values[0];
    // Skip first value to simplify 'lastValue' logic
    for (int i = 1; i < values.length; i++) 
        int value = values[i];

        if (value != lastValue + 1) 
            // Range ended, wrap it up
            result.add(rangeToString(rangeStart, lastValue);
            rangeStart = value;
        

        lastValue = value;
    

    // Conclude last range
    result.add(rangeToString(rangeStart, lastValue);

    return result.toString();


面向对象的解决方案

如果您愿意,还可以引入专门的Range 类来解决这个问题。在这种特殊情况下可能有点矫枉过正,但仍然如此。

让我们首先创建一个知道其开始和结束的Range 类。此外,它可以正确地将自身转换为String,您可以尝试增加范围。

public final class Range 
    private final int start;
    private int end;

    public Range(int value) 
        start = value;
        end = value;
    

    public boolean add(int value) 
        if (value != end + 1) 
            return false;
        
        end = value;
        return true;
    

    @Override
    public String toString() 
        return start == end
            ? Integer.toString(start)
            : start + "~" + end;
    

现在您可以轻松地在一个简单的循环中使用它:

public static String buildRanges(int[] values) 
    if (values.length == 0) 
        return "";
    

    StringJoiner result = new StringJoiner(", ");

    Range range = new Range(values[0]);
    // Skip first value to simplify logic
    for (int i = 1; i < values.length; i++) 
        int value = values[i];

        if (!range.add(value)) 
            // Range ended, wrap it up
            result.add(range.toString());
            range = new Range(value);
        
    

    // Conclude last range
    result.add(range.toString());

    return result.toString();

收集到List&lt;Range&gt;

这种方法的优点是您还可以收集到List&lt;Range&gt; ranges 之类的东西,然后继续处理数据,而不仅仅是转到String。示例:

public static List<Range> buildRanges(int[] values) 
    if (values.length == 0) 
        return List.of();
    

    List<Range> ranges = new ArrayList<>();

    Range range = new Range(values[0]);
    // Skip first value to simplify logic
    for (int i = 1; i < values.length; i++) 
        int value = values[i];

        if (!range.add(value)) 
            // Range ended, wrap it up
            ranges.add(range);
            range = new Range(value);
        
    

    // Conclude last range
    ranges.add(range);

    return ranges;

如果您还向Range 类添加一些getStart()getEnd() 方法,则特别有用。


注意事项

请注意,如果数组包含重复项,则该方法可能会表现得很奇怪。您没有指定在这种情况下要做什么,所以我只是假设您的用例不存在重复项。

【讨论】:

如果数组包含重复项,最好将其转换为 Set: ''' new HashSet(Arrays.asList(values))); ''' 然后迭代 @AndrzejWięcławski 但是你会失去数组的顺序,这有点关键。你当然可以使用TreeSet,但是你做了两次排序的努力,这是不必要的。如果重复是一件事(OP没有指定),在循环期间处理它们并简单地跳过它们可能更容易(数组是有序的,所以跳过它们非常简单)。【参考方案3】:

假设你有以下Interval 类:

public class Interval 
    public final int min;
    public final int max;

    public Interval(int min, int max) 
        this.min = min;
        this.max = max;
    

    public Interval(int minmax) 
        this(minmax, minmax);
    

    public static List<Interval> toIntervals(int... values) 
        List<Interval> list = new ArrayList<>();
        for (int val: values) 
            addInterval(list, val, val);
        
        return list;
    

    private static void addInterval(List<Interval> list, int min, int max) 
        int i = 0;
        while (i < list.size()) 
            Interval cur = list.get(i);
            if (max < cur.min-1) 
                break;
             else if (cur.max >= min-1) 
                min = Math.min(min, cur.min);
                max = Math.max(max, cur.max);
                list.remove(i);
             else 
                ++i;
            
        
        list.add(i, new Interval(min, max));
    

    @Override
    public String toString() 
        if (min == max) 
            return Integer.toString(min);
        
        return min + "~" + max;
    

您可以将示例中的数字列表转换为区间列表:

    List<Interval> list = Interval.toIntervals(4, 5, 6, 8, 14, 15, 16);

或者:

    List<Interval> list = Interval.toIntervals(13, 14, 15, 16, 21, 23, 24, 25, 100);

然后打印这样的列表:

    System.out.println(list.stream()
            .map(Interval::toString)
            .collect(Collectors.joining(", ")));

【讨论】:

【参考方案4】:

这是一个整数到字符串的转换,而不是 java 中的 cast,这意味着 (TYPE2)OBJECTOFTYPE1 例如 int-to-double (widening) 或 Object -to-String。

一个人必须自己做的范围。

int[] a = 13, 14, 15, 16, 21, 23, 24, 25, 100;

StringBuilder sb = new StringBuilder();
if (a.length > 0) 
    for (int i = 0; i < a.length; ++i) 
        if (sb.length() != 0) 
            sb.append(", ");
        
        sb.append(Integer.toString(a[i]));

        int rangeI = i;
        while (a[i] != Integer.MAX_VALUE && a[i] + 1 == a[i + 1]) 
            ++i;
        
        if (i != rangeI) 
            sb.append('~').append(Integer.toString(a[i]));
        
    

return sb.toString();

您可以使用append(a[i]),因为有一个重载方法在内部进行转换。为了清楚起见。

逗号分隔很标准。

并且为每个元素检查其增加的追随者的范围。

由于 java 忽略整数溢出,因此检查 MAX_VALUE。

【讨论】:

【参考方案5】:

将整数转换为字符串并使用 Java 的 StringBuilder 类附加它们:https://www.geeksforgeeks.org/stringbuilder-class-in-java-with-examples/

StringBuilder str = new StringBuilder();

并使用以下方法附加数字和“~”(波浪号)符号:

str.append(start_number);
str.append('~');
str.append(end_number);

【讨论】:

真正的问题是如何提取范围,而不是如何将 int 转换为字符串。 然后用for循环迭代并累积范围,直到达到它与下一个之间的范围大于1的数字,然后附加StringBuilder并重置新的范围。跨度> 这个答案没有错,但它只关注任务中可能最小和最简单的部分。它只是不完整 正如之前所说的那样,他似乎在制作字符串本身时遇到了麻烦。经过编辑和澄清,他当然想要范围本身。 我明白了,那完全公平。不幸的是,OP 改变了他们的问题,使您的答案看起来不太好。

以上是关于从数组中识别和收集连续范围[重复]的主要内容,如果未能解决你的问题,请参考以下文章

java基础

最大和连续子数组(面试问题)[重复]

如何以最小的复杂性识别数组中的重复数字?

识别和删除数组中重复项的最有效方法是啥?

Python帮助-如何从一维数组中提取特定范围的值? [重复]

剑指offer之数组