检查整数序列是否在增加

时间:2017-02-22 20:34:40

标签: c#

我只是部分地解决了以下问题。

给定一系列整数,检查是否可以通过从中删除不超过一个元素来获得严格增加的序列。

实施例

sequence = [1, 3, 2, 1]
almostIncreasingSequence(sequence) = false

sequence = [1, 3, 2]
almostIncreasingSequence(sequence) = true

我的代码只传递了一些例子:

bool almostIncreasingSequence(int[] sequence) {
   int seqIncreasing = 0;
    if (sequence.Length == 1) return true;
    for (int i = 0;i < sequence.Length-2;i++)
    {
        if ((sequence[i] == sequence[++i]+1)||(sequence[i] == sequence[++i])) 
        {
            seqIncreasing++; 
        } 
    } 
    return ((seqIncreasing == sequence.Length) || (--seqIncreasing == sequence.Length));
}

失败的例子:

Input:
    sequence: [1, 3, 2]
Output:
    false
Expected Output:
    true

Input:
    sequence: [10, 1, 2, 3, 4, 5]
Output:
    false
Expected Output:
    true

Input:
    sequence: [0, -2, 5, 6]
Output:
    false
Expected Output:
    true

Input:
    sequence: [1, 1]
Output:
    false
Expected Output:
    true

5 个答案:

答案 0 :(得分:9)

基于LINQ的答案很好,并且很好地表达了基本问题。它易于阅读和理解,并直接解决问题。但是,它确实存在需要为原始元素生成新序列的问题。随着序列变得越来越长,这变得更加昂贵并最终变得难以处理。

它需要使用Skip()Take()并没有帮助,这本身会增加处理原始序列的开销。

另一种方法是扫描序列一次,但是跟踪是否已经尝试删除以及何时找到无序元素,a)如果已经删除则立即返回false发现,和b)在确定序列时不包括已删除的元素。

您尝试的代码几乎完成了此操作。这是一个有效的版本:

static bool almostIncreasingSequence(int[] sequence)
{
    bool foundOne = false;

    for (int i = -1, j = 0, k = 1; k < sequence.Length; k++)
    {
        bool deleteCurrent = false;

        if (sequence[j] >= sequence[k])
        {
            if (foundOne)
            {
                return false;
            }
            foundOne = true;

            if (k > 1 && sequence[i] >= sequence[k])
            {
                deleteCurrent = true;
            }
        }

        if (!foundOne)
        {
            i = j;
        }

        if (!deleteCurrent)
        {
            j = k;
        }
    }

    return true;
}

注意:我原本以为你的尝试可以通过微小的改变来解决。但最终,事实证明它必须与我写的通用实现基本相同(特别是一旦我修复了那个......见下文)。唯一的实质差异实际上只是使用数组还是通用IEnumerable<T>

对于笑话,我写了另一种基于LINQ的解决方案的方法,因为它适用于任何序列,而不仅仅是数组。我也使它成为通用的(尽管有类型实现IComparable<T>的约束)。看起来像这样:

static bool almostIncreasingSequence<T>(IEnumerable<T> sequence) where T : IComparable<T>
{
    bool foundOne = false;
    int i = 0;
    T previous = default(T), previousPrevious = default(T);

    foreach (T t in sequence)
    {
        bool deleteCurrent = false;

        if (i > 0)
        {
            if (previous.CompareTo(t) >= 0)
            {
                if (foundOne)
                {
                    return false;
                }

                // So, which one do we delete? If the element before the previous
                // one is in sequence with the current element, delete the previous
                // element. If it's out of sequence with the current element, delete
                // the current element. If we don't have a previous previous element,
                // delete the previous one.

                if (i > 1 && previousPrevious.CompareTo(t) >= 0)
                {
                    deleteCurrent = true;
                }

                foundOne = true;
            }
        }

        if (!foundOne)
        {
            previousPrevious = previous;
        }

        if (!deleteCurrent)
        {
            previous = t;
        }
        i++;
    }

    return true;
}

当然,如果您愿意将原始序列复制到临时数组中,如果它不是一个,那么您可以轻松地使基于数组的版本通用,这将使代码更简单但仍然通用。这取决于你的优先事项。

<强>附录:

LINQ方法和线性方法(例如我的上面)之间的基本性能差异是显而易见的,但我很好奇并希望量化这种差异。所以我使用随机生成的序列进行了一些测试,以大致了解差异。

我执行了两个版本的测试:第一个,我运行了1000个试验的循环,其中序列可以是10到100个元素之间的任何长度;第二次,有10,000次试验,序列长度在100到1000之间。我执行了第二个版本,因为在我的笔记本电脑上,1000个试验的完整测试,在不到1/20秒的时间内完成了较短的序列,这对我来说对结果有效性的信心太短了。

对于第一个版本,代码花费大约1ms调用检查的线性方法,并且大约30ms调用LINQ方法,速度差异为30倍。将试验次数增加到10,000次证实了结果;对于每种方法,时间几乎完全缩放10倍,保持相差30倍。

对于第二个版本,差异更接近 400 x。线性版本花了大约0.07秒,而LINQ版本需要30秒。

正如预期的那样,序列越长,差异越大。对于非常短的序列,不仅代码不太可能在序列检查逻辑中花费太多时间,线性和LINQ方法之间的差异将相对较小。但随着序列变得越来越长,LINQ版本的差异将导致性能非常差,而线性版本仍然表现出色。

LINQ版本非常可读且简洁。因此,在输入总是相对较短的情况下(最多只有十几个元素),我会使用LINQ版本。但是如果我希望用比这更长的数据定期执行这个测试,我会避免LINQ并坚持使用更有效的线性方法。


关于随机生成的序列的注释:我编写了代码以生成所需长度的非负数的单调递增序列,然后在0和2(包括)之间插入值为{的新元素{1}}或int.MinValue(也为每次插入随机选择)。通过这种方式,三分之一的测试涉及非常有效的序列,第三部分涉及需要找到正确的单个元素去除的序列,第三部分无效(即不符合可以单调增加的要求)删除最多一个元素。)

答案 1 :(得分:1)

更新:修复了与使用Except生成子序列的方式相关的错误。显而易见的问题是,当原始序列包含重复项时产生的子序列可能是错误的;可能会删除重复项目的所有位置。

这个问题似乎看似简单,但你很容易陷入循环中,ifs和elses永远不会完全正确。

解决这个问题的最好方法是退后一步,了解你所要求的条件是什么意思。一个几乎严格增加的序列是这样的,即在创建的所有可能的子序列中删除一个单项,至少有一个必须严格增加。

好吧,这似乎是合理的推理,并且易于实现,所以我们这样做:

首先,一个简单的方法告诉我们给定的序列是否严格增加:

private static bool IsStrictlyIncreasing<T>(this IEnumerable<T> sequence)
    where T : IComparable<T>
{
    using (var e = sequence.GetEnumerator())
    {
        if (!e.MoveNext())
            return true;

        var previous = e.Current;

        while (e.MoveNext())
        {
            if (e.Current.CompareTo(previous) <= 0)
                return false;

            previous = e.Current;
        }

        return true;
    }
}

现在我们需要一个辅助方法来生成删除一个项目的所有可能的子序列(如上所述,如果Except具有值相等语义,只使用T将不会删除它):

private static IEnumerable<IEnumerable<T>> GenerateSubsequences<T>(
    this IEnumerable<T> sequence)
    => Enumerable.Range(0, sequence.Count())
                 .Select(i => sequence.Take(i)
                                      .Concat(sequence.Skip(i + 1)))

现在,我们只需要检查所有子序列并找到至少一个严格增加的子序列:

public static bool IsAlmostStrictlyIncreasing<T>(this IEnumerable<T> sequence)
    where T : IComparable<T>
    => sequence.GenerateSubsequences()
               .Any(s => s.IsStrictlyIncreasing());

应该这样做。

答案 2 :(得分:1)

我自己已经解决了CodeSignal challenge using C#,我可以告诉你我是怎么做到的。

首先,一个辅助方法处理决定何时从序列中删除元素的逻辑:

private static bool removeElement(IEnumerable<int> sequence, int i) {
    // This method handles the logic for determining whether to remove an element from a sequence of integers.
    // Initialize the return variable and declare some useful element aliases.
    bool removeElement = false;
    int c = sequence.ElementAt(i), p = sequence.ElementAtOrDefault(i - 1), n = sequence.ElementAtOrDefault(i + 1);

    // Remove the first element if and only if it is greater than or equal to the next element.
    if      (i == 0)                      removeElement = (c >= n);
    // Remove the last element if and only if it is less than or equal to the previous element.
    else if (i == (sequence.Count() - 1)) removeElement = (c <= p);
    // Removal logic for an element somewhere in the middle of the sequence:
    else {
        // If the current element is greater than the previous element...
        // ...and the current element is less than the next element, then do not remove the current element.
        if (c > p && c < n) removeElement = false;
        // If the current element is greater than or equal to the next element, then it might need to be removed.
        else if (c > p && c >= n) {
            removeElement = true;

            // Handle edge case for test 19.
            // If the current element is the next-to-last element...
            // ...and the only reason it's being considered for removal is because it is less than the last element...
            // ...then skip it and remove the last element instead.
            if (i == (sequence.Count() - 2)) removeElement = false;

            // Handle edge case for test 16.
            // If the current element occurs before the next-to-last element...
            if (i < (sequence.Count() - 2))
                // ...and both the current and next elements are less than the following element...
                // ...then skip the current element and remove the next one instead.
                if (n < sequence.ElementAt(i + 2) && c < sequence.ElementAt(i + 2)) removeElement = false;
        // Otherwise, remove the current element.
        } else removeElement = true;
    }

    return removeElement;
}

然后,我编写了main方法的两个版本:一个使用LINQ,一个不使用。

LINQ版本:

bool almostIncreasingSequence(int[] sequence) {
    // Eliminate the most trivial cases first.
    if      (sequence.Length <= 2)                                        return true;
    else if (sequence.SequenceEqual(sequence.Distinct().OrderBy(x => x))) return true;
    else {
        // Get the index of the first element that should be removed from the sequence.
        int index = Enumerable.Range(0, sequence.Length).First(x => removeElement(sequence, x));
        // Remove that element from the sequence.
        sequence = sequence.Where((x, i) => i != index).ToArray();
    }

    // Return whether or not the remaining sequence is strictly increasing.
    return sequence.SequenceEqual(sequence.Distinct().OrderBy(x => x));
}

非LINQ版本:

bool almostIncreasingSequence(int[] sequence) {
    // Eliminate the most trivial cases.
    if (sequence.Length <= 2) return true;
    // Make a copy of the input array in the form of a List collection.
    var initSequence = new List<int>(sequence);
    // Iterate through the List.
    for (int i = 0; i < initSequence.Count; i++) {
        // If the current element needs to be removed from the List, remove it.
        if (removeElement(initSequence, i)) {
            initSequence.RemoveAt(i);
            // Now the entire sequence after the first removal must be strictly increasing.
            // If this is not the case, return false.
            for (int j = i; j < initSequence.Count; j++) {
                if (removeElement(initSequence, j)) return false;
            }

            break;
        }
    }

    return true;
}

两个版本都通过了所有提供的测试用例:

38/38 tests passed.
Sample tests: 19/19
Hidden tests: 19/19
Score: 300/300

答案 3 :(得分:0)

这是我的版本。它与Peter Duniho's首先solution相似。

static bool AlmostIncreasingSequence(int[] sequence)
{
    int problemIndex = -1;
    for (int i = 0; i < sequence.Length - 1; i++)
    {
        if (sequence[i] < sequence[i + 1])
            continue; // The elements i and i + 1 are in order
        if (problemIndex != -1)
            return false; // The sequence has more than one problems, so it cannot be fixed
        problemIndex = i; // This is the first problem found so far
    }
    if (problemIndex == -1)
        return true; // The sequence has no problems
    if (problemIndex == 0)
        return true; // The sequence can be fixed by removing the first element
    if (problemIndex == sequence.Length - 2)
        return true; // The sequence can be fixed by removing the last element
    if (sequence[problemIndex - 1] < sequence[problemIndex + 1])
        return true; // The sequence can be fixed by removing the (problemIndex) element
    if (sequence[problemIndex] < sequence[problemIndex + 2])
        return true; // The sequence can be fixed by removing the (problemIndex + 1) element
    return false; // The sequence cannot be fixed
}

答案 4 :(得分:-2)

感谢陌生人的帮助!为了简单起见,我能够通过删除所有增量/减量运算符来简化我的逻辑,从而使我的所有测试都先通过。如果迭代器元素大于或等于下一个元素,则递增我的erasedElements变量。如果该变量为1,我们知道我们只删除了一个元素并满足了增加的序列。

bool almostIncreasingSequence(int[] sequence) {
    int erasedElements = 0;
    for (int i = 0; i < sequence.Length-1; i++)
    {
        if(sequence[i] >= sequence[i+1])
        {
            erasedElements += 1;
        }
    }

    Console.Write(erasedElements);
    return (erasedElements == 1);   
}

通过了以下所有序列:

[1,3,2,1]

[1,3,2]

[1,4,10,4,2]

[10,1,2,3,4,5]

[1,1,1,2,3]

[0,-2,5,6]

[1,1]