Python 线段树算法解决 LeetCode 2243. 形成目标数组的最小操作数

题目链接: https://leetcode-cn.com/problems/minimum-operations-to-make-array-sequential/

题目描述:

给你一个包含若干互不相同整数的数组 nums ,你需要执行以下操作直到数组为空 :

  • 如果数组中第一个元素是当前数组中的最小值,则删除它。
  • 否则,将第一个元素移动到数组的末尾。

请你返回需要多少个操作使 nums 为空。

示例 1:

输入:nums = [5,1,3]
输出:3
解释:需要执行 3 次操作才能使 nums 为空:
1. 删除 1 ,得到 [5,3] 。
2. 删除 3 ,得到 [5] 。
3. 删除 5 ,得到 [] 。

示例 2:

输入:nums = [1,2,3,5,4]
输出:5
解释:需要执行 5 次操作才能使 nums 为空:
1. 移动下标 1 处的 2 到末尾,得到 [1,3,5,4,2] 。
2. 删除 1 ,得到 [3,5,4,2] 。
3. 删除 3 ,得到 [5,4,2] 。
4. 删除 5 ,得到 [4,2] 。
5. 删除 4 ,得到 [] 。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • nums 中的所有元素互不相同

解题思路:

模拟题。题目描述给出了两个操作:

  • 删除数组中的最小值。
  • 将数组中的第一个元素移动到末尾。

可以发现,删除最小值操作和数组中的最小值有关,为了快速找到数组中的最小值,可以使用线段树(也可以用堆,但是线段树更为常用)。

对于数组元素的移动,可以使用双指针。用一个指针记录数组中的最小值,然后另一个指针向后遍历,如果找到了比最小值更小的元素,就将这个元素移到最后,同时更新最小值的位置。

代码实现:

首先实现线段树。对于每个节点,维护它的区间最小值和最小值的位置。对于每个节点的左右儿子,它的区间分别为 [left, mid] 和 [mid+1, right]。

class SegmentTree:
    def __init__(self, n):
        self.tree = [[0, 0] for _ in range(n * 4)]

    def build(self, node, left, right, nums):
        if left == right:
            self.tree[node][0] = nums[left]
            self.tree[node][1] = left
            return
        mid = (left + right) // 2
        self.build(node * 2, left, mid, nums)
        self.build(node * 2 + 1, mid + 1, right, nums)
        if self.tree[node * 2][0] < self.tree[node * 2 + 1][0]:
            self.tree[node][0] = self.tree[node * 2][0]
            self.tree[node][1] = self.tree[node * 2][1]
        else:
            self.tree[node][0] = self.tree[node * 2 + 1][0]
            self.tree[node][1] = self.tree[node * 2 + 1][1]

    def query(self, node, left, right, qleft, qright):
        if qleft <= left and right <= qright:
            return self.tree[node]
        mid = (left + right) // 2
        res = [float('inf'), -1]
        if qleft <= mid:
            left_res = self.query(node * 2, left, mid, qleft, qright)
            if left_res[0] < res[0]:
                res[0] = left_res[0]
                res[1] = left_res[1]
        if qright > mid:
            right_res = self.query(node * 2 + 1, mid + 1, right, qleft, qright)
            if right_res[0] < res[0]:
                res[0] = right_res[0]
                res[1] = right_res[1]
        return res

然后是主函数的实现。首先建立线段树,然后用双指针模拟操作。用 left 指针记录数组中的最小值的位置,用 right 指针遍历数组。如果 right 指针找到了比最小值更小的元素,就将这个元素移到最后,同时更新最小值的位置。如果 right 指针没有找到比最小值更小的元素,就将最小值删除,并更新最小值的位置。

def minOperations(nums):
    n = len(nums)
    tree = SegmentTree(n)
    tree.build(1, 0, n - 1, nums)
    left = tree.query(1, 0, n - 1, 0, n - 1)[1]
    right = (left + 1) % n
    ans = 0
    while left != -1:
        if nums[right] < nums[left]:
            nums.append(nums.pop(right))
            ans += 1
            left = (left - 1) % n
        else:
            if left == right:
                left_res = tree.query(1, 0, n - 1, 0, left - 1)
                right_res = tree.query(1, 0, n - 1, right + 1, n - 1)
                if left_res[0] < right_res[0]:
                    nums.pop(left)
                    ans += 1
                    left = left_res[1]
                else:
                    nums.pop(right)
                    ans += 1
                    left = right_res[1]
            else:
                nums.pop(left)
                ans += 1
                left = tree.query(1, 0, n - 1, 0, left - 1)[1]
        right = (left + 1) % n
    return ans

完整代码:

class SegmentTree:
    def __init__(self, n):
        self.tree = [[0, 0] for _ in range(n * 4)]

    def build(self, node, left, right, nums):
        if left == right:
            self.tree[node][0] = nums[left]
            self.tree[node][1] = left
            return
        mid = (left + right) // 2
        self.build(node * 2, left, mid, nums)
        self.build(node * 2 + 1, mid + 1, right, nums)
        if self.tree[node * 2][0] < self.tree[node * 2 + 1][0]:
            self.tree[node][0] = self.tree[node * 2][0]
            self.tree[node][1] = self.tree[node * 2][1]
        else:
            self.tree[node][0] = self.tree[node * 2 + 1][0]
            self.tree[node][1] = self.tree[node * 2 + 1][1]

    def query(self, node, left, right, qleft, qright):
        if qleft <= left and right <= qright:
            return self.tree[node]
        mid = (left + right) // 2
        res = [float('inf'), -1]
        if qleft <= mid:
            left_res = self.query(node * 2, left, mid, qleft, qright)
            if left_res[0] < res[0]:
                res[0] = left_res[0]
                res[1] = left_res[1]
        if qright > mid:
            right_res = self.query(node * 2 + 1, mid + 1, right, qleft, qright)
            if right_res[0] < res[0]:
                res[0] = right_res[0]
                res[1] = right_res[1]
        return res

def minOperations(nums):
    n = len(nums)
    tree = SegmentTree(n)
    tree.build(1, 0, n - 1, nums)
    left = tree.query(1, 0, n - 1, 0, n - 1)[1]
    right = (left + 1) % n
    ans = 0
    while left != -1:
        if nums[right] < nums[left]:
            nums.append(nums.pop(right))
            ans += 1
            left = (left - 1) % n
        else:
            if left == right:
                left_res = tree.query(1, 0, n - 1, 0, left - 1)
                right_res = tree.query(1, 0, n - 1, right + 1, n - 1)
                if left_res[0] < right_res[0]:
                    nums.pop(left)
                    ans += 1
                    left = left_res[1]
                else:
                    nums.pop(right)
                    ans += 1
                    left = right_res[1]
            else:
                nums.pop(left)
                ans += 1
                left = tree.query(1, 0, n - 1, 0, left - 1)[1]
        right = (left + 1) % n
    return ans

代码分析:

  • 线段树的构建和查询操作比较基础,这里就不再赘述。
  • 主函数中,我们用两个指针 leftright 模拟操作。
  • left 指针指向数组中的最小值,right 指针指向 left 指针的下一个位置。
  • 循环中,我们判断 right 指针指向的元素是否比 left 指针指向的元素小。
  • 如果更小,则将 right 指针指向的元素移动到数组末尾,并将 left 指针移动到数组的下一个位置。
  • 否则,如果 left 指针指向的元素是当前数组中的最小值,则将其删除,并将 left 指针移动到数组的下一个位置。
  • 如果 left 指针指向的元素不是当前数组中的最小值,则将其移动到数组末尾,并将 left 指针移动到数组的下一个位置。
  • left 指针指向 -1 时,表示数组为空,循环结束。
  • 最后,返回 ans,即操作次数。
Python 线段树算法解决 LeetCode 2243. 形成目标数组的最小操作数

原文地址: https://www.cveoy.top/t/topic/nJ9m 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录