【问题标题】:Array left rotation in JavaJava中的数组左旋转
【发布时间】:2020-03-26 23:06:26
【问题描述】:

给定一个整数数组和一个数字,我需要对数组执行左旋转并返回更新后的数组以打印为单行以空格分隔的整数。 我通过了 7/9 检查,但一些大型阵列由于超时而失败。

时间必须是

static int[] rotLeft(int[] a, int d) {
       int x = 0;
       while (x != d) {
           int first = a[0];
           int last = a[a.length - 1];
           for (int i = 0; i < a.length - 1; i++) {
               a[i] = a[i + 1];
               if (i == a.length - 2)
                   a[a.length - 2] = last;
               a[a.length - 1] = first;
           }
           x++;
       }
       return a;
   }

【问题讨论】:

标签: java arrays


【解决方案1】:

你一次只旋转一个位置,速度很慢,最好将元素移动到适当的位置,例如:

static int[] rotLeft(int[] a, int d) {
    if (d == 0 || a == null || a.length == 0) {
        return a;
    }

    int[] b = new int[a.length];
    for (int i = 0; i < a.length; i++) {
        b[i] = a[(i + d) % a.length];
    }
    return b;
}

【讨论】:

  • 你能解释一下这条线吗? b[i] = a[(i + d) % a.length];
  • @JJKam 它从旧数组中获取元素并将其转移到新数组中,只要i+d 可以超出数组长度,它就有% 环绕它
【解决方案2】:

您可以对这个问题应用两件事来改进运行时间。

  1. 确保d 小于a.length。如果d 大于a.length,那么您正在将元素旋转超过其原始位置并浪费循环。一个简单的方法是使用模赋值运算符(即d %= a.length,相当于d = d % a.length)。

  2. 元素应该移动我们旋转的整个距离,而不是每次移动一个空格。这允许我们执行整个操作是 1 个动作,而不是在d 动作中。

应用这两个原则会给我们以下代码:

static int[] rotLeft(int[] a, int d) {
  if (d < 0) {
    d = a.length - (-d % a.length);
  }

  d %= a.length;

  if (d == 0) {
    return a;
  }

  int first = a[0];

  int i = 0;
  int position = 0;

  while (i < a.length) {
    a[position] = a[(position + d) % a.length];

    position = (position + d) % a.length;
    i++;
  }

  a[a.length - d] = first;

  return a;
}

【讨论】:

    【解决方案3】:

    您进行多次传递,每次旋转一个位置,这会使您的程序变慢。 至少有 3 种方法可以改进您的计划:

    第三个选项执行就地旋转。

    可能值得对所有 3 个进行微基准测试以比较不同输入大小的速度

    【讨论】:

      【解决方案4】:

      当然,您可以像在示例代码中那样重复旋转d 次。但是,如果您计算班次并一次性完成,会更快:

      导入静态 java.lang.System.*;

      static int[] rotLeft( int[] a, int d ) 
      {
          var len = a.length;
          var shift = d % len;
          var buffer = new int[len]; 
          arraycopy( a, shift, buffer, 0, len - shift );
          arraycopy( a, 0, buffer, len - shift, shift );
          arraycopy( buffer, 0, a, 0, len );
          return a;
      }
      

      当然,您可以使用for-loop 来代替System.arraycopy()。如果不是强制返回a,可以省略第三次调用arraycopy(),改为返回buffer;这也会使原始数组保持不变。

      要输出数组,试试这个:

      var joiner = new StringJoiner( " " );
      for( var v : a ) joiner.add( Integer.toString( v ) );
      System.out.println( joiner.toString() );
      

      【讨论】:

      • 1) 你的缓冲区比需要的大,它只需要是shift,而不是len。参见例如my answer。 --- 2) 如果您期望 d 的值过大,为什么不期望负值呢?
      • @Andreas:负数将是右移,但规范中没有“超大数字”:它说“左旋转 d 次”。 d % a.length 一词只是一种优化。关于缓冲区:为什么要在就地修改输入数组后返回它?该方法要么返回void,要么它应该返回缓冲区——但最后,我很懒。但如果真的需要就地轮换,你是对的,大小为shift 的缓冲区就可以完成这项工作。
      • 1) d % a.length 不是优化,不适用于此答案的代码。如果d &gt; a.length,则需要防止IndexOutOfBoundsException。 --- 2) “我为什么要返回输入数组” 但是你正在这样做。 “它应该返回缓冲区” 但你没有这样做。而且由于您正在将所有buffer 复制回a,要返回它,缓冲区比它需要的要大,因为第二个arraycopy 可能在@ 中就位987654343@,我就是这么说的。
      【解决方案5】:

      为了获得更好的性能,您应该使用内置的数组复制方法。

      如果您必须更新现有数组,就像您的代码所做的那样,我建议您这样做:

      static int[] rotLeft(int[] a, int d) {
          if (a == null || a.length <= 1)
              return a; // nothing to rotate
          int shift = (d % a.length + a.length) % a.length; // normalize d
          if (shift == 0)
              return a; // no or full rotation(s)
          int[] t = Arrays.copyOfRange(a, 0, shift);
          System.arraycopy(a, shift, a, 0, a.length - shift);
          System.arraycopy(t, 0, a, a.length - shift, shift);
          return a;
      }
      

      如果返回的数组必须不同,就像其他答案一样,我会这样做:

      static int[] rotLeft(int[] a, int d) {
          if (a == null || a.length <= 1)
              return Arrays.copyOf(a, a.length); // nothing to rotate
          int shift = (d % a.length + a.length) % a.length; // normalize d
          if (shift == 0)
              return Arrays.copyOf(a, a.length); // no or full rotation(s)
          int[] t = new int[a.length];
          System.arraycopy(a, shift, t, 0, a.length - shift);
          System.arraycopy(a, 0, t, a.length - shift, shift);
          return t;
      }
      

      上述两种解决方案都允许d 超过数组的大小,即不只进行一次完整旋转,并使用负值,即向右而不是向左旋转。

      测试

      System.out.println(Arrays.toString(rotLeft(new int[] { 1, 2, 3, 4, 5 }, 1)));
      System.out.println(Arrays.toString(rotLeft(new int[] { 1, 2, 3, 4, 5 }, 3)));
      System.out.println(Arrays.toString(rotLeft(new int[] { 1, 2, 3, 4, 5 }, 5)));
      System.out.println(Arrays.toString(rotLeft(new int[] { 1, 2, 3, 4, 5 }, 7)));
      System.out.println(Arrays.toString(rotLeft(new int[] { 1, 2, 3, 4, 5 }, -7)));
      

      输出

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

      【讨论】:

        【解决方案6】:

        您不必一一进行旋转。如果旋转d,则索引i 处的元素在i &gt;= d 时移动到索引i - d,在i &lt; d 时移动到索引N + i - d。有意义吗?

        int[] result = new int[a.length]
        for (int i = 0; i < a.length; i++) {
            if (i < d) {
                result[a.length + i - d] = a[i];
            } else {
                result[i - d] = a[i];
            }
        }
        return result;
        

        要处理 d >= a.length 的情况,您可以添加 d = d % a.length 作为预处理步骤。

        【讨论】:

        • 这个非常简洁准确。您所说的“逐个轮换”是什么意思?你不也是这样吗?
        • 您的第一个实现逐个计算数组的“d”个完整旋转。
        • 您是如何设法避免出现异常的?例如。如果 a.length=3、i=0 和 d=9?结果将是-6。我的测试通过了,不知道你是怎么逃出来的?
        • 啊,这种情况没有涵盖...很可能您的测试没有使用这种情况。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-17
        • 1970-01-01
        • 1970-01-01
        • 2014-12-23
        相关资源
        最近更新 更多