【问题标题】:C# How to optimize my code using stacks? (HackerRank "Equal Stacks" problem)C#如何使用堆栈优化我的代码? (HackerRank“等栈”问题)
【发布时间】:2021-09-08 17:03:37
【问题描述】:

我已经解决了这个问题,但是 11/30 测试用例因为“超出时间限制”而失败。我想知道是否有办法优化我的代码,以便解决方案通过所有测试用例。

问题的链接: https://www.hackerrank.com/challenges/equal-stacks/problem

我的方法: 正如问题的名称所暗示的那样,我想使用堆栈,即使方法中传递的参数是 List。 所以首先我通过以下方式将列表转换为堆栈:每个顶部元素表示堆栈的当前高度。因此,如果有高度为 1、5、10 的块,则堆栈中的元素将为 1、6、16。 我的算法是从堆栈中弹出最大元素,直到所有堆栈具有相同的高度。

这是我的代码:

public static int equalStacks(List<int> h1, List<int> h2, List<int> h3)
{
    var result = 0;
    if (IsEmpty(h1) || IsEmpty(h2) || IsEmpty(h3)) return result;
    
    var h1Stack = GetStack(h1);
    var h2Stack = GetStack(h2);
    var h3Stack = GetStack(h3);
    
    while (h1Stack.Any() && h2Stack.Any() && h3Stack.Any())
    {
        var h1Height = LocalPeek(h1Stack);
        var h2Height = LocalPeek(h2Stack);
        var h3Height = LocalPeek(h3Stack);
    
        if (h1Height == h2Height && h1Height == h3Height) return CountHeight(h1Stack);
        
        var max = Math.Max(h1Height, Math.Max(h2Height, h3Height));
        if (h1Height == max && h1Stack.Any()) h1Stack.Pop();
        if (h2Height == max && h2Stack.Any()) h2Stack.Pop();
        if (h3Height == max && h3Stack.Any()) h3Stack.Pop();
    }
    
    return 0;
}

static bool IsEmpty(List<int> stack) 
{
    return (!stack?.Any() ?? true);
}

static int LocalPeek(Stack<int> stack){
    if (stack.Any()) return stack.Peek();
    return 0;
}

static Stack<int> GetStack(List<int> numbers)
{
    var stack = new Stack<int>(numbers.Count);

    for (var i = numbers.Count - 1; i >= 0; --i)
    {
        stack.Push(numbers[i] + LocalPeek(stack));
    }

    return stack;
}

static int CountHeight(Stack<int> stack)
{
    return stack.Any() ? stack.Peek() : 0;
}

如果我们有长度列表:m,n,k 作为输入,n 是它们的最大值,那么我认为我的算法在 O(n) 中执行;老实说,我认为它没有任何问题。 如果我做错了什么,请告诉我,我该如何解决。

谢谢。

【问题讨论】:

  • .Any() 将打开一个枚举。我只会使用.Count&gt;0
  • 但我认为你真正想要的是线性扫描,从“底部”到“顶部”,跨越所有列表。记录所有圆柱体排列的每个高度,保留最后一个。
  • 感谢您的回复@JeremyLakeman。在使用 Any() 方面,我知道 Count 更有效。但它不会有太大变化,而且我认为 Any 更具人类可读性。但无论如何,感谢您指出这一点。关于从“底部”扫描到“顶部”的第二条评论。目前我从“顶部”扫描到“底部”。在某些情况下,您的方法会更有效,在其他情况下是我的。这在很大程度上取决于输入。
  • 我的意思是,您可以将问题视为递减每个列表中的最后一个元素。如果所有这些元素都为零,那么您已经找到了可能的解决方案。如果任何元素为零,则前进到下一个元素。如果元素用完了,请停止。
  • 如果你真的想使用堆栈,在解析每一行时更改 main 方法来填充它们。

标签: c# algorithm optimization stack


【解决方案1】:

我还建议不要使用堆栈,这是一种非 linq 方法,每个列表(一次解析)上的 O(n),而且它分配的数量不超过计算出的变量。

public static int EqualStacks(int[] h1, int[] h2, int[] h3)
{
   var a = new[] {h1, h2, h3};
   var i = new int[3];
   var c = new int[3];
   var last = 0;

   bool GetLowest(out int result)
   {
      result = -1;
      if (c[0] <= c[1] && c[0] <= c[2] && i[0] < a[0].Length-1) result = 0;
      else if (c[1] <= c[0] && c[1] <= c[2] && i[1] < a[1].Length - 1) result =  1;
      else if (i[2] < a[2].Length - 1) result = 2;
      else return false;
      return true;
   }

   while (true)
   {
      if (!GetLowest(out var index)) 
         return last;

      c[index] += a[index][i[index]];
      i[index]++;

      if (c[0] == c[1] && c[1] == c[2])
         last = c[0];

      // Console.WriteLine($"Current = {string.Join(", ", c)}, Index = {index}, Last = {last}");
   }

}

用法

var result = EqualStacks(
   new[] {1, 1, 1, 2, 3},
   new[] {2, 3, 4},
   new[] {1, 4, 1, 1});
Console.WriteLine(result);

结果

Current = 1, 0, 0, Index = 0, Last = 0
Current = 1, 2, 0, Index = 1, Last = 0
Current = 1, 2, 1, Index = 2, Last = 0
Current = 2, 2, 1, Index = 0, Last = 0
Current = 2, 2, 5, Index = 2, Last = 0
Current = 3, 2, 5, Index = 0, Last = 0
Current = 3, 5, 5, Index = 1, Last = 0
Current = 5, 5, 5, Index = 0, Last = 5
Current = 5, 5, 6, Index = 2, Last = 5
Result = 5, 5, 6, Last = 5
5

注意:虽然我的大脑说它有效,但我还没有彻底测试过。. 会出什么问题(不好意思地说)? ¯\_(ツ)_/¯,

您还可以内联GetLowest(),这样可以省去讨厌的堆栈调用和其他恶作剧,并且您可以摆脱数组或堆栈分配它们

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetLowest(out int result, int[] c, int[] i, int[][] a)

基准测试

Method N Mean Error StdDev Gen 0 Gen 1 Allocated
Mine 10 90.89 ns 0.581 ns 0.544 ns 0.0153 - 128 B
Yours 10 2,069.31 ns 13.881 ns 12.984 ns 0.0343 - 288 B
Dmitrys 10 304.75 ns 1.331 ns 1.245 ns 0.0143 - 120 B
Mine 1000 8,626.12 ns 108.481 ns 101.473 ns 0.0153 - 128 B
Yours 1000 43,196.78 ns 327.480 ns 290.302 ns 1.4038 - 12,168 B
Dmitrys 1000 20,563.90 ns 64.089 ns 53.517 ns - - 120 B
Mine 10000 202,904.36 ns 684.727 ns 571.778 ns - - 128 B
Yours 10000 402,362.80 ns 3,263.325 ns 3,052.517 ns 14.1602 1.4648 120,168 B
Dmitrys 10000 205,329.07 ns 1,014.000 ns 948.496 ns - - 120 B

代码

[SimpleJob(RuntimeMoniker.Net50)]

[MemoryDiagnoser]
public class WeirdStuff
{

   private int[] data1;
   private int[] data2;
   private int[] data3;
   private List<int> h1;
   private List<int> h2;
   private List<int> h3;
   [Params(10, 1000, 10000)] public int N;

   [GlobalSetup]
   public void Setup()
   {
      var r = new Random(42);
      data1 = Enumerable.Range(0, N).Select(x => r.Next(1, 5)).ToArray();
      data2 = Enumerable.Range(0, N).Select(x => r.Next(1, 5)).ToArray();
      data3 = Enumerable.Range(0, N).Select(x => r.Next(1, 5)).ToArray();
      h1 = new List<int>(data1);
      h2 = new List<int>(data2);
      h3 = new List<int>(data3);
   }

   [Benchmark]
   public int Mine()
   {
      var a = new[] {data1, data2, data3};
      var i = new int[3];
      var c = new int[3];
      var last = 0;


      while (true)
      {
         if (!GetLowest(out var index, c, i, a))
            return last;

         c[index] += a[index][i[index]];
         i[index]++;

         if (c[0] == c[1] && c[1] == c[2])
            last = c[0];

         //Console.WriteLine($"Current = {string.Join(", ", c)}, Index = {index}, Last = {last}");
      }
   }

   [MethodImpl(MethodImplOptions.AggressiveInlining)]
   private static bool GetLowest(out int result, int[] c, int[] i, int[][] a)
   {
      result = -1;
      if (c[0] <= c[1] && c[0] <= c[2] && i[0] < a[0].Length - 1) result = 0;
      else if (c[1] <= c[0] && c[1] <= c[2] && i[1] < a[1].Length - 1) result = 1;
      else if (i[2] < a[2].Length - 1) result = 2;
      else return false;
      return true;
   }

   [Benchmark]
   public int Yours()
   {
      var result = 0;
      if (IsEmpty(h1) || IsEmpty(h2) || IsEmpty(h3)) return result;

      var h1Stack = GetStack(h1);
      var h2Stack = GetStack(h2);
      var h3Stack = GetStack(h3);

      while (h1Stack.Any() && h2Stack.Any() && h3Stack.Any())
      {
         var h1Height = LocalPeek(h1Stack);
         var h2Height = LocalPeek(h2Stack);
         var h3Height = LocalPeek(h3Stack);

         if (h1Height == h2Height && h1Height == h3Height) return CountHeight(h1Stack);

         var max = Math.Max(h1Height, Math.Max(h2Height, h3Height));
         if (h1Height == max && h1Stack.Any()) h1Stack.Pop();
         if (h2Height == max && h2Stack.Any()) h2Stack.Pop();
         if (h3Height == max && h3Stack.Any()) h3Stack.Pop();
      }

      return 0;
   }

   bool IsEmpty(List<int> stack)
   {
      return (!stack?.Any() ?? true);
   }

   int LocalPeek(Stack<int> stack)
   {
      if (stack.Any()) return stack.Peek();
      return 0;
   }

   Stack<int> GetStack(List<int> numbers)
   {
      var stack = new Stack<int>(numbers.Count);

      for (var i = numbers.Count - 1; i >= 0; --i)
      {
         stack.Push(numbers[i] + LocalPeek(stack));
      }

      return stack;
   }

   static int CountHeight(Stack<int> stack)
   {
      return stack.Any() ? stack.Peek() : 0;
   }

   [Benchmark]
   public int Dmitrys()
   {
      if (h1.Count <= 0 || h2.Count <= 0 || h3.Count <= 0)
         return 0;

      int height1 = h1.Sum();
      int height2 = h2.Sum();
      int height3 = h3.Sum();

      int p1 = 0;
      int p2 = 0;
      int p3 = 0;

      while (height1 != height2 || height1 != height3)
      {
         if (height1 >= height2 && height1 >= height3)
            height1 -= h1[p1++];
         else if (height2 >= height1 && height2 >= height3)
            height2 -= h2[p2++];
         else
            height3 -= h3[p3++];
      }

      return height1;
   }
}

【讨论】:

  • 在结束问题之前,我将等待一段时间使用 Stack 数据结构的可能答案。但据我了解,没有这样的解决方案。 HackerRank 的问题可能有问题。他们不应该将其归入堆栈类别,因为使用堆栈简单的解决方案会用完时间,而您不得不使用数组/列表。
【解决方案2】:

【讨论】:

  • 我知道有多种方法可以解决这个问题,但我的目标不是在我的个人资料中解决这个问题。在那种情况下,我只是复制粘贴解决方案。我想出算法并自己实现它们。在这种情况下,我的算法在某些测试用例中失败了。我只是想知道是否有一种可能的方法可以通过我的算法通过小的优化来通过所有测试用例,或者我应该想出另一个解决方案。
  • @ConnorStoop 你可能是想说“评论”?=! ;)
【解决方案3】:

我建议从最高堆栈中移除圆柱体,而堆栈高度不同:

// Assuming that we don't have cylinders of negative height
public static int equalStacks(List<int> h1, List<int> h2, List<int> h3) {
  if (h1.Count <= 0 || h2.Count <= 0 || h3.Count <= 0)
    return 0;

  int height1 = h1.Sum();
  int height2 = h2.Sum();
  int height3 = h3.Sum();

  int p1 = 0;
  int p2 = 0;
  int p3 = 0;

  while (height1 != height2 || height1 != height3) {
    if (height1 >= height2 && height1 >= height3) 
      height1 -= h1[p1++];
    else if (height2 >= height1 && height2 >= height3) 
      height2 -= h2[p2++];
    else 
      height3 -= h3[p3++];
  }

  return height1; 
}

【讨论】:

  • 是的,这和我的代码差不多。除了这不使用堆栈数据结构并且一次删除一个。我的解决方案使用堆栈并删除 1 或 2 个块,如果它们都违反了预期的结果。但这仍然不能回答我的问题。我想没有进一步的改进可以做。
  • @David Oganov:它确实使用堆栈结构(堆栈实际上被模拟为列表);我会从列表中删除(弹出)项目,但移动第一个元素指针(p1p2p3)代替
  • 是的,它是模拟的,这肯定会更快,因为您直接使用列表。
猜你喜欢
  • 2019-01-24
  • 1970-01-01
  • 2015-12-18
  • 2018-04-10
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多