【问题标题】:Kth smallest Element and Kth Element?第K个最小元素和第K个元素?
【发布时间】:2019-08-13 17:36:11
【问题描述】:

我真的很困惑第 Kth 最小元素和 Kth 元素之间到底有什么区别。

第k个元素=第k个元素是一个数组=数组[k-1]

但是,第 k 个最小的元素是什么?我有一个作业问题,我需要编写一个算法来找到 2 个排序数组中的第 k 个最小元素。我不是来请你做我的功课的,你不需要给我任何算法或代码。我只想了解第 k 个最小元素是什么意思。第k个最小元素和第k个元素有什么区别。

我问这个的原因是: 我谷歌什么是第 k 个最小元素,网站之一:

For example if A = [10, 20, 40, 60] and B =[15, 35, 50, 70, 100] and K = 4 
then solution should be 35 because union of above arrays will be C = 
[10,15,20,35,40,50,60,70,100] and fourth smallest element is 35.

与数组的第k个元素完全相同。 A U B[k-1] 就是答案。

另一个例子是:

A = [3, 5, 9, 15, 27, 33, 35, 41, 57, 65]
B = [1, 16, 18, 42, 44, 46, 48, 50, 52, 54]

AUB = [1, 3, 5, 9, 15, 16, 18, 27, 33, 35, 41, 42, 44, 46, 48, 50, 52, 54, 57, 65]
and if k = 6
then AUB[6-1] = 16;
if k = 8
then AUB[8-1] = 27;

我说的对吗?是否有第 k 个最小元素不在 AUB[k-1] 处的例外情况?如果是的话,你能举个例子,并解释一下吗?

编辑:刚刚看到有人说第k小的元素是数组[k-1]升序排列。

我问了我的老师一个问题:

当我们谈论第k个元素时,它是在a[k]还是a[k-1]

他的回答是:

仔细阅读问题陈述。输出应该是 S U T 中 2n 个元素中第 k 个最小的元素。该输出不一定位于任一列表的索引 k 处。为什么应该这样?

我不明白。 输出不一定在任一列表的索引 k 处?这是什么意思?

【问题讨论】:

  • 所以是的,你需要 AUB[k-1],你的老师说的是 AUB[k-1] != A[k-1] && AUB[k-1] != B[ k-1] 是可能的
  • 列表[42, 0, 5]中最小元素的索引是多少?
  • @Prune 我礼貌地不同意,这是关于术语和交流的,这两个标签都有很好的用途(OP 没有使用它们,但对于新成员来说这很好)。并非软件开发中的所有内容都是编程,因此关闭这个问题恕我直言。
  • @MrDeal:感谢您的观点;我对成为唯一的关​​闭投票没有任何问题——这就是为什么它需要我一个以上的人。这是我的经验的边缘,所以我提出了这个问题。
  • @Prune 你说得对,它在边界上,我也很欣赏你对它的看法。我主要关心的是OP应该问的地方。 CS SE 可能会将他们发送到这里我会说,也许 CS Educators SE 考虑到问题是关于作业的措辞?

标签: java algorithm sorting


【解决方案1】:

正如您已经指出的那样,两个数组的联合将是您正在查看的内容。下面是一个示例:

S = [0,4,5,7]
T = [1,2,8,9]
then A = S v T = [0,1,2,4,5,7,8,9]

现在,当您查看此数组时,您会发现第 k 个元素位于索引 k-1。这是因为我们倾向于从 one 开始计数。所以我们说第一个元素,我们指的是索引0处的元素。

接下来,这也是您其他问题的答案。由于您有两个数组,第 k 个最小的数字将位于 A[k-1],但是您的老师的意思是在任一数组中,所以 ST 它们可能不在索引 k-1 .在上面的例子中,第 5 个最小的数字是 5,位于 A 的索引 4,但它是 SS[2] 中的第三个元素。

【讨论】:

    【解决方案2】:

    输出不一定在任一列表的索引 k 处?这是什么意思?

    这意味着你应该在不创建C,也就是AUB的情况下解决问题。

    您应该改为并行迭代两个数组,直到找到第 k 个最小的元素。

    伪逻辑:

    Ai = 0, Bi = 0
    Loop K-1 times:
        if A[Ai] < B[Bi] then Ai++ else Bi++
    kth smallest = min(A[Ai], B[Bi])
    

    例子

    A = [10, 20, 40, 60], B =[15, 35, 50, 70, 100], K = 4
    
    Ai = 0, Bi = 0: A[0] < B[0] so Ai++
    Ai = 1, Bi = 0: A[1] > B[0] so Bi++
    Ai = 1, Bi = 1: A[1] < B[1] so Ai++
    Ai = 2, Bi = 1: min(A[2], B[1]) = 35
    

    第 4 个最小值是 35,位于 B[1]

    如您所见,输出不在任一列表的索引 3 (=4-1) 处。


    第K个最小的元素和第K个元素?

    而且因为你从不创建一个组合列表,而是直接在两个不同的列表上工作,所以没有第 K 个元素,所以标题中提出的问题毫无意义。

    【讨论】:

    • 这个运行时间不是 log(n) 吧?但是,感谢您向我澄清老师试图说的话。另外,你的代码工作得很好,但我认为运行时间不是 log(n),但它让我对这个程序应该如何工作有了一些了解,非常感谢!
    • @user38543 运行时间O(k),绝对是你能得到的最好的。任何其他算法都会更糟。例如。您的算法的运行时间是 O(m+n)mn 是每个数组的大小,因为这是创建 AUB 所需的时间。我认为不可能得到 O(log n) 的运行时间。
    • 真的吗?那么,为什么作业问题要求我们分析并编写一个 O(log n) 的算法?作业中的提示是告诉我们找到中位数(A)、中位数(B)和中位数(AUB)之间的关系。我在这段关系上花了 1 个小时,但找不到任何东西
    【解决方案3】:

    两个数组的并集只是一个包含两个数组的所有元素的数组。

    例如A[1,20,40,70] and B[10,50,60,80] 以上2个数组的并集可能是C[1,20,40,70,10,50,60,80]

    现在假设 k 的范围从 1(含)开始,假设 k = 3,现在第 k 个元素是 40,但第 k 个最小的元素是 20。

    有效地做到这一点的方法在于你如何处理它。一种(不太有效的)方法可能是简单地使用 k 嵌套迭代并从未排序的联合数组中找到第 k 个最小的元素。

    另一种方法可能是在取并集后对数组进行排序,另一种方法是简单地合并两个数组,从而使合成的并集排序(合并排序:合并过程)。在这种情况下,结果数组将具有与第 k 个元素相同的第 k 个最小元素。

    【讨论】:

      【解决方案4】:

      正如其他人所说,第K个最小的元素是arr[k]之后升序排序。

      这也称为Selection algorithm,最有名的随机输入数组算法是Quick select,运行在O(n)时间,与Quick Sort密切相关。

      我最近刚好写了一个实现,你可以看看。


      代码 - 在Java

      QuickSelect.java

      /**
       * Find k-th smallest element from an array, via quick select.
       *
       * @author eric
       * @date 3/24/19 3:49 PM
       */
      public class QuickSelect {
          /**
           * Find k-th smallest element, of given array.
           *
           * @param arr input array, will be modified (sorted partially),
           * @param k   k-th, start from 0,
           * @return index of k-th, in the array,
           */
          public static int findKth(int[] arr, int k) {
              if (k < 0 || k >= arr.length)
                  throw new IllegalArgumentException("array length = " + arr.length + ", thus k should < " + arr.length + ", but get: " + k);
              return findKth(arr, k, 0, arr.length - 1);
          }
      
          /**
           * Find k-th smallest element, of given sub array.
           *
           * @param arr   input array, will be modified (sorted partially),
           * @param k     k-th, start from 0,
           * @param start inclusive
           * @param end   inclusive
           * @return index of k-th, in the array,
           */
          public static int findKth(int[] arr, int k, int start, int end) {
              if (start == end && start == k) return k; // base case,
              int pvt = end; // index of pivot, initially taken from last element of sub array,
      
              // check each element in sub array,
              for (int i = start; i <= end; i++) {
                  if (i < pvt && arr[i] > arr[pvt]) { // on left of pivot, and it's larger,
                      if (pvt - i == 1) { // neighbor, just switch,
                          int tmp = arr[i];
                          arr[i] = arr[pvt];
                          arr[pvt] = tmp;
                      } else { // not neighbor,
                          // swap 3 positions,
                          int tmp = arr[i];
                          arr[i] = arr[pvt - 1];
                          arr[pvt - 1] = arr[pvt];
                          arr[pvt] = tmp;
      
                          pvt -= 1; // adjust pvt,
      
                          i--; // restart from i,
                      }
                  } else if (i > pvt && arr[i] < arr[pvt]) { // on right of pivot, and it's smaller,
                      if (i - pvt == 1) { // neighbor, just switch,
                          int tmp = arr[i];
                          arr[i] = arr[pvt];
                          arr[pvt] = tmp;
                      } else {
                          // swap 3 positions,
                          int tmp = arr[i];
                          arr[i] = arr[pvt + 1];
                          arr[pvt + 1] = arr[pvt];
                          arr[pvt] = tmp;
      
                          pvt += 1; // adjust pvt,
                          // continue from i+1;
                      }
                  }
              }
      
              int leftSize = pvt - start; // element count on left side of pivot, in sub array,
              if (leftSize == k) { // pivot itself is k-th,
                  return pvt;
              } else if (leftSize > k) {
                  return findKth(arr, k, start, pvt - 1); // find on left part,
              } else {
                  return findKth(arr, k - leftSize - 1, pvt + 1, end); // find on right part,
              }
          }
      }
      

      QuickSelectTest.java
      (测试用例,通过TestNG

      import eric.algorithm.dynamic.ShufflePerfectly;
      import org.testng.Assert;
      import org.testng.annotations.BeforeMethod;
      import org.testng.annotations.Test;
      
      import java.util.Arrays;
      
      /**
       * QuickSelect test.
       *
       * @author eric
       * @date 3/24/19 3:50 PM
       */
      public class QuickSelectTest {
          private int size = 20; // array size, should be even,
          private int[] arr; // array with unique elements,
          private int[] arrDup; // array with duplicated elements,
      
          @BeforeMethod
          private void setUp() {
              // init - arr,
              arr = new int[size];
              for (int i = 0; i < size; i++) arr[i] = i;
              ShufflePerfectly.shuffle(arr); // shuffle,
              // System.out.printf("[initial] arr = %s\n", Arrays.toString(arr));
      
              // init - arrDup,
              arrDup = new int[size];
              int halfIdx = size / 2;
              for (int i = 0; i < halfIdx; i++) {
                  arrDup[i] = i;
                  arrDup[i + halfIdx] = i;
              }
              ShufflePerfectly.shuffle(arrDup); // shuffle,
              // System.out.printf("[initial] arrDup = %s\n", Arrays.toString(arrDup));
          }
      
          @Test
          public void test() {
              System.out.printf("\n[initial]: arr = %s\n", Arrays.toString(arr));
      
              for (int i = 0; i < arr.length; i++) {
                  // setUp(); // re-random array,
                  int idx = QuickSelect.findKth(arr, i);
      
                  Assert.assertEquals(idx, i); // check index,
                  Assert.assertEquals(arr[idx], i); // check value,
                  System.out.printf("[after %d-th]: arr = %s\n", i, Arrays.toString(arr));
              }
          }
      
          @Test
          public void test_dup() {
              System.out.printf("\n[initial]: arrDup = %s\n", Arrays.toString(arrDup));
      
              for (int i = 0; i < arr.length; i++) {
                  // setUp(); // re-random array,
                  int idx = QuickSelect.findKth(arrDup, i);
      
                  Assert.assertEquals(idx, i); // check index,
                  Assert.assertEquals(arrDup[idx], i / 2); // check value,
                  System.out.printf("[after %d-th]: arrDup = %s\n", i, Arrays.toString(arrDup));
              }
          }
      
          @Test(expectedExceptions = IllegalArgumentException.class)
          public void test_invalid_outOfRange() {
              QuickSelect.findKth(arr, arr.length);
          }
      
          @Test(expectedExceptions = IllegalArgumentException.class)
          public void test_invalid_negative() {
              QuickSelect.findKth(arr, -1);
          }
      }
      

      提示:

      • 它将修改输入数组,除非复制。
      • 它支持重复元素。
      • 代码用于理解算法,而不是用于生产。
        对于生产,可能需要更随机地选择支点,我不确定。

      您的原始输入

      由于你有 2 个 sorted 数组,所以不需要上面的算法。

      您可以简单地在一个循环中循环遍历 2 数组,每个数组有一个指针,并在每一步中将值较小的指针加 1,直到总步长为 k。

      【讨论】:

        猜你喜欢
        • 2012-10-22
        • 1970-01-01
        • 1970-01-01
        • 2020-03-02
        • 2021-09-23
        • 1970-01-01
        • 2015-01-15
        • 2019-04-11
        • 2022-01-04
        相关资源
        最近更新 更多