数组

0.数组的常见操作

0.1 遍历所有的连续子序列

public void searchSubArray(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        for (int j = i; j < nums.length; j++) {
            {i, ..., j} // 起点为i,终点为j
        }
    }
}

0.2 不包含i自身的其他元素遍历

public void searchSubArray(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            nums[i] nums[j] // 不包含i自身的其他元素遍历
        }
    }
}

0.3 反转数组

public void reverseArray(int[] nums) {
    // 头尾指针
    int left = 0; right = nums.length - 1;
    while (left < right) {
        int tmp = nums[left];
        nums[left] = nums[right];
        nums[right] = tmp;
        left++;
        right--;
    }
}

0.4 数组插入元素

public void insert(int index, int element) throws Exception {
    if (index < 0 || index >= nums.length) throw new IndexOutOfBoundsException("下标越界");
    for (int i = size; i > index; i--) { // size 数组的实际长度
        nums[i] = nums[i - 1];
    }
    nums[index] = element;
    size++;
}

1.前缀和数组

1.1 求前缀和数组模板

public int[] getPreSumArray(int[] nums) {
    if (nums.length == null) {
        return nums;
    }
    int[] preSum = new int[nums.length + 1];
    for (int i = 0; i < nums.length; i++) {
        preSum[i] = preSump[i - 1] + nums[i - 1];
    }
    return preSum;
}

1.2 考察前缀和数组题的特点

适用场景:原始数组不会被修改的情况下,频繁查询某个区间的累加和。

  • 求解连续区间的和 = preSum[right + 1] - preSum[left];
  • 求解连续子序列和为K的子序列个数

1.3 典型题目

303. 区域和检索 - 数组不可变

给定一个整数数组 nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + ... + nums[right] )

示例 1:

输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]

解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

提示:

1 <= nums.length <= 104
-105 <= nums[i] <= 105
0 <= i <= j < nums.length
最多调用 104 次 sumRange 方法

class NumArray {
    // 前缀和问题:求解连续区间的和
    // 解题框架:
    // 1.遍历数组求出前缀和数组;
    // 2.根据前缀和求解区间和;
    private int[] preSum;

    public NumArray(int[] nums) {
        preSum = new int[nums.length + 1];
        for (int i = 1; i < preSum.length; i++) {// 这里是preSum.length
            preSum[i] = preSum[i - 1] + nums[i - 1];
        }
    }
    
    public int sumRange(int left, int right) {
        return preSum[right + 1] - preSum[left];
    }
}

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * int param_1 = obj.sumRange(left,right);
 */

560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2
示例 2:

输入:nums = [1,2,3], k = 3
输出:2

提示:

1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107

class Solution {
    public int subarraySum(int[] nums, int k) {
        // 获取前缀和数组后我们就可以拿到该数组的任意连续子序列的和
        int[] preSum = new int[nums.length + 1];
        for (int i = 1; i <= nums.length; i++) {
            preSum[i] = preSum[i - 1] + nums[i - 1];
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            for (int j = i; j < nums.length; j++) {
                if (preSum[j + 1] - preSum[i] == k) {
                    res++;
                }
            }
        }
        return res;
    }

    
    public int subarraySum(int[] nums, int k) {
        // 前缀和数组 + hashmap 时间复杂度O(N)
        if(nums.length == 0) return 0;
        Map<Integer, Integer> preSumMap = new HashMap<>(); // key为preSum, value为preSum的个数
        preSumMap.put(0, 1);
        int preSum = 0, res = 0;
        for (int num : nums) {
            preSum += num;
            if (preSumMap.containsKey(preSum - k)) { // preSum - (preSum - k) = k
                res += preSumMap.get(preSum - k);   
            }
            preSumMap.put(preSum, preSumMap.getOrDefault(preSum, 0) + 1);
        } 

        return res;
    }
}

2.差分数组

2.1 求差分数组的模板

public int[] getDiffArray(int[] nums) {
    if (nums.length == 0) return nums;
    int[] diff = new int[nums.length]; // 跟数组的长度一致
    diff[0] = nums[0];
    for (int i = 1; i < nums.length; i++) {
        diff[i] = nums[i] - nums[i - 1];
    }
    return diff;
}

2.2 根据差分数组求原数组的模板

public int[] getOriginArray(int[] diff) {
    int[] res = new int[diff.length];
    res[0] = diff[0];
    for (int i = 1; i < diff.length; i++) {
        res[i] = res[i - 1] + diff[i];
    }
    return res;
}

2.3 对连续区间[left, right]增加val之后的差分数组

diff[left] += val;
if (right < nums.length - 1) { // 只有在右边界小于nums.length - 1时才改变差分数组
    diff[right + 1] -= val;
}

2.3 题目特点

适用场景:频繁对原始数组的某个区间进行增减

2.4 典型题目

img

public int[] getResArray(int length, int[][] updates) {
    if (length == 0) return new int[]{};
    int[] res = new int[length];
    int[] diff = getDiffArray(res);
    for (int[] update : updates) {
        int left = update[0];
        int right = update[1];
        int val = update[2];
        diff[left] += val;
        if (right < length - 1) {
            diff[right + 1] -= val;
        }
    }
    return getOringinArray(diff);
}

1109. 航班预订统计

这里有 n 个航班,它们分别从 1 到 n 进行编号。

有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。

请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

示例 1:

输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
输出:[10,55,45,25,25]
解释:
航班编号 1 2 3 4 5
预订记录 1 : 10 10
预订记录 2 : 20 20
预订记录 3 : 25 25 25 25
总座位数: 10 55 45 25 25
因此,answer = [10,55,45,25,25]
示例 2:

输入:bookings = [[1,2,10],[2,2,15]], n = 2
输出:[10,25]
解释:
航班编号 1 2
预订记录 1 : 10 10
预订记录 2 : 15
总座位数: 10 25
因此,answer = [10,25]

提示:

1 <= n <= 2 * 104
1 <= bookings.length <= 2 * 104
bookings[i].length == 3
1 <= firsti <= lasti <= n
1 <= seatsi <= 104

class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] res = new int[n];
        int[] diff = new int[n];
        for (int[] booking : bookings) {
            int left = booking[0] - 1; // 区间这里容易出错
            int right = booking[1] - 1;
            int val = booking[2];
            diff[left] += val;
            if (right < n - 1) {
                diff[right + 1] -= val;
            }
        }
        return getOringinArray(diff);
    }

    private int[] getOringinArray(int[] diff) {
        if (diff.length == 0) return diff;
        int[] res = new int[diff.length];
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}

1094. 拼车

假设你是一位顺风车司机,车上最初有 capacity 个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。

这儿有一份乘客行程计划表 trips[][],其中 trips[i] = [num_passengers, start_location, end_location] 包含了第 i 组乘客的行程信息:

必须接送的乘客数量;
乘客的上车地点;
以及乘客的下车地点。
这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。

请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所有乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false)。

示例 1:

输入:trips = [[2,1,5],[3,3,7]], capacity = 4
输出:false
示例 2:

输入:trips = [[2,1,5],[3,3,7]], capacity = 5
输出:true
示例 3:

输入:trips = [[2,1,5],[3,5,7]], capacity = 3
输出:true
示例 4:

输入:trips = [[3,2,7],[3,7,9],[8,3,9]], capacity = 11
输出:true

提示:

你可以假设乘客会自觉遵守 “先下后上” 的良好素质
trips.length <= 1000
trips[i].length == 3
1 <= trips[i][0] <= 100
0 <= trips[i][1] < trips[i][2] <= 1000
1 <= capacity <= 100000

class Solution {
    public boolean carPooling(int[][] trips, int capacity) {
        // 解法:差分数组,连续区间增加操作后求数组最大数与capacity比较结果
        int len = 0;
        for (int[] trip : trips) {
            len = Math.max(len, trip[2]);
        }
        int[] diff = new int[len + 1];
        for (int[] trip : trips) {
            int left = trip[1];
            int right = trip[2] - 1; // 到这一站下车,因此前边的站都加旅客数量
            int val = trip[0];
            diff[left] += val;
            if (right < len) {
                diff[right + 1] -= val;
            }
        }
        int[] res = getOriginArray(diff);
        return capacity >= Arrays.stream(res).max().getAsInt();
    }

    private int[] getOriginArray(int[] diff) {
        if (diff.length == 0) return diff;
        int[] res = new int[diff.length];
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}
posted @ 2022-03-12 17:53  freryc  阅读(29)  评论(0编辑  收藏  举报