差分
差分基本概念及其性质 :可以看看这个视频 b站up 星垂月朦胧
给定一个数组 a ,其中 a = [a1, a2, ..., an],我们定义差分数组 d 如下:
d[i] = a[i] - a[i-1]
其中,d[1] = a[1]。这个差分数组 d记录了原数组 a 相邻元素之间的差值(ai+1-ai)。通过计算差分数组的前缀和,我们可以还原原数组 a。
例如,给定数组 a = [1, 3, 7, 5, 2],我们可以计算其差分数组 d 如下:
d = [1, 2, 4, -2, -3]
通过对差分数组 d 计算前缀和,可以还原原数组 a:
s[d] = [1, 3, 7, 5, 2]
差分数组的一个重要性质是可以用来简化区间操作。例如,对于区间 [L, R] 上的所有元素加上一个值 v,可以等价为两个点的操作:
d[L] += v d[R+1] -= v
这个操作表示从位置 L 开始增加 v,并从位置 R+1 开始减少 v,从而实现对整个区间 [L, R] 的更新。
模板一
package com.coedes.difference.template; import java.util.Arrays; /** * @description:from https://www.bilibili.com/video/BV1pi4y1j7si?p=2&vd_source=4accc3f927b8b6579f872125bad11046 * @author: wenLiu * @create: 2024/4/19 23:00 */ public class Template1 { static int[] d = new int[6]; // 初始化长度默认n+1防止d[r+1]操作溢出,默认元素值为0 // 定义add函数 static void add(int l, int r, int v) { d[l] += v; //初始化长度默认n+1 防止d[r+1]操作溢出无需特判 /*if (r + 1 < d.length) { d[r + 1] -= v; }*/ d[r + 1] -= v; } public static void main(String[] args) { int[] arr = {1, 3, 7, 5, 2}; add(2, 4, 5); add(1, 3, 2); add(0, 2, -3); // 对d数组进行前缀和处理 for (int i = 1; i < d.length; i++) { d[i] += d[i - 1]; } // 输出处理后的arr数组 for (int i = 0; i < arr.length; i++) { arr[i] += d[i]; } // 打印结果 for (int num : arr) { System.out.print(num + " "); } Arrays.fill(d, 0); } }
模板二:
import java.util.Scanner; public class Template2 { int[] b = new int[100010]; public void insert(int l ,int r ,int c){ b[l] += c; b[r+1] -= c; } public static void main(String[] args){ new Template2().test(); } public void test(){ Scanner scan = new Scanner(System.in); int n = scan.nextInt(); int m = scan.nextInt(); int[] a = new int[n+1]; for(int i = 1 ; i <= n ; i ++ ){ a[i] = scan.nextInt(); } //b[1] = b[1]+a[1] = 0 + a[1] = a[1] , b[2] = b[2]-a[1] = 0 - a[1] = -a[1] //b[2] = b[2]+a[2] = a[2]-a[1] , b[3] = b[3]-a[2] = -a[2]....
//insert(i,i,a[i]) 初始化差分数组 for(int i = 1;i <= n ; i ++ ){ insert(i,i,a[i]); } while(m-->0){ int l = scan.nextInt(); int r = scan.nextInt(); int c = scan.nextInt(); insert(l,r,c); } for(int i = 1;i <= n ; i ++ ){ a[i] = a[i-1] + b[i]; // a[i]-a[i-1] = b[i] => a[i] = a[i-1]+b[i] System.out.print(a[i] + " "); } } }
LeetCode 1094. 拼车
参考 该题解可以对差分基本概念有个了解。
class Solution { static int[] d; static void insert(int v, int l, int r) { d[l] += v; d[r] -= v; }
public boolean carPooling(int[][] trips, int capacity) { int maxTo = Integer.MIN_VALUE; for (int i = 0; i < trips.length; i++) maxTo = Math.max(maxTo, trips[i][2]); d = new int[maxTo+1]; for (int[] trip : trips) { insert(trip[0], trip[1], trip[2]); } int pre = 0; for (int i = 0; i <= maxTo; i++) { pre += d[i]; if (pre > capacity) { return false; } } return true; } }
class Solution { TreeMap<Integer, Integer> map; // TreeMap 代替d数组 避免下标考虑 static void insert(TreeMap<Integer, Integer> map, int v, int l, int r) { map.put(l, map.getOrDefault(l, 0) + v); map.put(r, map.getOrDefault(r, 0) - v); } public boolean carPooling(int[][] trips, int capacity) { map = new TreeMap<>(); for (int[] trip : trips) { insert(map, trip[0], trip[1], trip[2]); } int pre = 0; for (int key : map.keySet()) { int value = map.get(key); pre += value; if (pre > capacity) { return false; } } return true; } }
模板法:
class Solution { static int[] d; static void insert(int l, int r, int v) { d[l] += v; d[r + 1] -= v; } public int[] corpFlightBookings(int[][] bookings, int n) { d = new int[n + 2]; for (int[] booking : bookings) { insert(booking[0], booking[1], booking[2]); } int[] res = new int[n]; int preSum = 0; for (int i = 0; i < res.length; i++) { preSum += d[i+1]; res[i] = preSum; } return res; } }
public class Solution2 { // 压缩差分数组d public int[] corpFlightBookings(int[][] bookings, int n) { //因为原始数组为0 没必要另外开辟数组存放差分数组d int[] nums = new int[n]; for (int[] booking : bookings) { // 注意:这里要减1,因为nums数组的下标是从0开始的 // l = i-1 r+1 = i nums[booking[0] - 1] += booking[2]; if (booking[1] < n) { nums[booking[1]] -= booking[2]; } } for (int i = 1; i < n; i++) { nums[i] += nums[i - 1]; } return nums; } }
描述
1. 给定一个长度为 n 的只含小写字母的字符串。
2. 定义轮换操作 k,将字符串中下标属于[1,k]的每个小写字母向后移动一位,即 a -> b, b -> c, ..., z -> a。
3. 给出一个操作序列 k1, k2, ...,km表示连续对字符串进行轮换操作。
4. 求经过所有操作序列中的操作后,字符串的最终结果。
输入
第一行两个整数n,m(1 ≤ n,m ≤105)代表字符串长度以及轮换序列的长度
第二行为字符串s的初始状态
第三行有m个整数k(1 ≤ k i≤ n),代表操作序列里的每一次操作,以空格隔开。
输出
一行字符串,代表最终结果
样例
input
5 3 abcde 3 5 2
output
deeef
package com.coedes.difference.visa2023; import java.util.Scanner; /** * @description:from https://codefun2000.com/p/P1135 * @author: wenLiu * @create: 2024/4/20 12:46 */ public class Main { static int[] d; static void insert(int l, int r, int v) { d[l] += v; d[r + 1] -= v; } public static void main(String[] args) { /** * 5 3 * abcde * 3 5 2 */ Scanner in = new Scanner(System.in); int n = in.nextInt(); int m = in.nextInt(); String s = in.next(); // 初始化差分数组 d = new int[n + 2]; char[] chars = new char[n + 1]; for (int i = 0; i < s.length(); i++) { chars[i+1] = s.charAt(i); } int k; // 计算区间操作后的差分数组 for (int i = 0; i < m; i++) { k = in.nextInt(); calDifference(k); } // 求差分数组前缀和 for (int i = 1; i <= n; i++) { d[i] += d[i - 1]; } // 输出结果 for (int i = 1 ; i < chars.length; i++) { int u = chars[i] - 'a'; u = (u + d[i]) % 26; System.out.print((char) ('a' + u)); } } private static void calDifference(int k) { insert(1, k, 1); } }
- 监考规则:
- 只监视一个考场:每分钟收费3金币。
- 同时监视两个以上的考场:每分钟收费4金币。
- 当处于两个监考任务之间的空隙之间时,会进入待机状态,每分钟消耗1金币
- 输入数据:
- n:考场数量。1 ≤n ≤ 10000
- 每个考场的开始时间和结束时间。0 ≤ ai,bi ≤ 106
- 输出:
- 今天的监考任务所收取的金币总额。
样例1
输入
3 1 5 4 6 6 6
输出
21
思路:差分
某一时刻收取的金币值,是由当前时刻监考的考场数量决定的,题目给定每个考场的开始时间a
和结束时间b
,相当于对区间[a,b]
执行+1
操作
package com.coedes.difference.huawei2023041901; import java.util.Scanner; /** * @description:https://codefun2000.com/p/P1195 * @author: wenLiu * @create: 2024/4/20 13:52 */ public class Main { // 0 ≤ ai, bi ≤ 10e6 private static final int N = 1000010; //差分数组 private static int[] d; public static void main(String[] args) { initArrays(); long res = getRes(); System.out.println(res); } private static long getRes() { Scanner in = new Scanner(System.in); int n = in.nextInt(); int minL = Integer.MAX_VALUE; int maxR = Integer.MIN_VALUE; for (int i = 1; i <= n; i++) { int l1 = in.nextInt(); int r1 = in.nextInt(); // 差分数组 d[l1] += 1; d[r1 + 1] -= 1; minL = Math.min(l1, minL); maxR = Math.max(r1, maxR); } long pre = 0; long res = 0; for (int i = minL; i <= maxR; i++) { pre += d[i];//当前时间段同时存在的考试场数 if (pre == 0) { res++; } else if (pre == 1) { res += 3; } else { res += 4; } } return res; } private static void initArrays() { d = new int[N]; } }
2023美团-天文爱好者
流星出现时刻为s,消失时刻为t,如果在时间段[s, t]内,就能观测到这颗流星。
希望找到一个最佳观测时刻,观测到最多的流星数量。
同时,他想知道在这个最佳时刻,他最多能观测到多少个流星,以及有多少个这样的最佳时刻?
1 ≤n< 100000,1 <si<t¡< 109
package com.coedes.difference.meituan2023031102; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; /** * @description:https://codefun2000.com/p/P1078 * @author: wenLiu * @create: 2024/4/20 15:31 */ public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); int[] l = new int[n]; int[] r = new int[n]; String[] lInput = br.readLine().split(" "); String[] rInput = br.readLine().split(" "); // 读取 l 数组 for (int i = 0; i < n; i++) { l[i] = Integer.parseInt(lInput[i]); } // 读取 r 数组 for (int i = 0; i < n; i++) { r[i] = Integer.parseInt(rInput[i]); } // 构建差分数组 TreeMap<Integer, Integer> map = new TreeMap<>(); for (int i = 0; i < n; i++) { map.merge(l[i], 1, Integer::sum); // d[l]+=1 map.merge(r[i] + 1, -1, Integer::sum); // d[r+1]-=1 } // 统计每个时间点出现流星频数 {流星数量, 出现对应流星数量天数} TreeMap<Integer, Integer> cnts = new TreeMap<>(); int sum = 0; int beg = map.firstKey(); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { int key = entry.getKey(); int value = entry.getValue(); cnts.put(sum, cnts.getOrDefault(sum, 0) + (key - beg)); beg = key; sum += value; } // 找到最大的 cnts 中的值 int maxCnt = 0; int maxNum = 0; for (Map.Entry<Integer, Integer> entry : cnts.entrySet()) { int u = entry.getKey(); int v = entry.getValue(); if (u > maxCnt) { maxCnt = u; maxNum = v; } } System.out.println(maxCnt + " " + maxNum); } }