Swift | Heap 55ms 100%
Amortized Analysis: O(logn) on the average per operation, after n operations. This is due to the fact that each index insertion (in change) results in at most o
**Amortized Analysis**: $O(\log n)$ on the average per operation, after $n$ operations. This is due to the fact that each index insertion (in ```change```) results in at most one removal (in ```find```).

More formally, $n = n_{init} + n_{change} + n_{find} = 1 + n_{change} + n_{find}$ container operations result into $n_{change}$ heap insertions and $\le n_{change}$ heap min pops, that is $\le 2\times n_{change} \le 2 \times n$ heap operations.

That is, $n$ container operations result into  $\le 2n$ heap operations, each heap operation taking $O(\log n)$ running time. This means that the total running time is $O(n\log n)$ or $O(\log n)$ per container operation.

I have omitted in the analysis $O(1)$ heap and dictionary operations but the conclusion would be the same.


# Code
```swift []
class NumberContainers {
    var indexToNumber = [Int: Int]()
    var numberToIndices = [Int: Heap<Int>]()

    func change(_ index: Int, _ number: Int) {
        indexToNumber[index] = number
        numberToIndices[number, default: Heap()].insert(index)
    }
    
    func find(_ number: Int) -> Int {
        while let index = numberToIndices[number]?.min {
            if indexToNumber[index] == number { return index }
            numberToIndices[number]?.popMin()
        }
        return -1
    }
}
```
['Swift']