【问题标题】:Array movement from a1,..,an,b1,..,bn to a1,b1,..,an,bn数组从 a1,..,an,b1,..,bn 移动到 a1,b1,..,an,bn
【发布时间】:2013-08-05 07:27:56
【问题描述】:

今天遇到一个让我很困惑的问题

问题

我有一个数组,就像:arr[a1, a2, a3....an, b1, b2, b3.....bn],如何移动数组的元素将它转移到arr[a1, b1, a2, b2......an,bn],你应该就地移动(space complexity should be constant)。

我尽力想了想,得到了一个像冒泡排序一样丑陋的算法:

b1 moves forward by n - 1;
b2 moves forward by n - 2;
.
.
bn-1 moves forward by 1;

但是时间复杂度是O(n2),谁能给我一个更好的算法? 我找到了另一种更好的方法,就像快速排序一样:

First we swap the element from a(n/2) to a(n) with the elements from b1 to b(n/2);now we get two independent sub problems,So we can solve it by recursion.
T(n) = 2T(n/2) + O(n) 
the time complexity is O(nlgn)

这里是完整的代码:

void swapArray(int *arr, int left, int right)
{
    int mid = (left + right) >> 1;
    int temp = mid + 1;
    while(left <= mid)
    {
        swap(arr[left++], arr[temp++]);
    }
}
void arrayMove(int *arr, int lb, int le, int rb, int re)
{
    if(le - lb <= 0 || re - rb <= 0)
        return;
    int mid = (lb + le + 1) >> 1;
    int len = le - mid;
    if(rb + len >= re)
    {
        swapArray(arr, mid + 1, rb);
    }
    else
    {
        swapArray(arr, mid, rb + len);
    }
    arrayMove(arr, lb, mid - 1, mid, le);
    arrayMove(arr, rb, rb + len, rb + 1 + len, re);
}

【问题讨论】:

标签: arrays algorithm matrix


【解决方案1】:

在涉猎和实验/磕磕绊绊之后,我想我开始明白了,尽管数学对我来说仍然很难。我认为它是这样的:

确定转置的排列周期(这可以在实际数据传输期间或之前完成)。公式to = 2*from mod (M*N - 1), where M = 2, N = array length / 2 可用于查找索引目标(排列)。 (我减少了这个问题的公式,因为我们知道 M = 2。)访问索引的标记可以帮助确定下一个循环的开始(从技术上讲,可以使用循环计算而不是位集作为标记,只保留内存中的下一个循环开始)。一个临时变量保存从起始地址到循环结束的数据。

总而言之,这可能意味着两个临时变量、循环计算和每个数组元素一个原地移动。

例如:

arr          = 0,1,2,3,4,5,6,7,8,9
destinations:  0,2,4,6,8,1,3,5,7,9

start = 1, tmp = arr[1]    

cycle start
5->1, 7->5, 8->7, 4->8, 2->4, tmp->2
cycle end

not visited - 3

start = 3, tmp = arr[3]

cycle start
6->3, tmp->6
cycle end

Transposition complete.

有什么问题吗?
随时询问,请访问http://en.wikipedia.org/wiki/In-place_matrix_transposition

【讨论】:

  • 我正在考虑类似的解决方案,但无法解决恒定空间复杂度的问题。此解决方案还具有线性空间复杂度,因为如果访问过条目,则必须保存。
  • 这确实比将所有内容复制到不同的数组占用更少的内存,因为对于访问的数组,每个元素只需要一个额外的位。但正如 Nico 所说,它仍然是线性空间复杂度。
  • 首先我们将a(n/2) 到a(n) 的元素与b1 到b(n/2) 的元素交换;现在我们得到两个独立的子问题,所以我们可以解决它通过递归。 T(n) = 2T(n/2) + O(n) 时间复杂度为 O(nlgn)
  • @NicoSchertler 我认为这里的第二个示例使用没有位集的循环,尽管我不明白rosettacode.org/wiki/Matrix_transposition#C
  • 链接示例通过再次执行循环来确定元素是否已被访问。如果循环中有一个元素在当前元素之前,我们已经访问了当前元素。在我看来不是最优的;我肯定更喜欢位标志。但是,虽然它可能是最好的解决方案之一,但它具有线性空间复杂度,因此无法解决这个问题。
【解决方案2】:

这是一个用 Python 编写的简短代码:

n = 4
a = ['a1', 'a2', 'a3', 'a4', 'b1', 'b2', 'b3', 'b4']
result = a[:n]
for index in range(n):
    result.insert(index*2 + 1, a[n+index])
print result  #  prints ['a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4']

【讨论】:

  • insert 仍在Ο(n)中。通过 n/2 次操作,您的算法也在 Ο(n^2) 中。
  • 空间复杂度应该是恒定的
  • @interjay 因为大多数算法都需要内存空间来进行诸如*2 + 1 之类的计算,人们如何确定它何时不再“就地”? (真诚,不讽刺)
  • @groovy:就地意味着除了原始数据结构外,它只能使用 O(1) 额外的内存。见en.wikipedia.org/wiki/In-place_algorithm。这意味着执行简单计算没有问题,但是您不能在此答案中分配像result 这样的额外数据结构。
  • 这种方法可以更快地工作!首先我们将元素从a(n/2)交换到a(n)与从b1到b(n/2)的元素;现在我们得到两个独立的子问题,所以我们可以通过递归来解决。 T(n) = 2T(n/2) + O(n) 时间复杂度为 O(nlgn)
【解决方案3】:

也许这不是最好的方法,因为它不能直接回答您的问题,但是您为什么不简单地将元素的新索引映射到元素的旧索引?

old_array: [a1, a2, a3....an, b1, b2, b3.....bn]
=>
lookup: [0 n 1 n+1 2 n+2 ... n-1 2n-1]
=>
[a1 b1 a2 b2 ... an bn]

当你想从索引中抓取一个元素时

Get(index)
 return old_array[lookup[index]]
End

Get(2) => old_array[lookup[2]] => old_array[1] => a2 | a2 is at index 2

【讨论】:

  • 空间复杂度应该是恒定的
【解决方案4】:

“从 a1,..,an,b1,..,bn 到 a1,b1,..,an,bn 的数组移动”的工作 Java 代码

private static void myTrans(int[] m) {
    int n = (m.length - 1);

    int i = 1;
    for (int start = 1; start < n; start++) {
        int temp = m[start];
        m[start] = m[n / 2 + i];

        for (int j = (n / 2 + i); j > start; j--) {
            m[j] = m[j - 1];
        }

        start++;
        m[start] = temp;
        printArray(m);
        i++;
    }
}

【讨论】:

    【解决方案5】:
    public void shuffle(char[] arr, int left, int right){
        //center
        int c = (left+right)/2;
        int q = 1 + (left+c)/2;
        for(int k=1,i=q;i<=c;i++,k++){
             //Swap elements
             int temp = arr[i];
             arr[i] = arr[c+k];
             arr[c+k] = temp;
        }
        shuffle(arr,left,c);
        shuffle(arr,c+1,right);
    }
    

    这将围绕中心随机播放:

    1. a1a2a3a4b1b2b3b4 -> a1a2b1b2a3a4b3b4
    2. a1a2b1b2 -> a1b1a2b2
    3. a3a4b3b4 -> a3b3a4b4

    此解决方案的复杂性将是 nlogn

    【讨论】:

      【解决方案6】:

      请检查我尝试的 Java 就地排序解决方案:

      时间复杂度:O(N),其中 N 是数组的长度。 空间复杂度:O(1)

      public static void sortSpecialArray(String[] array) {
          sortSpecialArrayUtil(array);
          sortSpecialArrayUtil(array);
      }
      
      /**
       * Given an array with ['a1', 'a2', 'a3', ..., 'aN', 'b1', 'b2', 'b3', ...,'bN', 'c1'...'cN']
       * sort the array to be a1, b1, c1...
       *
       * O(N) time complexity (derived from O(N * K) below)
       * O(1) space complexity
       */
      private static void sortSpecialArrayUtil(String[] array) {
          // O(1) space complexity requires this function to sort the array in-place
          // this can be done using a pointer that keeps track of where we are
          // in the array. The idea is to swap the values in the array to get the element in the right place.
          int n = stripNumber(array[array.length - 1]);
          int charCount = array.length / n;
      
          // start the loop at index = 1, up until index = array.length - 2
          // ignore those two elements because they're already in the right place
          int lastIndex = array.length - 2;
          String element;
          for (int i = 1; i <= lastIndex; i++) {
              element = array[i];
              swap(array, i, getCorrectIndex(charCount, element));
          }
      }
      
      /**
       * Assume that array's element is in format of something like "a1", where there can only be one 'letter' before the number
       * extract number from the string
       *
       * This is done in time O(K), where K is the length of the String
       */
      private static int stripNumber(String str) {
          return Integer.parseInt(str.substring(1, str.length()));
      }
      
      private static int getCorrectIndex(int charCount, String element) {
          char c = Character.toLowerCase(element.charAt(0));
          int num = stripNumber(element);
      
          // the correct index is found by the following formula: index = charDistance + (charCount * (num - 1))
          int charDistance = c - 'a';
          return charDistance + (charCount * (num - 1));
      }
      
      private static void swap(String[] array, int fromIndex, int toIndex) {
          String temp = array[toIndex];
          array[toIndex] = array[fromIndex];
          array[fromIndex] = temp;
      }
      

      测试:

      @Test
      public void testSortArray() {
          String[] testCase = new String[] { "a1", "a2", "a3", "a4", "a5", "b1", "b2", "b3", "b4", "b5", "c1", "c2", "c3", "c4", "c5", "d1", "d2", "d3", "d4", "d5" };
          sortSpecialArray(testCase);
          for (String s : testCase) {
              System.out.print(s + " ");
          }
      }
      

      【讨论】:

        【解决方案7】:

        假设输入数组为 [a1,a2,a3,b1,b2,b3,c1,c2,c3],所以根据问题,我们希望输出为 [a1,b1,c1,a2,b2,c2,a3,b3,c3]。让我将数组表示为二维矩阵 每行表示 n 的组大小(在这种情况下 n 为 3)。

        Original array
        a1 a2 a3
        b1 b2 b3
        c1 c2 c3
        
        What we Want
        a1 b1 c1
        a2 b2 c2
        a3 b3 c3
        

        你有没有注意到什么。让我告诉你输出数组实际上是输入数组的转置,表示为二维矩阵,可以使用常量空间轻松完成。

        • 注意:您需要开发将原始数组分解为二维数组的逻辑。

        实现Code

        【讨论】:

        • 进行就地换位并不像您声称的那样“容易完成”。只有方阵比较容易,否则就很复杂了。您所做的只是重新提出问题而没有给出实际的解决方案。
        • @interjay 你在开玩笑吧!!!!我已经实现了这个问题,事实上这是亚马逊的在线测试问题之一。
        • 不,我不是在开玩笑。我打赌你的算法实际上并没有到位。如果是,请在此处发布。
        • 我想,你的实现不是in-place。您需要修改输入数组而不分配任何大小取决于n 的其他数组。这使它成为一个更加困难的问题。
        • @interjay 好吧,我明白了吗? stackoverflow.com/questions/18043778/…
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-19
        • 2018-08-03
        • 2023-03-15
        • 2018-08-18
        相关资源
        最近更新 更多