是否有比天真的更好的解决方案来按数组顺序查找错误?

时间:2019-04-23 09:24:55

标签: arrays data-structures

在一次采访中,我被问到以下问题,我得到了一个整数数组,我需要返回该数组中整数的数量,这些整数不在索引中(如果对数组进行排序的话)。 / p>

例如对于数组[1,1,3,4,1],我需要返回3,因为最后三个整数(3,4,1)不在索引中,如果数组是排序为[1,1,1,3,4]。 对于[5,4,3,2,1],我需要返回4,([1,2,3,4,5])。

我只想出了一个简单的解决方案,即复制原始数组,然后对其进行排序并比较两个数组,然后计算每个索引中不同元素的数量。 这需要O(nlogn)时间和O(n)空间。

在我看来,也许我错过了一些东西,也许会有更好的解决方案。有更好的解决方案吗?

1 个答案:

答案 0 :(得分:0)

天真意味着缺乏智慧/判断力;相反,在压力下获得简单,优雅的解决方案就相反。

可能还有改进的余地,但是如果存在,它就不太可能是“显而易见的”,而且可能也不简单。 (除了下面的实验,我的实验没有证明任何东西;-)

思考
对于要放置的项目,其左侧(在数组中的前面)较大项目的数量必须等于其右侧较小项目的数量。

很明显,无需第二个数组即可确定。下面的代码具有更好的空间复杂度O(1),但时间复杂度更差O(n ^ 2)。原谅局促的风格和递归,但我试图保持简洁。

using System;
using System.Text;

namespace ConsoleApp2
{
  static class Program
  {
    private static int itemVisits;
    static string PrintArray(int[] arr)
    {
      var sb = new StringBuilder("[");
      foreach (var i in arr)
        sb.AppendFormat("{0}, ", i);
      sb.Length -= 2;// kill last comma and space
      sb.Append("]");
      return sb.ToString();
    }

    static void Test(int[] arr)
    {
      Console.WriteLine($"{PrintArray(arr)}...");
      Console.WriteLine($"  method1: {ItemsOutOfPlace1(arr)} elements out of place, {itemVisits} item visits");
//      Console.WriteLine($"  method2: {ItemsOutOfPlace2(arr)} elements out of place, {itemVisits} item visits");
    }

    static int ItemsOutOfPlace1(int[] arr)
    {
      int countOutOfPlace = 0;
      itemVisits = 0;
      for (int i = 0; i < arr.Length; i++)
        countOutOfPlace += NeedToMove(arr[i], i) != 0 ? 1 : 0;
      return countOutOfPlace;

//  In place functions follow...
      int NeedToMove(int val, int i) { return CountRightLessThan(val, i + 1) - CountLeftGreaterThan(val, i - 1); }

      int CountRightLessThan(int val, int i)
      {
        if (i >= arr.Length) return 0;
        itemVisits++;
        return (arr[i] < val ? 1 : 0) + CountRightLessThan(val, i + 1);
      }

      int CountLeftGreaterThan(int val, int i)
      {
        if (i < 0) return 0;
        itemVisits++;
        return (arr[i] > val ? 1 : 0) + CountLeftGreaterThan(val, i - 1);
      }
    }

    static void Main(string[] args)
    {
      Test(new []{ 1,1,3,4,1 });
      Test(new []{ 5, 4, 3, 2, 1 });
      Test(new []{ 2,3,4,1,6,7,8,9,5 });
      Test(new []{ 30,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,1 });

      Console.ReadKey();
    }
  }
}

生产

[1, 1, 3, 4, 1]...
  method1: 3 elements out of place, 20 item visits
[5, 4, 3, 2, 1]...
  method1: 4 elements out of place, 20 item visits
[2, 3, 4, 1, 6, 7, 8, 9, 5]...
  method1: 9 elements out of place, 72 item visits
[30, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 1]...
  method1: 2 elements out of place, 870 item visits

严格地是T(n(n-1))。我试图对此进行改进(您可能会发现对ItemsOutOfPlace2()的注释引用),但没有排序,仅实现了最佳情况时间O(nlog(n)),并且需要回到O(n)空间,并且需要更多,更简洁的代码。

结论

  1. 没有形式分析,就没有简单/明显的方法来改善您的空间和时间复杂性。 (如果存在,它可能必须增加代码的复杂性)。
  2. 非正式分析说,每个项目都必须“定位”,并且如果您能以比O(nlogn)时间更便宜的方式进行任意数据处理,那么您将发现一个新的最坏情况优于O (nlogn)排序算法。
  3. 我的“天真”解决方案仅在空间和时间上有所改进。除非确实无法提供数组分配,否则最好使用您的数组。而且,您可以使用任何排序算法,因此,如果您碰巧知道数据经常被排序,则可以使用利用这种排序的排序(例如Timsort或Block排序),这种输入的时间复杂度接近O(n)。 / li>

好问题