【问题标题】:Codility NailingPlanksCodility NailingPlanks
【发布时间】:2020-09-19 20:14:16
【问题描述】:

试图了解 Codility NailingPlanks 的解决方案。

问题链接: https://app.codility.com/programmers/lessons/14-binary_search_algorithm/nailing_planks/

给定两个由 N 个整数组成的非空数组 A 和 B。 这些阵列代表 N 个木板。更准确地说,A[K] 是开始和 B[K] 第 K 个木板的末端。

接下来,给定一个由 M 个整数组成的非空数组 C。这 数组代表 M 个钉子。更准确地说,C[I] 是 你可以敲入第 I 个钉子。

如果存在钉子 C[I],我们说木板 (A[K], B[K]) 被钉了 使得 A[K] ≤ C[I] ≤ B[K]。

目标是找到必须使用的最少钉子数量 直到所有的木板都钉牢。换句话说,你应该找到一个 值 J 使得所有木板都将在仅使用第一个后​​被钉牢 J钉子。更准确地说,对于每块木板 (A[K], B[K]) 使得 0 ≤ K

解决方案链接: https://github.com/ZRonchy/Codility/blob/master/Lesson12/NailingPlanks.java

import java.util.Arrays;

class Solution {
    public int solution(int[] A, int[] B, int[] C) {
        // the main algorithm is that getting the minimal index of nails which
        // is needed to nail every plank by using the binary search
        int N = A.length;
        int M = C.length;
        // two dimension array to save the original index of array C
        int[][] sortedNail = new int[M][2];
        for (int i = 0; i < M; i++) {
            sortedNail[i][0] = C[i];
            sortedNail[i][1] = i;
        }
        // use the lambda expression to sort two dimension array
        Arrays.sort(sortedNail, (int x[], int y[]) -> x[0] - y[0]);
        int result = 0;
        for (int i = 0; i < N; i++) {//find the earlist position that can nail each plank, and the max value for all planks is the result
            result = getMinIndex(A[i], B[i], sortedNail, result);
            if (result == -1)
                return -1;
        }
        return result + 1;
    }
    // for each plank , we can use binary search to get the minimal index of the
    // sorted array of nails, and we should check the candidate nails to get the
    // minimal index of the original array of nails.
    public int getMinIndex(int startPlank, int endPlank, int[][] nail, int preIndex) {
        int min = 0;
        int max = nail.length - 1;
        int minIndex = -1;
        while (min <= max) {
            int mid = (min + max) / 2;
            if (nail[mid][0] < startPlank)
                min = mid + 1;
            else if (nail[mid][0] > endPlank)
                max = mid - 1;
            else {
                max = mid - 1;
                minIndex = mid;
            }
        }
        if (minIndex == -1)
            return -1;
        int minIndexOrigin = nail[minIndex][1];
        //find the smallest original position of nail that can nail the plank
        for (int i = minIndex; i < nail.length; i++) {
            if (nail[i][0] > endPlank)
                break;
            minIndexOrigin = Math.min(minIndexOrigin, nail[i][1]);
            // we need the maximal index of nails to nail every plank, so the
            // smaller index can be omitted    ****
            if (minIndexOrigin <= preIndex) // ****
                return preIndex;            // ****
        } 
        return minIndexOrigin;
    }
}

我不明白解决方案中标有**** 的第99-102 行。

我的问题是:

如果 minIndexOrigin &lt;= preIndex ,那么它将使用 preIndex, 但是如果preIndex 没有钉住当前的木板怎么办?

解决方案有一点错误吗?

【问题讨论】:

    标签: java algorithm binary-search


    【解决方案1】:

    在这些行中处理的情况是,当您发现有一个索引可以钉住当前木板时,该索引小于(或等于)我们需要能够钉另一个的最低索引(之前分析过)板。在这种情况下,我们不需要进一步寻找当前的木板,因为我们知道:

    • 我们可以钉木板
    • 我们可以使用不大于我们真正需要用于另一块木板的索引。

    由于我们只对不同木板所需的最小索引中的最大索引感兴趣(即“最差”木板的索引),我们知道我们刚刚找到的索引不再重要:如果我们已经知道我们将使用至少preIndex 的所有钉子,我们知道该组中的一个钉子将钉在当前的木板上。我们可以退出循环并返回一个不会影响结果的“虚拟”索引。

    注意调用循环中的效果:

    result = getMinIndex(A[i], B[i], sortedNail, result); 
    

    在这个分配之后,result 将等于调用之前的result:这块木板(A[i], B[i]) 可以用更早的钉子钉住,但我们并不真正关心是哪个钉子​​,因为我们需要知道最坏的情况,这已由result 反映,并且所有达到该索引的钉子都已经在将被锤击的钉子集中。

    您可以将其与 alpha-beta 修剪进行比较。

    【讨论】:

    • hmm,哪部分的代码,可以保证,较早的钉子,能钉当前的板子?
    • 不是特别那个钉子,但是既然挑战描述清楚的说钉子会从索引0一直锤到你返回的索引,你可以知道那个该范围内的钉子将钉住当前的木板。只是你没有准确地记住是哪一个钉子,因为你已经知道你需要更进一步地获得前一块木板,所以钉在当前木板上的钉子将在被锤击的中间钉子。
    • 哦,我明白了,所以钉子会从索引0开始敲到我们返回的索引,好的,现在明白了,谢谢
    【解决方案2】:

    https://app.codility.com/demo/results/trainingR7UKQB-9AQ/

    这是一个 100% 的解决方案。木板被压缩成(开始,结束)对并排序,支持二进制搜索。对于每个钉子,该钉子用于在搜索失败之前移除尽可能多的木板。当木板数组为空时,可以返回当前钉子的索引,表示已使用钉子的数量。

    O((N + M) * log(M))

    所有代码都在这里,https://github.com/niall-oc/things/blob/master/codility/nailing_planks.py

    def find_plank(nail, planks):
        """
        planks is an array of pairs (begin, end) for each plank.
        planks is sorted by start position of planks
        """
        if not planks:
            return -1 # empty planks
        BEGIN_IDX = 0
        END_IDX = 1
        lower = 0
        upper = len(planks)-1
        while lower <= upper:
            mid = (lower + upper) // 2
            if planks[mid][BEGIN_IDX] > nail:
                upper = mid - 1
            elif planks[mid][END_IDX] < nail:
                lower = mid + 1
            else: # nail hits plank[mid]
                return mid # return this plank id so we can pop the plank
        return -1
    
    
    def solution(A, B, C):
        """
        Strategy is to sort the planks first. Then scan the nails and do the following.
        For each nail perform a binary search for a plank.
            if plank found then pop plank then search again until the nail hits no more planks.
        The plank list should diminish until it hits zero meaning we have found the minimum number of nails needed
        If any planks remain then return -1
    
        100% https://app.codility.com/demo/results/trainingR7UKQB-9AQ/
        """
    
        if max(B) < min(C) or max(C) < min(A):
            return -1 # no nail can hit that plank
    
        planks = sorted(zip(A,B))
    
        for i in range(len(C)):
            nail = C[i]
            p_idx = find_plank(nail, planks)
            # print(f'idx{i} nail at pos {nail}, matched {p_idx}, in {planks}')
            while p_idx > -1:
                del planks[p_idx]
                p_idx = find_plank(nail, planks)
                # print(f'idx{i} nail at pos {nail}, matched {p_idx}, in {planks}')
            if not planks:
                # print('NO PLANKS', i+1)
                return i+1 # the index of the nail that removed the last plank.
    
        return -1 # else we couldn't remove all planks
    

    【讨论】:

    • del planks[p_idx] 需要 O(N) 时间,所以这是 O(N * M)
    【解决方案3】:

    这是整个算法的总结。我想谁明白它不会有任何问题。

    我们在做什么?

    1- 在不丢失原始索引的情况下订购指甲。

    2- 对于每块木板,通过二分查找找到可以钉木板的最小钉值。

    3-在最小钉值和木板末端位置之间找到每个钉子的最小原始索引,并取这些最小原始索引中的最小值。

    4- 返回每块木板最小钉原始索引的最大值。

    我们为什么要这样做?

    1- 我们不想搜索整个数组来找到最小索引。索引的原始顺序就是要求的,所以我们需要存储它。

    2- 我们需要找到最小指甲值来限制需要检查的可能原始索引的数量。需要二分查找才能找到对数时间复杂度的最小值。

    3- 我们需要找到候选指甲的原始索引。第一个候选可以是最小钉子值,最后一个候选可以是木板的结束位置。这就是为什么我们只在这个区间检查原始索引。

    4- 我们找到每个木板的钉子的最小原始索引。但答案将是这些最小索引中的最大值,因为当我们完成每块木板的钉钉时,问题会询问我们使用的最后一个钉子的索引。

    【讨论】:

      【解决方案4】:

      我想为所需的O(log(M) * (M + N)) 运行时复杂度提供我的算法和实现。

      1. 对 C 元素进行二分搜索。
      2. 对于每次迭代,创建一个前缀总和来确定当前的二分搜索迭代。这将需要两个步骤:
        一种。计算 C 中指甲位置的出现次数。
        湾。针对特定索引处的可用钉子创建适当的前缀总和。
      3. 如果木板范围内没有钉子,则无法在此处找到钉子,因此无法解决该任务。
      4. 如果该连续数组中没有解,我们需要寻找更长的范围。
      5. 如果在该连续序列中存在解,我们需要寻找更小的范围。
      6. 二分查找一直持续到我们最终找到最小范围为止。

      二分查找的运行时复杂度为log(M),因为我们将一个范围一分为二。内部迭代的运行时复杂度来自三个循环:

      一个。 O(mid),其中mid &lt; M。所以,在最坏的情况下,这是O(M)
      湾。 O(2M),即O(M),因为我们可以保留常量。
      C。 O(N),遍历A和B的元素个数。

      因此,内循环的运行时复杂度为O(M + N)

      因此算法的整体运行时复杂度为O(log(M) * (M + N))

      前缀和的整体空间复杂度为O(2 * M),因此基本上是O(M)

      bool check(vector<int> &A, vector<int> &B, vector<int> &C, int mid)             
      {                                                                               
        const int M = C.size();                                                       
        vector<int> prefix_sums(2 * M + 1, 0);                                        
        for (int i = 0; i < mid; ++i) ++prefix_sums[C[i]];                            
        for (size_t i = 1; i < prefix_sums.size(); ++i) prefix_sums[i] += prefix_sums[i - 1];
        for (size_t i = 0; i < A.size(); ++i) if (prefix_sums[B[i]] == prefix_sums[A[i] - 1]) return false;
        return true;                                                                  
      }
      
      int solution(vector<int> &A, vector<int> &B, vector<int> &C)                    
      {                                                                               
        int start = 1;                                                                
        int end = C.size();                                                           
        int min_nails = -1;                                                           
        while (start <= end) {                                                        
          int mid = (start + end) / 2;                                                
          if (check(A, B, C, mid)) { end = mid - 1; min_nails = mid; }                
          else start = mid + 1;                                                       
        }                                                                             
        return min_nails;                                                             
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-09
        • 2014-12-21
        • 2014-05-31
        • 2015-02-19
        • 2013-10-28
        • 1970-01-01
        • 1970-01-01
        • 2016-03-10
        相关资源
        最近更新 更多