假设有如上图数组,找到10位置的7与他相邻的且比他小的两个数为5位置的5以及15位置的4。
我们现在需要知道6到14位置的子数组中有多少是以7作为最小值的。
6-10,6-11,6-12,6-13,6-14位置都是要跨过10位置的7,才是以7作为最小值。
如上图以10作为子数组最小值的时候,一共有25种组合,得出来的累加和为25乘以7。
推广:
假设来到i位置对应的值是x,假设左边离我最近的比我小的位置是k位置的y,右边离我最近的比我小的位置是j位置的z。产生多少累加和?
k+1位置到i位置的数字的个数为i-(k+1)+1=i-k去乘以(j到i有多少个结束位置)(j-i【j到不了】)最后再乘以x就是这段区域的累加和。
如上方式是对应于无重复值的情况,那么如果有重复值呢?
以3位置的3做最小值的子数组,推导范围为1-6范围,以7位置的3做最小值的子数组推导范围为1-10位置。以11位置的3做最小值的子数组推导范围为1-12,这样是为了去重,防止算重。前面位置是全量的,结尾位置是去重算了的
// 测试链接:https://leetcode.cn/problems/sum-of-subarray-minimums/
// subArrayMinSum1是暴力解
// subArrayMinSum2是最优解的思路
// sumSubarrayMins是最优解思路下的单调栈优化
// Leetcode上不要提交subArrayMinSum1、subArrayMinSum2方法,因为没有考虑取摸
// Leetcode上只提交sumSubarrayMins方法,时间复杂度O(N),可以直接通过
public class SumOfSubarrayMinimums {
public static int subArrayMinSum1(int[] arr) {
int ans = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i; j < arr.length; j++) {
int min = arr[i];
for (int k = i + 1; k <= j; k++) {
min = Math.min(min, arr[k]);
}
ans += min;
}
}
return ans;
}
// 没有用单调栈
public static int subArrayMinSum2(int[] arr) {
// left[i] = x : arr[i]左边,离arr[i]最近,<=arr[i],位置在x
int[] left = leftNearLessEqual2(arr);
// right[i] = y : arr[i]右边,离arr[i]最近,< arr[i],的数,位置在y
int[] right = rightNearLess2(arr);
int ans = 0;
for (int i = 0; i < arr.length; i++) {
//左边到不了的算一个开头数量
int start = i - left[i];
//右边到不了的位置算一个总的结尾数量
int end = right[i] - i;
ans += start * end * arr[i];
}
return ans;
}
public static int[] leftNearLessEqual2(int[] arr) {
int N = arr.length;
int[] left = new int[N];
for (int i = 0; i < N; i++) {
int ans = -1;
for (int j = i - 1; j >= 0; j--) {
if (arr[j] <= arr[i]) {
ans = j;
break;
}
}
left[i] = ans;
}
return left;
}
public static int[] rightNearLess2(int[] arr) {
int N = arr.length;
int[] right = new int[N];
for (int i = 0; i < N; i++) {
int ans = N;
for (int j = i + 1; j < N; j++) {
if (arr[i] > arr[j]) {
ans = j;
break;
}
}
right[i] = ans;
}
return right;
}
public static int sumSubarrayMins(int[] arr) {
int[] stack = new int[arr.length];
int[] left = nearLessEqualLeft(arr, stack);
int[] right = nearLessRight(arr, stack);
long ans = 0;
for (int i = 0; i < arr.length; i++) {
long start = i - left[i];
long end = right[i] - i;
ans += start * end * (long) arr[i];
ans %= 1000000007;
}
return (int) ans;
}
public static int[] nearLessEqualLeft(int[] arr, int[] stack) {
int N = arr.length;
int[] left = new int[N];
int size = 0;
for (int i = N - 1; i >= 0; i--) {
while (size != 0 && arr[i] <= arr[stack[size - 1]]) {
left[stack[--size]] = i;
}
stack[size++] = i;
}
while (size != 0) {
left[stack[--size]] = -1;
}
return left;
}
public static int[] nearLessRight(int[] arr, int[] stack) {
int N = arr.length;
int[] right = new int[N];
int size = 0;
for (int i = 0; i < N; i++) {
while (size != 0 && arr[stack[size - 1]] > arr[i]) {
right[stack[--size]] = i;
}
stack[size++] = i;
}
while (size != 0) {
right[stack[--size]] = N;
}
return right;
}
public static int[] randomArray(int len, int maxValue) {
int[] ans = new int[len];
for (int i = 0; i < len; i++) {
ans[i] = (int) (Math.random() * maxValue) + 1;
}
return ans;
}
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 = 100;
int maxValue = 50;
int testTime = 100000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * maxLen);
int[] arr = randomArray(len, maxValue);
int ans1 = subArrayMinSum1(arr);
int ans2 = subArrayMinSum2(arr);
int ans3 = sumSubarrayMins(arr);
if (ans1 != ans2 || ans1 != ans3) {
printArray(arr);
System.out.println(ans1);
System.out.println(ans2);
System.out.println(ans3);
System.out.println("出错了!");
break;
}
}
System.out.println("测试结束");
}
}
如果一个式子有严格的递推过程,都有logn复杂度的递推方法
1a+1c = 2
1b+1d = 1
2a +1c = 3
2b+d=2
得到:
a=1
c=1
b=1
d=0
如何让矩阵的次方算的足够快:
以10的七十五次方为例:
75的二进制:1001011
定义一个t = 10
每次t和自己乘:10的二次方,10的四次方 10的八次方。。。。
ans = 110的一次放 * 10的二次方 乘以10的八次方再乘以10的六十四次方,按位去转换。
只需要logN的复杂度就可以求出最后的次方值。
如果是矩阵的75次方:
得到单位矩阵为对角线互为0,1
假设F(n):
减到最到底的是3,所以这是一个3阶的递推
乘以一个3乘3的矩阵,推广:
如果是一个i的矩阵,则乘以一个i乘i矩阵的n-i次方,递推式不变
public static int f1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
return f1(n - 1) + f1(n - 2);
}
public static int f2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
int res = 1;
int pre = 1;
int tmp = 0;
for (int i = 3; i <= n; i++) {
tmp = res;
res = res + pre;
pre = tmp;
}
return res;
}
// O(logN)
public static int f3(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
// [ 1 ,1 ]
// [ 1, 0 ]
//单位矩阵
int[][] base = {
{ 1, 1 },
{ 1, 0 }
};
int[][] res = matrixPower(base, n - 2);
//(0,1)(1,0)加上得到第n项 根据递推式
return res[0][0] + res[1][0];
}
public static int[][] matrixPower(int[][] m, int p) {
int[][] res = new int[m.length][m[0].length];
//对角线上全填入1
for (int i = 0; i < res.length; i++) {
res[i][i] = 1;
}
// res = 矩阵中的1
int[][] t = m;// 矩阵1次方
for (; p != 0; p >>= 1) {
if ((p & 1) != 0) {
res = product(res, t);
}
t = product(t, t);
}
return res;
}
// 两个矩阵乘完之后的结果返回
public static int[][] product(int[][] a, int[][] b) {
int n = a.length;
int m = b[0].length;
int k = a[0].length; // a的列数同时也是b的行数
int[][] ans = new int[n][m];
for(int i = 0 ; i < n; i++) {
for(int j = 0 ; j < m;j++) {
for(int c = 0; c < k; c++) {
ans[i][j] += a[i][c] * b[c][j];
}
}
}
return ans;
}
第五年6头牛,第六年9头牛。
得出F(n) = F(n-1)+F(n-3) :
今年的牛由去年的牛活下来留到今年和新出生的牛构成(3年前有多少小牛都能帮着生小牛,而三年中的牛都生不了)
行列式推导:
最后得到F(n)=3乘以x+2乘以y+1乘以z
//递归方法
public static int c1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
return c1(n - 1) + c1(n - 3);
}
//递推方法
public static int c2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
int res = 3;
int pre = 2;
int prepre = 1;
int tmp1 = 0;
int tmp2 = 0;
for (int i = 4; i <= n; i++) {
tmp1 = res;
tmp2 = pre;
res = res + prepre;
pre = tmp1;
prepre = tmp2;
}
return res;
}
//矩阵快速幂
public static int c3(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
int[][] base = {
{ 1, 1, 0 },
{ 0, 0, 1 },
{ 1, 0, 0 } };
int[][] res = matrixPower(base, n - 3);
return 3 * res[0][0] + 2 * res[1][0] + res[2][0];
}
public static int[][] matrixPower(int[][] m, int p) {
int[][] res = new int[m.length][m[0].length];
//对角线上全填入1
for (int i = 0; i < res.length; i++) {
res[i][i] = 1;
}
// res = 矩阵中的1
int[][] t = m;// 矩阵1次方
for (; p != 0; p >>= 1) {
if ((p & 1) != 0) {
res = product(res, t);
}
t = product(t, t);
}
return res;
}
// 两个矩阵乘完之后的结果返回
public static int[][] product(int[][] a, int[][] b) {
int n = a.length;
int m = b[0].length;
int k = a[0].length; // a的列数同时也是b的行数
int[][] ans = new int[n][m];
for(int i = 0 ; i < n; i++) {
for(int j = 0 ; j < m;j++) {
for(int c = 0; c < k; c++) {
ans[i][j] += a[i][c] * b[c][j];
}
}
}
return ans;
}
由前到后已学模型:
1、二叉树递归套路
2、从左往右尝试模型
3、范围尝试模型
4、样本对应模型
5、业务限制模型
6、斐波那契数列的矩阵乘法模型
public class ZeroLeftOneStringNumber {
public static int getNum1(int n) {
if (n < 1) {
return 0;
}
return process(1, n);
}
public static int process(int i, int n) {
if (i == n - 1) {
return 2;
}
if (i == n) {
return 1;
}
return process(i + 1, n) + process(i + 2, n);
}
public static int getNum2(int n) {
if (n < 1) {
return 0;
}
if (n == 1) {
return 1;
}
int pre = 1;
int cur = 1;
int tmp = 0;
for (int i = 2; i < n + 1; i++) {
tmp = cur;
cur += pre;
pre = tmp;
}
return cur;
}
public static int getNum3(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return n;
}
int[][] base = { { 1, 1 }, { 1, 0 } };
int[][] res = matrixPower(base, n - 2);
return 2 * res[0][0] + res[1][0];
}
public static int fi(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
int[][] base = { { 1, 1 },
{ 1, 0 } };
int[][] res = matrixPower(base, n - 2);
return res[0][0] + res[1][0];
}
public static int[][] matrixPower(int[][] m, int p) {
int[][] res = new int[m.length][m[0].length];
for (int i = 0; i < res.length; i++) {
res[i][i] = 1;
}
int[][] tmp = m;
for (; p != 0; p >>= 1) {
if ((p & 1) != 0) {
res = product(res, tmp);
}
tmp = product(tmp, tmp);
}
return res;
}
// 两个矩阵乘完之后的结果返回
public static int[][] product(int[][] a, int[][] b) {
int n = a.length;
int m = b[0].length;
int k = a[0].length; // a的列数同时也是b的行数
int[][] ans = new int[n][m];
for(int i = 0 ; i < n; i++) {
for(int j = 0 ; j < m;j++) {
for(int c = 0; c < k; c++) {
ans[i][j] += a[i][c] * b[c][j];
}
}
}
return ans;
}
public static void main(String[] args) {
for (int i = 0; i != 20; i++) {
System.out.println(getNum1(i));
System.out.println(getNum2(i));
System.out.println(getNum3(i));
System.out.println("===================");
}
}
}
F(n)
如果按照上述方式摆放,则是一个F(n-1)
如果按照上述方式摆放,则是F(n-2)
F(n) = F(n-1)+F(n-2)