leetcode
leetcode 2151 ~ 2200
设计内存分配器

设计内存分配器

难度:

标签:

题目描述

You are given an integer n representing the size of a 0-indexed memory array. All memory units are initially free.

You have a memory allocator with the following functionalities:

  1. Allocate a block of size consecutive free memory units and assign it the id mID.
  2. Free all memory units with the given id mID.

Note that:

  • Multiple blocks can be allocated to the same mID.
  • You should free all the memory units with mID, even if they were allocated in different blocks.

Implement the Allocator class:

  • Allocator(int n) Initializes an Allocator object with a memory array of size n.
  • int allocate(int size, int mID) Find the leftmost block of size consecutive free memory units and allocate it with the id mID. Return the block's first index. If such a block does not exist, return -1.
  • int free(int mID) Free all memory units with the id mID. Return the number of memory units you have freed.

 

Example 1:

Input
["Allocator", "allocate", "allocate", "allocate", "free", "allocate", "allocate", "allocate", "free", "allocate", "free"]
[[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]]
Output
[null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]

Explanation
Allocator loc = new Allocator(10); // Initialize a memory array of size 10. All memory units are initially free.
loc.allocate(1, 1); // The leftmost block's first index is 0. The memory array becomes [1,_,_,_,_,_,_,_,_,_]. We return 0.
loc.allocate(1, 2); // The leftmost block's first index is 1. The memory array becomes [1,2,_,_,_,_,_,_,_,_]. We return 1.
loc.allocate(1, 3); // The leftmost block's first index is 2. The memory array becomes [1,2,3,_,_,_,_,_,_,_]. We return 2.
loc.free(2); // Free all memory units with mID 2. The memory array becomes [1,_, 3,_,_,_,_,_,_,_]. We return 1 since there is only 1 unit with mID 2.
loc.allocate(3, 4); // The leftmost block's first index is 3. The memory array becomes [1,_,3,4,4,4,_,_,_,_]. We return 3.
loc.allocate(1, 1); // The leftmost block's first index is 1. The memory array becomes [1,1,3,4,4,4,_,_,_,_]. We return 1.
loc.allocate(1, 1); // The leftmost block's first index is 6. The memory array becomes [1,1,3,4,4,4,1,_,_,_]. We return 6.
loc.free(1); // Free all memory units with mID 1. The memory array becomes [_,_,3,4,4,4,_,_,_,_]. We return 3 since there are 3 units with mID 1.
loc.allocate(10, 2); // We can not find any free block with 10 consecutive free memory units, so we return -1.
loc.free(7); // Free all memory units with mID 7. The memory array remains the same since there is no memory unit with mID 7. We return 0.

 

Constraints:

  • 1 <= n, size, mID <= 1000
  • At most 1000 calls will be made to allocate and free.

代码结果

运行时间: 109 ms, 内存: 16.8 MB


/*
 * 思路:
 * 使用Java Stream实现内存分配器。我们将使用IntStream来遍历内存数组。
 * allocate操作中使用IntStream找到第一个符合要求的空闲块,并分配给mID。
 * free操作中使用IntStream释放所有mID对应的内存单元。
 */

import java.util.stream.IntStream;

public class AllocatorStream {
    private int[] memory;

    public AllocatorStream(int n) {
        memory = new int[n];
    }

    public int allocate(int size, int mID) {
        int start = IntStream.range(0, memory.length - size + 1)
            .filter(i -> IntStream.range(i, i + size).allMatch(j -> memory[j] == 0))
            .findFirst()
            .orElse(-1);
        if (start != -1) {
            IntStream.range(start, start + size).forEach(i -> memory[i] = mID);
        }
        return start;
    }

    public int free(int mID) {
        int freed = (int) IntStream.range(0, memory.length)
            .filter(i -> memory[i] == mID)
            .peek(i -> memory[i] = 0)
            .count();
        return freed;
    }
}

解释

方法:

该题解采用了一个列表 space 来维护内存分配的状态。每个元素是一个三元组 [ID, cnt, idx],表示从下标 idx 开始,有 cnt 个连续的内存单元被分配给了 ID。如果 ID 为 -1,则表示这些内存单元是空闲的。在分配内存时,遍历 space,找到第一个空闲且大小足够的块,然后更新这个块的状态,并在需要时插入一个新的空闲块。在释放内存时,将所有属于给定 mID 的块标记为空闲,并合并连续的空闲块。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在实现`Allocator`类时,为什么选择使用三元组列表`space`来维护内存状态而不是其他数据结构如二叉树或哈希表?
使用三元组列表`space`来维护内存状态的主要原因是它的简单性和直观性。列表允许我们以线性方式遍历和查找足够大的空闲块,这对于维护连续的内存块非常有效。此外,使用列表可以非常方便地插入和删除元素,特别是在内存块分配和释放时。虽然二叉树或哈希表在某些操作上可能提供更快的时间复杂度,但它们在处理连续内存块的合并和拆分操作时可能不如列表直观和高效,特别是需要频繁地更新数据结构以反映内存的连续空间变化。
🦆
在`allocate`方法中,如何处理分配后剩余的内存块如果正好为0的情况,是否还需要插入剩余的空闲块?
在`allocate`方法中,如果分配内存后剩余的内存块大小正好为0,那么就不需要插入一个新的空闲块。这是因为没有剩余的空间可以作为一个独立的空闲块来维护。在这种情况下,原有的块被完全分配给请求者,列表中只更新该块的状态而不添加新的元素,这有助于避免不必要的列表操作和空间浪费。
🦆
在`free`方法中,释放内存后进行连续空闲块的合并操作,这种合并是否考虑了所有可能的连续空闲区域合并,包括多个连续区域的合并?
在`free`方法中,释放内存后进行的连续空闲块的合并操作确实考虑了所有可能的连续空闲区域的合并,包括多个连续区域的合并。方法通过遍历列表并检查相邻的空闲块来实现合并。如果发现连续的空闲块,就将它们合并成一个更大的空闲块。这个过程会继续进行,直到没有更多可以合并的连续空闲块为止。这种方法确保了内存的最大化利用与整合,减少了内存碎片。
🦆
如果多次进行内存分配和释放操作,`space`列表的长度可能会怎样变化,这对性能有何影响?
多次进行内存分配和释放操作可能会导致`space`列表的长度增加,特别是在频繁地分配较小块和随后释放这些块的情况下,因为这可能在列表中引入更多的小的空闲块。这种变化会影响性能,主要是因为列表长度的增长可能导致遍历和查找操作的时间开销增加。长列表可能导致分配和释放操作的效率下降,尤其是在查找合适的空闲块或合并空闲块时。为了优化性能,可能需要定期的内存整理和碎片整理操作,以减少空间碎片和缩短列表长度。

相关问题