前缀和问题
对于 leetcode 上的一类问题:
一个数组中,连续子数组 ,满足一定的条件的问题,这一类问题基本上有 如下两种思路:
以 i 结尾 的 dp 数组应用,特别适合连续子数组;
滑动窗口;也特别适合连续数组的约束;
我约到的题目, 连续子数组 的 和 最大, 用 dp;
连续子数组 的和 与 k 的关系, 大于k,小于 k,等于k,通常就是 使用 前缀和数组;
前缀和 数组 在遍历的时候,可以生产, 然后可以用一些特殊的数据结构来加快 在前缀和中的查找;
比如 ==k 的 子数组的个数,可以使用 map 记录;
小于 k 的数, 可以使用一个 treeset 记录 前缀和元素, 能够做到 logN 的方式来查找。
另外一类就是 滑动窗口的问题, 通常也是可以使用额外的空间结构来记录 相应的特性元素,比如滑动的时候,记录最小值,使用最小堆; 记录元素出现的次数 来动态维护 相应的特性;
滑动窗口,连续子数组, 前缀和,辅助结构,基本上就能够处理 大多数的这类问题了。
甚至,都可以应用到 二维数组中; #### 363. 矩形区域不超过 K 的最大数值和
连续的 子矩阵,也是一个换皮的题目,可以使用 前缀和,只需要 遍历,行或者列。
另外一道题:
给你一个数组, 可以做K次操作,每次操作,可以加一, 然你求出 至多 k次操作之后, 出现频率最大的数;
这道题,可以排序,然后 遍历每个元素,假设最大频率元素为a[i], 然后可以向前 找 [l, r] 的区间,区间长度为 n,那么只需要 区间和 与 n * a[i] 的diff 小于 k 就行; 所以 可以利用前缀和辅助数组来帮助加快查询;
这是一道比较隐秘的 前缀和问题;
子数组最大最小值问题
1, 给定一个数组,对于其中的连续子数组;
比如设定连续的长度m; 有一类问题:
- 长度为m中的 最大值或者最小值的 当中最大或者最小的;
- 最大值或者最小值 的差值构成的数组中; 最大的是?
这两个问题,都可以通过 设置一个 logM 的最大最小堆来维护滑动窗口中的最大值或者最小值;来获得一个接近 o(N)的复杂度;
两外一类就是不设定M,M 是可以任意值的, 这个最大最小的差值的一些特型?
- 比如最大最小差值的和, amazon;
- 最大最小差值,满足 小于 N 的个数;
【https://blog.csdn.net/uuuououlcz/article/details/41684545】
看起来怎么都需要一个 n2的解法,另外最大最小差值,就有N2个;
我们看看这样一个 例子, 6,9,4,7,4,1; 对于满足某种条件的值; 用最大最小值队列,维护 Ai-j 的最大最小值;
知道无法满足,最大值减去最小值<N;
我们先用 优先级队列来做;维护 Ai-j 最大最小值;
固定i, 然后探索J。 看j 能最远达到什么地方,用 队列维护最大最小值,就可以复用;
比如N= 4;
i=6; j 只能到达 4; 队列就是 4 6 9; 9 6 4;
remove 6 : 队列 4,9; 9;4
i=9; j 还是只能4; 队列就是 4; 4;
i=4; j 可以到1; 队列 1 4 4 7; 7 4 4 1;一直遍历;
最坏,N logN; 没有 题解中使用单调队列来的快; 因为有很多信息是不需要的;
对于求和的问题??
1 2 ,3 ,9, 7,6, 5
目前只能想到, 记录最大值,最小值,然后,跳过一些不必要的计算;依旧还是个N2的局;
求和的问题是一个变换, 求出最大值的和,求出最小值的和,然后 Sum(max)- sum(min)
求最大,最小值, 是leetcode-907;
利用的单调栈, 和 求 最大矩形面积是一样的题; 单调栈,可以开一个专题,是弱项;
单调栈求最小值:
找的就是 最小值或者最大值,然后求两边 大于或者是小于我的数值;
为什么 单 stack.top() >= cur 的时候, 就需要计算呢?
因为 stack.top() 找到了一个cur,已经比我小了,往后再找就没有意义了,不可能比我大了。为什么要 == cur,其实大多数时候,不需要等于也是可以得
-
为了方便计算,最好是,在 array 的最后加入一个 0元素,保证在for循环就能处理所有的元素
//和最大矩形面积是一个思路; // 找到一种方法, 对于每一个 元素 a, 能够很方便的知道,我 左边 大于我的个数, 右边大于我的个数 //也就是 我处于谷底的个数, 这种特别是和 单调栈,栈中存放 元素的下标 // 1 7 5 8 9 4 10 // 比如 1 5 8 9 ,看4 的时候, 简直一个套路;
int len = arr.length;
if (len == 1)
return arr[0];
Stack<Integer> s = new Stack<>();
long ans = 0;
for (int i = 0; i <= len; i++) {
int cur = 0;
if(i!=len) cur =arr[i];
while (!s.isEmpty() && arr[s.peek()] >= cur) {
Integer index = s.pop();
int rangeLeft = s.isEmpty() ? -1 : s.peek();
ans += (long) (index - rangeLeft) * (i - index) * arr[index];
ans %= mod;
}
s.add(i);
}
return (int) (ans % mod);
二分查找汇总
s = 0, e = n-1; while(s<=e) ; 这种情况下 e = m-1; s=m+1;
如果 e = m 或者 s=m; 是可能存在死循环的,一定要注意;如果非得 要 e=m;注意处理 s=e 的情况;
循环结束的时候,有两种 e > s; e 可能小于0 或者 S > n; 看情况要不要处理;
leet-34: 查找K,可能有多个K,最左边的K 和最右边的K;
找到 K 的时候,不要反悔, 记录 ans = m 就行; 可以处理只有一个 k的情况;
leet-33: 螺旋数组查找
//螺旋数组,画图,最好理解; 一共是两个左右线段, mid 在第一个还是第二个,一共4种情况,很直观;
leet-74; 搜索二维数组,转化为一维就行
leet-153: 螺旋数组的最小值:
值得记录: 出现了 e = m 的情况; 其实也可以画线段处理,但是要处理很多情况;
s-e递增, m = 最小值; s=e的情况;等等;官方的解法,就是 从 nums[m] < nums[e]
这种情况来处理;避免很多edge case 讨论,不太容易想到;
leet-162: 寻找峰值:
假设 数组只有一个 递减 和 递增 ,那么 肯定是能找到的峰值的,那么就有了 二分查找,m。 如果m 处递增,则往后继续,如果递减,往前继续; 否则满足条件返回;
并查集
/**
* 并查集,特别适合解决,两个 元素, 是否在一个 集合里, 是否在连通图里面;
* 并且非常适合,不断有新元素加入到整个集合里面来;
*/
public class UnionFind {
/// 记录每个元素的parent;
/// 通常需要知道 value 的scope,同时scope 不能太大,否则开辟数组需要很大的空间;
int[] parents;
public UnionFind(int size){
parents = new int[size];
for(int i=0; i< size; i++)
parents[i] = i;
}
/**
* 使用压缩路径的方式
* @param v
* @return
*/
public int findRoot(int v){
if(v!=parents[v]){
parents[v] = findRoot(parents[v]);
}
return parents[v] ;
}
/**
* union 的方式决定了 findRoot的时间复杂度;
* v1-....-root; v2...root
* @param v1
* @param v2
*/
public void union(int v1, int v2 ){
int root1 = findRoot(v1);
int root2 = findRoot(v2);
parents[root1] = root2;
}
public boolean inSameSet(int v1, int v2){
int root1 = findRoot(v1);
int root2 = findRoot(v2);
return root1 == root2;
}
public static void main(String[] args) {
UnionFind uf = new UnionFind(10);
System.out.println(uf.findRoot(1));
System.out.println(uf.inSameSet(1,3));
uf.union(1,3);
System.out.println(uf.inSameSet(1,3));
uf.union(2,4);
uf.union(1,2);
System.out.println(uf.inSameSet(1,4));
}
}