You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift报错:无法对不可变值使用可变成员,求解决方案

问题:使用Swift Algorithm Club的Heap结构体解决「Heaps: Find the Running Median」时出错

嘿,我看到你在用Swift Algorithm Club的Heap实现来搞定Hackerrank的「Heaps: Find the Running Median」挑战,但遇到了问题,而且你的代码片段还没写完(buildHea...这里明显截断了)。先把你给出的代码整理出来,然后咱们聊聊这类问题里最容易踩的几个坑:

你提供的代码片段

import Foundation
// Enter your code here
struct Heap<Element> {
    var elements : [Element]
    let priorityFunction : (Element, Element) -> Bool
    init(elements: [Element] = [], priorityFunction: @escaping (Element, Element) -> Bool) {
        self.elements = elements
        self.priorityFunction = priorityFunction
        buildHea... // 这里代码未完成
    }
}

常见问题分析&解决方案

求动态中位数的核心是用两个堆配合:一个最大堆存较小的一半元素,一个最小堆存较大的一半元素。结合你的代码情况,大概率是以下几个地方出了问题:

1. Heap核心方法未完整实现

你的buildHeap方法只写了一半,这个方法是用来把初始数组堆化的关键步骤,没实现的话堆的结构完全不对,后续的入队、出队操作都会失效。我给你补全一个完整的Heap实现,包含所有必要的核心方法:

struct Heap<Element> {
    var elements: [Element]
    let priorityFunction: (Element, Element) -> Bool
    
    init(elements: [Element] = [], priorityFunction: @escaping (Element, Element) -> Bool) {
        self.elements = elements
        self.priorityFunction = priorityFunction
        buildHeap()
    }
    
    // 堆化初始数组
    mutating func buildHeap() {
        for index in (0..<elements.count/2).reversed() {
            siftDown(from: index)
        }
    }
    
    var isEmpty: Bool { elements.isEmpty }
    var count: Int { elements.count }
    func peek() -> Element? { elements.first }
    
    // 入队:添加元素后上浮调整
    mutating func enqueue(_ element: Element) {
        elements.append(element)
        siftUp(from: elements.count - 1)
    }
    
    // 出队:取出堆顶后下沉调整
    mutating func dequeue() -> Element? {
        guard !isEmpty else { return nil }
        elements.swapAt(0, elements.count - 1)
        let element = elements.removeLast()
        siftDown(from: 0)
        return element
    }
    
    // 上浮操作:维护堆的优先级
    private mutating func siftUp(from index: Int) {
        var child = index
        var parent = parentIndex(of: child)
        while child > 0 && priorityFunction(elements[child], elements[parent]) {
            elements.swapAt(child, parent)
            child = parent
            parent = parentIndex(of: child)
        }
    }
    
    // 下沉操作:维护堆的优先级
    private mutating func siftDown(from index: Int) {
        var parent = index
        while true {
            let left = leftChildIndex(of: parent)
            let right = rightChildIndex(of: parent)
            var candidate = parent
            if left < count && priorityFunction(elements[left], elements[candidate]) {
                candidate = left
            }
            if right < count && priorityFunction(elements[right], elements[candidate]) {
                candidate = right
            }
            if candidate == parent {
                return
            }
            elements.swapAt(parent, candidate)
            parent = candidate
        }
    }
    
    // 辅助方法:计算父节点索引
    private func parentIndex(of index: Int) -> Int {
        (index - 1) / 2
    }
    
    // 辅助方法:计算左子节点索引
    private func leftChildIndex(of index: Int) -> Int {
        index * 2 + 1
    }
    
    // 辅助方法:计算右子节点索引
    private func rightChildIndex(of index: Int) -> Int {
        index * 2 + 2
    }
}

2. 堆的优先级函数设置错误

最大堆的优先级函数应该是$0 > $1(保证堆顶是最大元素),最小堆是$0 < $1(保证堆顶是最小元素),如果搞反了,堆的逻辑会完全颠倒,导致中位数计算错误。

3. 两个堆的平衡维护不当

每次插入元素后,必须保证:

  • 两个堆的大小差不超过1
  • 最大堆的堆顶元素 ≤ 最小堆的堆顶元素

我给你写一个完整的中位数计算逻辑,包含堆的平衡维护:

func findRunningMedian(_ numbers: [Double]) -> [Double] {
    // 最大堆:存储较小的一半元素,堆顶是最大的那个
    var maxHeap = Heap<Double>(priorityFunction: >)
    // 最小堆:存储较大的一半元素,堆顶是最小的那个
    var minHeap = Heap<Double>(priorityFunction: <)
    var medians: [Double] = []
    
    for number in numbers {
        // 把元素插入到合适的堆
        if maxHeap.isEmpty || number <= maxHeap.peek()! {
            maxHeap.enqueue(number)
        } else {
            minHeap.enqueue(number)
        }
        
        // 平衡两个堆的大小
        if maxHeap.count > minHeap.count + 1 {
            // 最大堆太大,把堆顶移到最小堆
            if let element = maxHeap.dequeue() {
                minHeap.enqueue(element)
            }
        } else if minHeap.count > maxHeap.count {
            // 最小堆太大,把堆顶移到最大堆
            if let element = minHeap.dequeue() {
                maxHeap.enqueue(element)
            }
        }
        
        // 计算当前中位数
        let median: Double
        if maxHeap.count == minHeap.count {
            median = (maxHeap.peek()! + minHeap.peek()!) / 2.0
        } else {
            // 元素总数为奇数时,最大堆的堆顶就是中位数
            median = maxHeap.peek()!
        }
        medians.append(median)
    }
    
    return medians
}

// 示例测试
let input = [1, 2, 3, 4, 5]
let result = findRunningMedian(input.map { Double($0) })
print(result) // 输出 [1.0, 1.5, 2.0, 2.5, 3.0]

4. 注意Hackerrank的输入输出格式

最后还要注意Hackerrank的要求:比如输入是多行数字,输出需要保留指定的小数位数(通常是1位),你需要把计算出的中位数按格式输出。

内容的提问来源于stack exchange,提问作者Rick Clark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:33:09