2022.8.12
Leetcode 128 用户分组
思路:我们用哈希表存储 键:不同大小的组 值:组内元素 (用一个链表或者ArrayList)
如果一个哈希表的组内元素个数 == 它的组的大小 则说明我们当前组加满了,可以塞进答案中了
遍历所有的点,重复操作即可 (时间复杂度 O(N))
class Solution {
public List<List<Integer>> groupThePeople(int[] groupSizes) {
Map<Integer,ArrayList<Integer>> map = new HashMap<>();
List<List<Integer>> 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<Integer> 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<Integer,Integer> 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);
}
}