【问题标题】:Swift Mini-Max Sum One Test Case Failed - HackerRankSwift Mini-Max Sum 一个测试用例失败 - HackerRank
【发布时间】:2019-06-20 13:17:11
【问题描述】:

首先,我检查了这类问题是否适合 Stackoverflow,并基于一个类似的问题 (javascript) 和来自这个问题:https://meta.stackexchange.com/questions/129598/which-computer-science-programming-stack-exchange-sites-do-i-post-on -- 确实如此。

就这样吧。在我看来,挑战非常简单:

给定五个正整数,找出最小和最大值 可以通过将五个整数中的四个恰好相加来计算。然后 将各自的最小值和最大值打印为单行 两个空格分隔的长整数。

例如,.我们的最小总和是 和我们的最大总和是 。我们会 打印

16 24

输入约束: 1 <= arr[i] <= (10^9)

我的解决方案非常简单。这是我最擅长的:

func miniMaxSum(arr: [Int]) -> Void {
    let sorted = arr.sorted()
    let reversed = Array(sorted.reversed())
    var minSum = 0
    var maxSum = 0

    _ = sorted
        .filter({ $0 != sorted.last!})
        .map { minSum += $0 }  
    _ = reversed
        .filter({ $0 != reversed.last!})
        .map { maxSum += $0 }    

    print("\(minSum) \(maxSum)")
}

如您所见,我有两个排序数组。一个是递增的,一个是递减的。我正在删除两个新排序的数组的最后一个元素。我删除最后一个元素的方法是使用filter,这可能会产生问题。但从那里,我想我可以很容易地得到 4 个元素的最小和最大总和。

我通过了 13/14 个测试用例。我的问题是,这个解决方案可能会失败的测试用例是什么?

问题链接:https://www.hackerrank.com/challenges/mini-max-sum/problem

【问题讨论】:

  • 你应该链接到 HackerRank 上的问题,这样人们可以更轻松地尝试它
  • 是的,添加了它!
  • 您是否遇到错误或超时?
  • @MartinR 哎呀,还没试过。对于 Timsort,我们应该将接受的答案视为 O(nLog(n))(最坏情况)还是 O(n)? O(n) 解决方案不应该排序,它考虑到 4 = 5 - 1
  • @ielyamani:问题是“为什么这段代码会在测试用例中失败?”而不是“这个编程挑战的最快解决方案是什么?” – 我故意没有提供完整的解决方案,因此 OP 不会因为解决该挑战而失去满足感 :)

标签: swift algorithm


【解决方案1】:

这里

_ = sorted
    .filter({ $0 != sorted.last!})
    .map { minSum += $0 }  

您的期望是除了最大的元素之外的所有元素都被添加。但这只是正确的,因为最大的元素是 唯一的。(对于最大和也是如此。)

选择一个包含所有相同错误的数组会使问题更加明显:

miniMaxSum(arr: [1, 1, 1, 1, 1])
// 0 0 

一个更简单的解决方案是计算所有元素的总和一次,然后通过减去最大和最小的数组元素来获得结果。我会把实现留给你:)

【讨论】:

  • 搞定了,谢谢。从来没有这样想过!
【解决方案2】:

这是 O(n) 的解决方案:

func miniMaxSum(arr: [Int]) {
    var smallest = Int.max
    var greatest = Int.min
    var sum = 0

    for x in arr {
        sum += x
        smallest = min(smallest, x)
        greatest = max(greatest, x)
    }

    print(sum - greatest, sum - smallest, separator: "  ")
}

【讨论】:

  • 这很好,但重要的是要注意,如果arr.count - summedElementCount != 1,它并不能一概而论。
【解决方案3】:

我知道这不是 codereview.stackexchange.com,但我认为需要进行一些清理工作,所以我会从那开始。

  1. let reversed = Array(sorted.reversed())

    Array.reversed() 返回的ReversedCollection 的全部意义在于它不会导致复制元素,并且不会占用任何额外的内存或时间来生成。它只是一个集合的包装器,拦截索引操作并更改它们以模仿一个被反转的缓冲区。询问.first?它会给你.last 的包装集合。询问.last?它将返回.first 等。

    通过从sorted.reversed() 初始化一个新的Array,您会导致不必要的复制,并破坏ReversedCollection 的意义。在某些情况下,这可能是必要的(例如,您想将指向反向元素缓冲区的指针传递给 C API),但这不是其中之一。

    所以我们可以把它改成let reversed = sorted.reversed()

  2. -> Void 什么都不做,省略它。

  3. sorted.filter({ $0 != sorted.last!}) 效率低。

    ...但不仅如此,这是您错误的根源。这有一个错误。如果你有一个像[1, 1, 2, 3, 3] 这样的数组,你的minSum 将是4[1, 1, 2] 的总和),而它应该是7[1, 1, 2, 3] 的总和)。同样,maxSum 将是 8[2, 3, 3] 的总和)而不是 9[1, 2, 3, 3] 的总和)。

    您正在扫描整个数组,进行sorted.count 相等性检查,只是为了丢弃具有已知位置的元素(最后一个元素)。相反,使用dropLast(),它返回一个包含输入的集合,但其操作掩盖了最后一个元素的存在。

    _ = sorted
        .dropLast()
        .map { minSum += $0 }      
    _ = reversed
        .dropLast()
        .map { maxSum += $0 }
    
  4. _ = someCollection.map(f)

    ... 是一种反模式。 mapforEach 之间的区别特征是它生成一个结果数组,该数组存储闭包的返回值,作为对每个输入元素的评估。如果您不打算使用结果,请使用forEach

    sorted.dropLast().forEach { minSum += $0 }  
    reversed.dropLast().forEach { maxSum += $0 }  
    

    但是,还有更好的方法。与其通过改变变量并手动添加来求和,不如使用reduce 来执行此操作。这是理想的,因为它允许您删除 minSummaxSum 的可变性。

    let minSum = sorted.dropLast().reduce(0, +)
    let maxSum = reversed.dropLast().reduce(0, +)
    
  5. 您根本不需要reversed 变量。您可以通过对sorted 进行操作并使用dropFirst() 而不是dropLast() 来实现相同的目标:

    func miniMaxSum(arr: [Int]) {
        let sorted = arr.sorted()
    
        let minSum = sorted.dropLast().reduce(0, +)
        let maxSum = sorted.dropFirst().reduce(0, +)
    
        print("\(minSum) \(maxSum)")
    }
    
  6. 您的代码假定输入大小始终为 5。最好在代码中记录这一点:

    func miniMaxSum(arr: [Int]) {
        assert(arr.count == 5)
    
        let sorted = arr.sorted()
    
        let minSum = sorted.dropLast().reduce(0, +)
        let maxSum = sorted.dropFirst().reduce(0, +)
    
        print("\(minSum) \(maxSum)")
    }
    
  7. 您的解决方案的泛化使用大量额外内存,而您可能无法使用这些内存。

    这个问题修复了相加数字的数量(总是 4)和输入数字的数量(总是 5)。这个问题可以概括为从任何大小的arr 中挑选summedElementCount 数字。在这种情况下,两次排序和求和是低效的:

    • 您的解决方案的空间复杂度为O(arr.count)
      • 这是因为需要保存已排序的数组。如果允许您就地改变 arr,这可能会减少到 `O(1)。
    • 您的解决方案的时间复杂度为O((arr.count * log_2(arr.count)) + summedElementCount)

      • 推导:先排序(取O(arr.count * log_2(arr.count))),然后将第一个和最后一个summedElementCount相加(即每个O(summedElementCount)

          O(arr.count * log_2(arr.count)) + (2 * O(summedElementCount))
        = O(arr.count * log_2(arr.count)) + O(summedElementCount) // Annihilation of multiplication by a constant factor
        = O((arr.count * log_2(arr.count)) + summedElementCount) // Addition law for big O
        

    这个问题可以通过有界优先级队列来解决,例如 Google 的 Java 高瓦库中的 MinMaxPriorityQueue。它只是min-max heap 的一个包装器,它维护固定数量的元素,当添加这些元素时,会导致最大元素(根据提供的比较器)被驱逐。如果你在 Swift 中有这样的东西,你可以这样做:

    func miniMaxSum(arr: [Int], summedElementCount: Int) {
        let minQueue = MinMaxPriorityQueue<Int>(size: summedElementCount, comparator: <)
        let maxQueue = MinMaxPriorityQueue<Int>(size: summedElementCount, comparator: >)
    
        for i in arr {
            minQueue.offer(i)
            maxQueue.offer(i)
        }
    
        let (minSum, maxSum) = (minQueue.reduce(0, +), maxQueue.reduce(0, +))
    
        print("\(minSum) \(maxSum)")
    }
    
    • 此解决方案的空间复杂度仅为O(summedElementCount) 额外空间,需要容纳两个队列,每个队列的最大大小为summedElementCount

      • 这比之前的解决方案少,因为summedElementCount &lt;= arr.count
    • 这个解决方案的时间复杂度为O(arr.count * log_2(summedElementCount))

      • 偏差:for 循环执行 arr.count 迭代,每个迭代由两个队列上的 log_2(summedElementCount) 操作组成。

          O(arr.count) * (2 * O(log_2(summedElementCount)))
        = O(arr.count) * O(log_2(summedElementCount)) // Annihilation of multiplication by a constant factor
        = O(arr.count * log_2(summedElementCount)) // Multiplication law for big O
        
      • 我不清楚这比O((arr.count * log_2(arr.count)) + summedElementCount) 好还是坏。如果你知道,请在下面的 cmets 中告诉我!

【讨论】:

  • 完美!完美的!完美!
  • 这个解的复杂度是O(nLog(n)),问题可以在O(n)内解决
  • @FabioFelici 我认为在一般情况下没有O(n) 解决方案(其中arr.count - summedElementCount != 1),因为保持最小-最大堆在插入时仍然具有对数性能。请参阅我新写的第 7 点。
  • @Alexander 见下文
【解决方案4】:

试试这个接受的:

func miniMaxSum(arr: [Int]) -> Void {
    let sorted = arr.sorted()
    let minSum = sorted[0...3].reduce(0, +)
    let maxSum = sorted[1...4].reduce(0, +)
    print("\(minSum) \(maxSum)"
}

【讨论】:

    【解决方案5】:

    试试这个-

    func miniMaxSum(arr: [Int]) -> Void {
    
    var minSum = 0
    var maxSum = 0
    var minChecked = false
    var maxChecked = false
    let numMax = arr.reduce(Int.min, { max($0, $1) })
    print("Max number in array: \(numMax)")
    
    let numMin = arr.reduce(Int.max, { min($0, $1) })
    print("Min number in array: \(numMin)")
    
    for item in arr {
    
        if !minChecked && numMin == item {
            minChecked = true
        } else {
            maxSum = maxSum + item
        }
    
        if !maxChecked && numMax == item {
            maxChecked = true
        } else {
            minSum = minSum + item
        }
    }
    print("\(minSum) \(maxSum)")
    }
    

    【讨论】:

    • 您应该解释发生了什么,然后粘贴一些代码。 BTW ` { max($0, $1) }` 与 max 相同。
    【解决方案6】:

    试试这个:

    func miniMaxSum(arr: [Int]) -> Void {
    let min = arr.min()
    let max = arr.max()
    let total = arr.reduce(0, +)
    print(total - max!, total - min!, separator: " ")
    }
    

    【讨论】:

      猜你喜欢
      • 2022-08-08
      • 2022-06-19
      • 1970-01-01
      • 2022-06-19
      • 2019-03-14
      • 2020-11-24
      • 1970-01-01
      • 1970-01-01
      • 2020-06-07
      相关资源
      最近更新 更多