вопрос про скорость работы алгоритмов

Имеется задача: создать класс с указанными методами:

class NumArray {
  
    public NumArray(int[] nums) {
    //инициализация объекта с массивом интов
    }

    public void update(int index, int val) {
    // изменить значение под индексом index на значение val
    }

    public int sumRange(int left, int right) {
    //посчитать сумму между левым и правым индексом включительно
    }
}

Казалось бы ничего трудного, но я и реализовал это самым очевидным способом:

class NumArray {
    int[] nums;
    public NumArray(int[] nums) {
        this.nums=nums;
    }

    public void update(int index, int val) {
        nums[index]=val;
    }

    public int sumRange(int left, int right) {
        int sum=0;
        for(int i=left;i<=right;i++)
            sum+=nums[i];
        return sum;
    }
}

Класс прошел 13/15 проверок и на 14 когда я получил на вход большие массивы и большую разницу между left и right я не прошел проверку - не вписался в лимит по времени. Подсмотрел в ответы - там люди бинарные деревья организовывают и прочие "сложности". И я не могу понять ЗАЧЕМ? По сути методы в моем виде итак максимально быстрые? (update - это O(1) - и быстрее сделать невозможно) и (sumRange - это О(n) - так же быстрее сделать невозможно). В чем я ошибаюсь? Заранее спасибо


Ответы (1 шт):

Автор решения: had0uken

Вообщем благодаря подсказкам в комментариях решил задачу. Воспользовался структурой данных "дерево отрезков". Действительно при таком варианте решения я все также никуда не делся от сложности O(n) - именно такая сложность при построении дерева. Поэтому этот вариант помогает выиграть в скорости, когда у нас есть ОДИН исходный массив, и множество манипуляций с ним. То есть строится дерево 1 раз со сложностью О(n), а далее все манипуляции проводятся уже за логарифмическое время. И благодаря этому я проскочил те тесты, где задается один массив, и множественные вызовы методов на нем. Собственно сама реализация класса, если вдруг кому понадобится:

   public NumArray(int[] nums) {
        this.nums = nums;
        if (nums == null || nums.length == 0) return;
        this.root = buildTree(0, nums.length - 1);   //запуск построения дерева
        //System.out.println(this.root);
    }

    private TreeNode buildTree(int start, int end) {       //построение дерева
        TreeNode treeNode = new TreeNode();
        treeNode.start = start;
        treeNode.end = end;

        if (start == end) {
            treeNode.sum = nums[start];
            //System.out.println(treeNode.sum);
            return treeNode;
        }
        int mid = start + (end - start) / 2;
        treeNode.left = buildTree(start, mid);
        treeNode.right = buildTree(mid + 1, end);

        treeNode.sum = treeNode.left.sum + treeNode.right.sum;
        //System.out.println(treeNode.sum);

        return treeNode;
    }

    public void update(int i, int val) {
        update(root, i, val);               //стартовый запуск метода update, в котором уже будут крутиться рекурсии
    }

    private void update(TreeNode node, int i, int val) {
        if (node == null) return;
        if (node.end == node.start) {
            node.sum = val;
            return;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if (i <= mid)
            update(node.left, i, val);
        else update(node.right, i, val);
        node.sum = node.left.sum + node.right.sum;
    }

    public int sumRange(int start, int end) {
        return sumRange(root, start, end);        // аналогично стартовый запуск метода sumRange, в котором будет крутиться рекурсия.
    }

    private int sumRange(TreeNode node, int start, int end) {
        if (start < node.start || end > node.end) {
            return -1;
        }

        if ((start == node.start) && (end == node.end)) {
            return node.sum;
        }

        int mid = node.start + (node.end - node.start) / 2;
        if (start > mid)
            return sumRange(node.right, start, end);
        if (end <= mid) {
            return sumRange(node.left, start, end);
        }
        return sumRange(node.left, start, mid) + sumRange(node.right, mid + 1, end);

    }
}

Всем спасибо, за подсказки!

→ Ссылка