数组
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 典型题目
给定一个整数数组 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);
*/
给你一个整数数组 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 典型题目
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);
}
这里有 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;
}
}
假设你是一位顺风车司机,车上最初有 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;
}
}