算法拾遗二十七之窗口最大值或最小值的更新结构

news2024/9/25 5:23:34

算法拾遗二十七之窗口最大值或最小值的更新结构

      • 滑动窗口
      • 题目一
      • 题目二
      • 题目三
      • 题目四

滑动窗口

第一种:R++,R右动,数会从右侧进窗口
第二种:L++,L右动,数从左侧出窗口

题目一

在这里插入图片描述
arr是N,窗口大小为W,会返回N-W+1个数

	// 暴力的对数器方法
	public static int[] right(int[] arr, int w) {
		if (arr == null || w < 1 || arr.length < w) {
			return null;
		}
		int N = arr.length;
		int[] res = new int[N - w + 1];
		int index = 0;
		//第一个窗口0-(w-1)
		int L = 0;
		int R = w - 1;
		while (R < N) {
			int max = arr[L];
			for (int i = L + 1; i <= R; i++) {
				max = Math.max(max, arr[i]);

			}
			res[index++] = max;
			L++;
			R++;
		}
		return res;
	}
	public static int[] getMaxWindow(int[] arr, int w) {
		if (arr == null || w < 1 || arr.length < w) {
			return null;
		}
		// qmax 双端队列就是窗口最大值的更新结构
		// 里面放下标方便更新
		LinkedList<Integer> qmax = new LinkedList<Integer>();
		//N-w+1
		int[] res = new int[arr.length - w + 1];
		int index = 0;
		//R表示窗口的右边界
		for (int R = 0; R < arr.length; R++) {
			//双端队列上最尾巴上的元素是不是小于当前的值的,如果小于等于则从尾巴弹出
			while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
				qmax.pollLast();
			}
			qmax.addLast(R);
			//窗口的过期数的下标
			if (qmax.peekFirst() == R - w) {
				qmax.pollFirst();
			}
			//形成窗口的时候,表示需要收集答案,每一步都收集一个答案
			if (R >= w - 1) {
				res[index++] = arr[qmax.peekFirst()];
			}
		}
		return res;
	}

题目二

在这里插入图片描述
思路:
如果L-R已经达标了,那么L-R内部的所有子数组都是达标的,因为L-R范围内部的子数组max在变小,min在变大。
如果L-R范围不达标,那么L往左扩大范围和R往右边扩大范围都不达标。

准备一个窗口内最大值的更新结构,同样准备窗口内最小值的更新结构。【同时使用两个结构维持窗口内的最大和最小】
步骤:
1、L不动,R如果满足【L-R范围的最大值和最小值的差值如果都满足小于等于num】则R一直往右边扩
,直到不满足条件为止。(注意此时是L在0为止,算出从零开始的子数组有多少个累加进结果)
2、L向右移动一位,看看R能不能往右扩,再算从L+1位置到能构成条件的子数组有多少个,再累加进结果。

	// 暴力的对数器方法
	public static int right(int[] arr, int sum) {
		if (arr == null || arr.length == 0 || sum < 0) {
			return 0;
		}
		int N = arr.length;
		int count = 0;
		for (int L = 0; L < N; L++) {
			for (int R = L; R < N; R++) {
				int max = arr[L];
				int min = arr[L];
				for (int i = L + 1; i <= R; i++) {
					max = Math.max(max, arr[i]);
					min = Math.min(min, arr[i]);
				}
				if (max - min <= sum) {
					count++;
				}
			}
		}
		return count;
	}

	public static int num(int[] arr, int sum) {
		if (arr == null || arr.length == 0 || sum < 0) {
			return 0;
		}
		int N = arr.length;
		int count = 0;
		LinkedList<Integer> maxWindow = new LinkedList<>();
		LinkedList<Integer> minWindow = new LinkedList<>();
		int R = 0;
		for (int L = 0; L < N; L++) {
			//依次尝试窗口为0开始,1开始。。。。
			while (R < N) {
				//R扩到初次不达标的时候停
				while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]) {
					//维护从大到小列表
					maxWindow.pollLast();
				}
				maxWindow.addLast(R);
				while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]) {
					//维护从小到大列表
					minWindow.pollLast();
				}
				minWindow.addLast(R);
				//最大值减去最小值大于sum则初次不达标了停止
				if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) {
					break;
				} else {
					R++;
				}
			}
			//加结果
			count += R - L;
			//如果L即将过期则弹出窗口对应值
			if (maxWindow.peekFirst() == L) {
				maxWindow.pollFirst();
			}
			if (minWindow.peekFirst() == L) {
				minWindow.pollFirst();
			}
		}
		return count;
	}

	// for test
	public static int[] generateRandomArray(int maxLen, int maxValue) {
		int len = (int) (Math.random() * (maxLen + 1));
		int[] arr = new int[len];
		for (int i = 0; i < len; i++) {
			arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1));
		}
		return arr;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr != null) {
			for (int i = 0; i < arr.length; i++) {
				System.out.print(arr[i] + " ");
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		int maxLen = 100;
		int maxValue = 200;
		int testTime = 100000;
		System.out.println("测试开始");
		for (int i = 0; i < testTime; i++) {
			int[] arr = generateRandomArray(maxLen, maxValue);
			int sum = (int) (Math.random() * (maxValue + 1));
			int ans1 = right(arr, sum);
			int ans2 = num(arr, sum);
			if (ans1 != ans2) {
				System.out.println("Oops!");
				printArray(arr);
				System.out.println(sum);
				System.out.println(ans1);
				System.out.println(ans2);
				break;
			}
		}
		System.out.println("测试结束");

	}

题目三

在这里插入图片描述
给定两个数组一个是gas数组,一个是cost数组,
0号加油站有1单位的油,但是跑到加油站2需要耗费两单位的油,从哪个加油站开始能转完一圈?
在这里插入图片描述
如上图,只能通过从C出发才能跑完一圈
思路:
加工出一个arr数组,就是gas-cost数组,做一个简化
在这里插入图片描述
再看如下例子
在这里插入图片描述
暴力解:搞定循环数组的遍历,从0位置出发找是否有解,从1位置出发找是否有解依次找下去
方法二:
1、先算累加和:
在这里插入图片描述
这样写的目的:从零出发一直累加到五之前,可以判断从0位置出发不是良好出发点,再看从1位置出发依次累加
在这里插入图片描述
里面包含负数可以知道它不是一个良好出发点,如上图这个数组就是算从零位置开始计算的累加和得到的那个数组减去-2得到的数组。
做一个两倍长度的数组就是想让它所有原始数组中出发的那个位置,在长数组中能把它原始的累加和给加工出来
则每次计算后续的出发点的数组内容时,则减去前一个就能得到了
在这里插入图片描述
搞一个窗口,这个窗口就是原始数组的长度,窗口的最小值(最薄弱点)是-5减去出发位置前面一个数,从而还原出原始数组累加和最薄弱点,还原出的结果还是小于0则不是良好出发点,还原出的结果不小于0则是良好出发点

如0位置出发薄弱点为-5,还原出原始累加和为-5-0还是小于0不是良好出发点。

这道题,在这里插入图片描述
要想到窗口往最大值和最小值上面靠,进而想到累加和最薄弱的点

// 测试链接:https://leetcode.com/problems/gas-station
public class GasStation {

	// 这个方法的时间复杂度O(N),额外空间复杂度O(N)
	public static int canCompleteCircuit(int[] gas, int[] cost) {
		boolean[] good = goodArray(gas, cost);
		for (int i = 0; i < gas.length; i++) {
			if (good[i]) {
				return i;
			}
		}
		return -1;
	}

	public static boolean[] goodArray(int[] g, int[] c) {
		int N = g.length;
		int M = N << 1;
		int[] arr = new int[M];
		for (int i = 0; i < N; i++) {
			arr[i] = g[i] - c[i];
			arr[i + N] = g[i] - c[i];
		}
		for (int i = 1; i < M; i++) {
			arr[i] += arr[i - 1];
		}
		LinkedList<Integer> w = new LinkedList<>();
		for (int i = 0; i < N; i++) {
			while (!w.isEmpty() && arr[w.peekLast()] >= arr[i]) {
				w.pollLast();
			}
			w.addLast(i);
		}
		boolean[] ans = new boolean[N];
		for (int offset = 0, i = 0, j = N; j < M; offset = arr[i++], j++) {
			if (arr[w.peekFirst()] - offset >= 0) {
				ans[i] = true;
			}
			if (w.peekFirst() == i) {
				w.pollFirst();
			}
			while (!w.isEmpty() && arr[w.peekLast()] >= arr[j]) {
				w.pollLast();
			}
			w.addLast(j);
		}
		return ans;
	}

}

题目四

在这里插入图片描述

public class Code04_MinCoinsOnePaper {

	public static int minCoins(int[] arr, int aim) {
		return process(arr, 0, aim);
	}

	public static int process(int[] arr, int index, int rest) {
		if (rest < 0) {
			return Integer.MAX_VALUE;
		}
		if (index == arr.length) {
			return rest == 0 ? 0 : Integer.MAX_VALUE;
		} else {
			int p1 = process(arr, index + 1, rest);
			int p2 = process(arr, index + 1, rest - arr[index]);
			if (p2 != Integer.MAX_VALUE) {
				p2++;
			}
			return Math.min(p1, p2);
		}
	}

	// dp1时间复杂度为:O(arr长度 * aim)
	public static int dp1(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		int N = arr.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 0; rest <= aim; rest++) {
				int p1 = dp[index + 1][rest];
				int p2 = rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : Integer.MAX_VALUE;
				if (p2 != Integer.MAX_VALUE) {
					p2++;
				}
				dp[index][rest] = Math.min(p1, p2);
			}
		}
		return dp[0][aim];
	}

	public static class Info {
		public int[] coins;
		public int[] zhangs;

		public Info(int[] c, int[] z) {
			coins = c;
			zhangs = z;
		}
	}

	public static Info getInfo(int[] arr) {
		HashMap<Integer, Integer> counts = new HashMap<>();
		for (int value : arr) {
			if (!counts.containsKey(value)) {
				counts.put(value, 1);
			} else {
				counts.put(value, counts.get(value) + 1);
			}
		}
		int N = counts.size();
		int[] coins = new int[N];
		int[] zhangs = new int[N];
		int index = 0;
		for (Entry<Integer, Integer> entry : counts.entrySet()) {
			coins[index] = entry.getKey();
			zhangs[index++] = entry.getValue();
		}
		return new Info(coins, zhangs);
	}

	// dp2时间复杂度为:O(arr长度) + O(货币种数 * aim * 每种货币的平均张数)
	public static int dp2(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		// 得到info时间复杂度O(arr长度)
		Info info = getInfo(arr);
		int[] coins = info.coins;
		int[] zhangs = info.zhangs;
		int N = coins.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		// 这三层for循环,时间复杂度为O(货币种数 * aim * 每种货币的平均张数)
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 0; rest <= aim; rest++) {
				dp[index][rest] = dp[index + 1][rest];
				for (int zhang = 1; zhang * coins[index] <= aim && zhang <= zhangs[index]; zhang++) {
					if (rest - zhang * coins[index] >= 0
							&& dp[index + 1][rest - zhang * coins[index]] != Integer.MAX_VALUE) {
						dp[index][rest] = Math.min(dp[index][rest], zhang + dp[index + 1][rest - zhang * coins[index]]);
					}
				}
			}
		}
		return dp[0][aim];
	}

	// dp3时间复杂度为:O(arr长度) + O(货币种数 * aim)
	// 优化需要用到窗口内最小值的更新结构
	public static int dp3(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		// 得到info时间复杂度O(arr长度)
		Info info = getInfo(arr);
		int[] c = info.coins;
		int[] z = info.zhangs;
		int N = c.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		// 虽然是嵌套了很多循环,但是时间复杂度为O(货币种数 * aim)
		// 因为用了窗口内最小值的更新结构
		for (int i = N - 1; i >= 0; i--) {
			for (int mod = 0; mod < Math.min(aim + 1, c[i]); mod++) {
				// 当前面值 X
				// mod  mod + x   mod + 2*x   mod + 3 * x
				LinkedList<Integer> w = new LinkedList<>();
				w.add(mod);
				dp[i][mod] = dp[i + 1][mod];
				for (int r = mod + c[i]; r <= aim; r += c[i]) {
					while (!w.isEmpty() && (dp[i + 1][w.peekLast()] == Integer.MAX_VALUE
							|| dp[i + 1][w.peekLast()] + compensate(w.peekLast(), r, c[i]) >= dp[i + 1][r])) {
						w.pollLast();
					}
					w.addLast(r);
					int overdue = r - c[i] * (z[i] + 1);
					if (w.peekFirst() == overdue) {
						w.pollFirst();
					}
					dp[i][r] = dp[i + 1][w.peekFirst()] + compensate(w.peekFirst(), r, c[i]);
				}
			}
		}
		return dp[0][aim];
	}

	public static int compensate(int pre, int cur, int coin) {
		return (cur - pre) / coin;
	}

	// 为了测试
	public static int[] randomArray(int N, int maxValue) {
		int[] arr = new int[N];
		for (int i = 0; i < N; i++) {
			arr[i] = (int) (Math.random() * maxValue) + 1;
		}
		return arr;
	}

	// 为了测试
	public static void printArray(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	// 为了测试
	public static void main(String[] args) {
		int maxLen = 20;
		int maxValue = 30;
		int testTime = 300000;
		System.out.println("功能测试开始");
		for (int i = 0; i < testTime; i++) {
			int N = (int) (Math.random() * maxLen);
			int[] arr = randomArray(N, maxValue);
			int aim = (int) (Math.random() * maxValue);
			int ans1 = minCoins(arr, aim);
			int ans2 = dp1(arr, aim);
			int ans3 = dp2(arr, aim);
			int ans4 = dp3(arr, aim);
			if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) {
				System.out.println("Oops!");
				printArray(arr);
				System.out.println(aim);
				System.out.println(ans1);
				System.out.println(ans2);
				System.out.println(ans3);
				System.out.println(ans4);
				break;
			}
		}
		System.out.println("功能测试结束");

		System.out.println("==========");

		int aim = 0;
		int[] arr = null;
		long start;
		long end;
		int ans2;
		int ans3;

		System.out.println("性能测试开始");
		maxLen = 30000;
		maxValue = 20;
		aim = 60000;
		arr = randomArray(maxLen, maxValue);

		start = System.currentTimeMillis();
		ans2 = dp2(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp2答案 : " + ans2 + ", dp2运行时间 : " + (end - start) + " ms");

		start = System.currentTimeMillis();
		ans3 = dp3(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp3答案 : " + ans3 + ", dp3运行时间 : " + (end - start) + " ms");
		System.out.println("性能测试结束");

		System.out.println("===========");

		System.out.println("货币大量重复出现情况下,");
		System.out.println("大数据量测试dp3开始");
		maxLen = 20000000;
		aim = 10000;
		maxValue = 10000;
		arr = randomArray(maxLen, maxValue);
		start = System.currentTimeMillis();
		ans3 = dp3(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp3运行时间 : " + (end - start) + " ms");
		System.out.println("大数据量测试dp3结束");

		System.out.println("===========");

		System.out.println("当货币很少出现重复,dp2比dp3有常数时间优势");
		System.out.println("当货币大量出现重复,dp3时间复杂度明显优于dp2");
		System.out.println("dp3的优化用到了窗口内最小值的更新结构");
	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ linux下获取时间戳 秒、微妙、纳秒

1.例子#include <iostream>#include <sys/time.h>#include <cstdlib>#include <cstdio>#include <ctime>#include <cmath>#include <unistd.h>usingnamespace std;time_t clocktime(){time_t t time(NULL);std::cout << &quo…

聚势合力,电巢与SDIA协会“战略合作签约仪式”圆满落成

前言&#xff1a; 2023年03月02日下午&#xff0c;电巢科技与深圳市平板显示行业协会齐聚深圳南山电巢XR演播厅&#xff0c;共同举办了隆重的战略合作签约仪式。 双方就数字化建设、品牌赋能、人才培养、技术创新等企业服务深入合作上达成一致&#xff0c;合力为产业赋能&…

【OJ比赛日历】快周末了,不来一场比赛吗? #03.11-03.17 #12场

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01;更多比赛信息见 CompHub主页 或 点击文末阅读原文以下信息仅供参考&#xff0c;以比赛官网为准目录2023-03-11&…

netty-websocket 鉴权token及统一请求和响应头(鉴权控制器)

自己想法和实现&#xff0c;如果有说错的或者有更好的简单的实现方式可以私信交流一下(主要是实现握手时鉴权) 需求实现 握手鉴权是基于前台请求头 Sec-WebSocket-Protocol的本身socket并没有提供自定义请求头&#xff0c;只能自定义 Sec-WebSocket-Protocol的自协议 问题描述…

你几乎不知道的浏览器内置对象/事件/ajax

浏览器内置对象/事件/ajax 浏览器是⼀个 JS 的运⾏时环境&#xff0c;它基于 JS 解析器的同时&#xff0c;增加了许多环境相关的内容。⽤⼀张图表示各个运⾏环境和 JS 解析器的关系如下&#xff1a; 我们把常⻅的&#xff0c;能够⽤ JS 这⻔语⾔控制的内容称为⼀个 JS 的运⾏环…

Leetcode—环形链表

前言&#xff1a;给定一个链表&#xff0c;判断是否为循环链表并找环形链表的入口点 首先我们需要知道什么是双向循环链表&#xff0c;具体如下图所示。 对于链表&#xff0c;我们如何去判断链表是循环链表呢&#xff1f;又寻找入环点呢&#xff1f;我们可以利用快慢指针的方法…

Meta CTO:Quest 2生命周期或比预期更久

前不久&#xff0c;Meta未来4年路线图遭曝光&#xff0c;泄露了该公司正在筹备中的一些AR/VR原型。除此之外&#xff0c;还有消息称Quest Pro或因销量不佳&#xff0c;而不再迭代。毫无疑问&#xff0c;Meta的一举一动持续受到行业关注&#xff0c;而面对最近的爆料&#xff0c…

关于指针运算的一道题

目录 刚看到这道题的时候我也和大多数小白一样感到无从下手&#xff0c;但是在我写这篇博客的前几分钟开始我对这道题有了一点点的理解。所以我就想着趁热打铁&#xff0c;写一篇博客来记录一下我的想法。 题目如下&#xff1a; 画图&#xff1a; 逐一解答&#xff1a; 题一…

笔记:二叉树

学习了二叉树&#xff0c;今天来整理一下笔记吧&#xff01;一&#xff1a;树的理解树的度是不限制的&#xff0c;一个双亲结点可以有很多子节点&#xff0c;但是子节点是不能交叉的&#xff0c;也就是不能有环。树的的最大层次叫做这棵树的深度或者高度&#xff1b;树的代码表…

高三应该怎么复习

高三是学生们备战高考的重要一年&#xff0c;正确有序的复习可以有效地提高复习效率&#xff0c;下面是一些高效复习的方法和建议&#xff1a;1. 制定合理的学习计划和目标高三的学生要制定合理的学习计划和目标&#xff0c;适当的计划和目标可以使学习更有针对性和效率。建议根…

本地部署dynamics

打开虚拟机的dynamics部署管理&#xff0c;点击组织再点击浏览 进入网址&#xff0c;输入账号密码 登录成功 点销售旁边的下箭头&#xff0c;选择设置中的系统作业 成功进入到系统作业 进入这个网址 Dynamics 365 客户参与快速入门&#xff08;本地&#xff09;&#xff08;Dy…

VRRP与BFD联动配置

VRRP与BFD联动配置 1. 实验目的 熟悉VRRP与BFD联动的应用场景掌握VRRP与BFD联动的配置方法2. 实验拓扑 实验拓扑如图14-13所示: 图14-13:VRRP与BFD联动配置 3. 实验步骤 配置IP地址PC1的配置 PC1的配置如图14-14所示:

Python 协程详解,都在这里了

什么是协程 协程&#xff08;co-routine&#xff0c;又称微线程、纤程&#xff09; 是一种多方协同的工作方式。 协程不是进程或线程&#xff0c; 其执行过程类似于 Python 函数调用&#xff0c; Python 的 asyncio 模块实现的异步IO编程框架中&#xff0c; 协程是对使用 asy…

毕设常用模块之舵机介绍以及使用方法

舵机 舵机是一种位置伺服的驱动器&#xff0c;主要是由外壳、电路板、无核心马达、齿轮与位置检测器所构成。其工作原理是由接收机或者单片机发出信号给舵机&#xff0c;其内部有一个基准电路&#xff0c;产生周期为 20ms&#xff0c;宽度为 1.5ms 的基准信号&#xff0c;将获…

易优cms user 登录注册标签

user 登录注册标签 user 登录注册入口标签 [基础用法] 标签&#xff1a;user 描述&#xff1a;动态显示购物车、登录、注册、退出、会员中心的入口&#xff1b; 用法&#xff1a; {eyou:user typeuserinfo} <div id"{$field.htmlid}"> …

如何用项目管理软件,帮助项目经理监控进度?

项目无论规模大小&#xff0c;都要处理许多任务&#xff0c;管理项目文档&#xff0c;监控任务进度等。 有一个方法可以帮助项目经理在制定计划和项目推进时确保一切保持井井有条。 项目管理软件是最有用的工具之一&#xff0c;通常被用于项目计划、时间管理等&#xff0c;能在…

我国近视眼的人数已经超过了六亿,国老花眼人数超过三亿人

眼镜是一种用于矫正视力问题、改善视力、减轻眼睛疲劳的光学器件&#xff0c;在我们的生活中不可忽略的一部分&#xff0c;那么我国眼镜市场发展情况是怎样了&#xff1f;下面小编通过可视化互动平台对我国眼镜市场的状况进行分析。我国是一个近视眼高发的国家&#xff0c;据统…

【MFA】windows环境下,使用Montreal-Forced-Aligner训练并对齐音频

文章目录一、安装MFA1.安装anaconda2.创建并进入虚拟环境3.安装pyTorch二、训练新的声学模型1.确保数据集的格式正确2.训练声音模型-导出模型和对齐文件3.报错处理1.遇到类似&#xff1a; Command ‘[‘createdb’,–host‘ ’, ‘Librispeech’]’ returned non-zero exit sta…

我一个普通程序员,光靠GitHub打赏就年入70万,

一个国外程序员名叫 Caleb Porzio在网上公开了自己用GitHub打赏年入70万的消息和具体做法。 Caleb Porzio 发推庆祝自己靠 GitHub 打赏&#xff08;GitHub Sponsors&#xff09;赚到了 10 万美元。 GitHub Sponsors是 GitHub 2019 年 5 月份推出的一个功能&#xff0c;允许开发…

SpringBatch简介

参考&#xff1a;https://cloud.tencent.com/developer/article/1456757简介SpringBatch主要是一个轻量级的大数据量的并行处理(批处理)的框架。作用和Hadoop很相似&#xff0c;不过Hadoop是基于重量级的分布式环境(处理巨量数据)&#xff0c;而SpringBatch是基于轻量的应用框架…