区间和的个数(树状数组、线段树)

给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

示例 1: 输入:nums = [-2,5,-1], lower = -2, upper = 2 输出:3 解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。 示例 2: 输入:nums = [0], lower = 0, upper = 0 输出:1

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • -105 <= lower <= upper <= 105
  • 题目数据保证答案是一个 32 位 的整数

解答:

class Solution {
    public int countRangeSum(int[] nums, long lower, long upper) {
        long sums[] = new long[nums.length];
        for (int i = 0; i < nums.length; i++) {
            sums[i] = ((i - 1 >= 0) ? sums[i - 1] : 0) + nums[i];
        }
        int result = divideAndConquer(sums, 0, sums.length - 1, upper, lower);
        return result;
    }
    private int divideAndConquer(long sums[], int start, int end, long upper, long lower) {
        if (start > end)
            return 0;
        if (start == end)
            return (sums[start] <= upper && sums[start] >= lower) ? 1 : 0;
        int mid = (start + end) / 2;
        int counts = 0;
        counts += divideAndConquer(sums, start, mid, upper, lower);
        counts += divideAndConquer(sums, mid + 1, end, upper, lower);
        int ls = start, le = mid;
        while (le >= start && sums[mid + 1] - sums[le] <= upper)
            le--;
        for (int r = mid + 1; r <= end; r++) {
            while (ls <= mid && sums[r] - sums[ls] >= lower)
                ls++;
            while (le + 1 <= mid && sums[r] - sums[le + 1] > upper)
                le++;
            if (ls - le - 1 < 0)
                continue;
            counts += (ls - le - 1);
        }
        ls = start;
        int i = 0, r = mid + 1;
        long merged[] = new long[end - start + 1];
        while (ls <= mid || r <= end) {
            if (ls > mid || (r <= end && sums[r] < sums[ls])) {
                merged[i++] = sums[r++];
            } else {
                merged[i++] = sums[ls++];
            }
        }
        for (i = 0; i < merged.length; i++) {
            sums[start + i] = merged[i];
        }
        return counts;
    }
}

将数据流变为多个不相交区间(设计、二分查找)

给你一个由非负整数 a1, a2, ..., an 组成的数据流输入,请你将到目前为止看到的数字总结为不相交的区间列表。 实现 SummaryRanges 类:

  • SummaryRanges() 使用一个空数据流初始化对象。
  • void addNum(int val) 向数据流中加入整数 val 。
  • int[][] getIntervals() 以不相交区间 [starti, endi] 的列表形式返回对数据流中整数的总结。

示例: 输入: ["SummaryRanges", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals"] [[], [1], [], [3], [], [7], [], [2], [], [6], []] 输出: [null, null, [[1, 1]], null, [[1, 1], [3, 3]], null, [[1, 1], [3, 3], [7, 7]], null, [[1, 3], [7, 7]], null, [[1, 3], [6, 7]]] 解释: image.png

提示:

  • 0 <= val <= 104
  • 最多调用 addNum 和 getIntervals 方法 3 * 104 次

**进阶:**如果存在大量合并,并且与数据流的大小相比,不相交区间的数量很小,该怎么办?

解答:

class SummaryRanges {
    private final TreeSet<Integer> tree = new TreeSet<>();
    public void addNum(int val) {
        tree.add(val);
    }
    public int[][] getIntervals() {
        ArrayList<int[]> result = new ArrayList<>(1 << 2);
        Iterator<Integer> iterator = tree.iterator();
        int[] array = new int[tree.size()];
        int i = 0;
        while (iterator.hasNext())
            array[i++] = iterator.next();
        int length = array.length;
        if (length == 0)
            return result.toArray(new int[0][]);
        int start = array[0];
        for (i = 0; i < length; ++i) {
            if (i + 1 < length && array[i + 1] - array[i] != 1) {
                result.add(new int[] { start, array[i] });
                start = array[i + 1];
            } else if (i + 1 == length) {
                result.add(new int[] { start, array[i] });
            }
        }
        return result.toArray(new int[result.size()][]);
    }
}

逆波兰表达式求值(栈、数组)

根据 逆波兰表示法,求表达式的值。 有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1: 输入:tokens = ["2","1","+","3",""] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 示例 2: 输入:tokens = ["4","13","5","/","+"] 输出:6 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 示例 3: 输入:tokens = ["10","6","9","3","+","-11","","/","*","17","+","5","+"] 输出:22 解释: 该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22

提示:

  • 1 <= tokens.length <= 104
  • tokens[i] 要么是一个算符("+"、"-"、"*" 或 "/"),要么是一个在范围 [-200, 200] 内的整数

逆波兰表达式: 逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

解释:

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (String s : tokens) {
            if (s.equals("+")) {
                stack.push(stack.pop() + stack.pop());
            } else if (s.equals("-")) {
                stack.push(-stack.pop() + stack.pop());
            } else if (s.equals("*")) {
                stack.push(stack.pop() * stack.pop());
            } else if (s.equals("/")) {
                int num1 = stack.pop();
                stack.push(stack.pop() / num1);
            } else {
                stack.push(Integer.parseInt(s));
            }
        }
        return stack.pop();
    }
}

本文内容到此结束了, 如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。 如有错误❌疑问💬欢迎各位大佬指出。 主页共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃