2022.8.12
Leetcode 128 用户分组

思路:我们用哈希表存储 键:不同大小的组 值:组内元素 (用一个链表或者ArrayList)
如果一个哈希表的组内元素个数 == 它的组的大小 则说明我们当前组加满了,可以塞进答案中了
遍历所有的点,重复操作即可 (时间复杂度 O(N))
- class Solution {
- public List
> groupThePeople(int[] groupSizes) {
- Map
> map = new HashMap<>(); - List
> res = new ArrayList<>();
- for (int i = 0; i < groupSizes.length; i ++) {
- int j = groupSizes[i];
- if (map.get(j) == null) { // 说明还没有ArrayList
- map.put(j,new ArrayList());
- }
- map.get(j).add(i); // 把元素加入到组内
- if (map.get(j).size() == j) { // 如果组满了就重开组
- res.add(map.get(j));
- map.remove(j);
- }
- }
- return res;
- }
- }
2022.8.13
Leetcode 768 最多能完成排序的块
768. 最多能完成排序的块 II - 力扣(LeetCode)

思路1:假设我们有一个排序后的组,如果对一段从0开始的区间 它的元素种类和 我们未排序的块一样,那我们可以排序的次数就要加1,因此我们可以用一个hash表来存储,未排序复杂对应元素++,排序复杂元素-- ,如果一段区间元素的个数都是0,则res++(时间复杂度 O(N * logN))
思路二:单调栈的利用,假设我们当前元素i的前边元素都已经分好块了,如果我们 i 小于 上一个块的最大值,则说明上一个块其实没有完全分好(它必须加上我们的i)如果我们的 i 大于上一个元素,则我们的 i 可以成为一个新的块(时间复杂度 O(N))
思路三:很巧妙的利用前缀和的思想,我们可以发现 可以分块的区间它的前缀和都是和排完序之后的区间前缀和相等的(元素一样,和肯定相等)所以我们可以统计有多少次前缀和相等即可(时间复杂度 O(N * Log N))
单调栈代码:
- class Solution {
- public int maxChunksToSorted(int[] arr) {
- Deque
stk = new LinkedList<>(); - for (int x: arr) {
- int t = x;
- while (!stk.isEmpty() && stk.peek() > x) { // 把不合法的区间合成一段
- t = Math.max(t,stk.peek());
- stk.pop();
- }
- stk.push(t);
- }
- return stk.size();
- }
- }
前缀和代码:
- class Solution {
- public int maxChunksToSorted(int[] arr) {
- int[] sum = Arrays.copyOf(arr,arr.length); // 复制加排序
- Arrays.sort(sum);
- for (int i = 0; i < arr.length; i ++) {
- if (i != 0) { // 前缀和的处理
- sum[i] += sum[i - 1];
- arr[i] += arr[i - 1];
- }
- }
- int res = 0;
- for (int i = 0; i < arr.length; i ++) {
- if (sum[i] == arr[i]) res ++;
- }
- return res;
- }
- }
Hash表代码:
- class Solution {
- public int maxChunksToSorted(int[] arr) {
- int[] a = Arrays.copyOf(arr,arr.length);
- Arrays.sort(a);
- Map
map = new HashMap<>(); - int res = 0;
- for (int i = 0,cnt = 0; i < arr.length; i ++) {
- map.put(arr[i],map.getOrDefault(arr[i],0) + 1);
- if (map.getOrDefault(arr[i],0) == 0) cnt --;
- else if (map.getOrDefault(arr[i],0) == 1)cnt ++;
- map.put(a[i],map.getOrDefault(a[i],0) - 1);
- if (map.getOrDefault(a[i],0) == 0) cnt --;
- else if (map.getOrDefault(a[i],0) == -1) cnt ++;
- if (cnt == 0) res ++;
- }
- return res;
- }
- }
2022.8.14
acwing 4405 统计子矩阵个数
c++ 蓝桥杯省赛
思路:n,m 都是 500 级别 暴力枚举矩阵四个边界时间复杂度是 O(N^4) 肯定超时了
因此考虑双指针优化,固定 一个有边界 r , 左边界 l 定义为最靠左的矩阵之和不超过 k 的边,当 r往右, l 也只能往右 因此得到对于 l 和 r 的单调函数 ,可以采用双指针优化
预处理前缀和的复杂度是O(N) 的,由于我们固定上下边界,我们只需要处理每一列的前缀和
代码:
- import java.util.*;
-
- public class Main{
- static int N = 510;
- static int[][] s = new int[N][N];
-
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- int n = sc.nextInt(), m = sc.nextInt(), k = sc.nextInt();
- for (int i = 1; i <= n; i ++) {
- for (int j = 1; j <= m; j ++) {
- s[i][j] = sc.nextInt();
- s[i][j] += s[i - 1][j];
- }
- }
- long res = 0;
- for (int i = 1; i <= n; i ++) {
- for (int j = i; j <= n; j ++) {
- for (int l = 1,r = 1,sum = 0; r <= m; r ++) {
- sum += s[j][r] - s[i - 1][r]; // 加上最右边的一列
- while (sum > k) {
- sum -= s[j][l] - s[i - 1][l]; // 总和超过 k 了 需要把最左边的一列剔除
- l ++;
- }
- res += r - l + 1; // 总和没有超过k 此时l就是对于r 最靠左的一条边界 ,r 可以++
- }
- }
- }
- System.out.print(res);
- }
- }