返回

算法入门 | 深入剖析LeetCode 307:区域和检索-数组可修改

后端

探索 LeetCode 307:“区域和检索-数组可修改”

简介

数据结构和算法是计算机科学的基础,对于解决各种问题至关重要。在 LeetCode 307 中,我们遇到了一道经典问题:“区域和检索-数组可修改”。本篇文章将深入剖析这道题,探讨如何利用树状数组和线段树两种高效的数据结构来解决问题,同时深入理解算法的运作原理和数据结构的优势。

问题

给定一个数组 nums,定义两个操作:

  • update(index, val):更新下标为 index 的元素为 val
  • sumRange(left, right):求子数组 [left, right] 的和

树状数组

树状数组,又称二叉索引树,是一种一维索引树,将一维数组中的元素组织成一棵二叉树。它支持高效的单点更新和区间查询操作。

线段树

线段树是一种二叉搜索树,将数组划分为多个区间,并在每个节点中存储区间内的信息。线段树支持高效的区间查询、区间更新和区间求和操作。

解决方案

Java 树状数组解法

class NumArray {

    private int[] tree;
    private int[] nums;
    private int n;

    public NumArray(int[] nums) {
        this.nums = nums;
        n = nums.length;
        tree = new int[n + 1];
        buildTree(nums);
    }

    private void buildTree(int[] nums) {
        for (int i = 0; i < n; i++) {
            update(i, nums[i]);
        }
    }

    public void update(int index, int val) {
        int diff = val - nums[index];
        nums[index] = val;
        index++;
        while (index <= n) {
            tree[index] += diff;
            index += index & -index;
        }
    }

    public int sumRange(int left, int right) {
        return getSum(right) - getSum(left - 1);
    }

    private int getSum(int index) {
        int sum = 0;
        index++;
        while (index > 0) {
            sum += tree[index];
            index -= index & -index;
        }
        return sum;
    }
}

C++ 线段树解法

class NumArray {
public:
    struct SegmentTreeNode {
        int start, end;
        long long sum;
        SegmentTreeNode *left, *right;

        SegmentTreeNode(int start, int end, long long sum) : start(start), end(end), sum(sum), left(nullptr), right(nullptr) {}
    };

    SegmentTreeNode *root;

    NumArray(vector<int>& nums) {
        if (nums.empty()) return;
        root = buildTree(nums, 0, nums.size() - 1);
    }

    SegmentTreeNode *buildTree(vector<int>& nums, int start, int end) {
        if (start > end) return nullptr;
        SegmentTreeNode *node = new SegmentTreeNode(start, end, 0);
        if (start == end) {
            node->sum = nums[start];
        } else {
            int mid = (start + end) / 2;
            node->left = buildTree(nums, start, mid);
            node->right = buildTree(nums, mid + 1, end);
            node->sum = node->left->sum + node->right->sum;
        }
        return node;
    }

    void update(int index, int val) {
        update(root, index, val);
    }

    void update(SegmentTreeNode *node, int index, int val) {
        if (node->start == node->end) {
            node->sum = val;
        } else {
            int mid = (node->start + node->end) / 2;
            if (index <= mid) {
                update(node->left, index, val);
            } else {
                update(node->right, index, val);
            }
            node->sum = node->left->sum + node->right->sum;
        }
    }

    long long sumRange(int left, int right) {
        return sumRange(root, left, right);
    }

    long long sumRange(SegmentTreeNode *node, int left, int right) {
        if (node->start > right || node->end < left) {
            return 0;
        } else if (node->start >= left && node->end <= right) {
            return node->sum;
        } else {
            return sumRange(node->left, left, right) + sumRange(node->right, left, right);
        }
    }
};

比较

两种解法各有优缺点:

  • 树状数组:空间复杂度 O(n),时间复杂度为单点更新 O(log n),区间查询 O(log n)
  • 线段树:空间复杂度 O(n),时间复杂度为单点更新 O(log n),区间查询 O(log n)

总结

树状数组和线段树都是高效的数据结构,可以用来解决各种数组相关问题。本篇文章详细分析了如何利用这两种数据结构解决 LeetCode 307 问题,深入理解了算法和数据结构的原理。在实际应用中,可以根据具体问题和数据规模选择最合适的数据结构来优化算法性能。

常见问题解答

  1. 树状数组和线段树有什么区别?

    树状数组是一种一维索引树,而线段树是一种二叉搜索树。树状数组支持高效的单点更新和区间查询,而线段树支持高效的区间查询、区间更新和区间求和。

  2. 哪种数据结构更适合解决 LeetCode 307 问题?

    两种数据结构都可以解决该问题。树状数组空间复杂度更低,线段树支持的