前缀和
问题描述
有一个整数数组,给出数组范围L、R,求L到R之间的数据和
思路
- 循环L到R之间的数据累加
- 构造二维数组,里面保存所有可能的L到R的数据累加和
- 重新构造一个整数数组,后面一个元素保存前面所有元素和自己本身元素之和,计算arr[L,R] = arr[R] - arr[L-1]
二维地图
public static int sumLR(int[] arr, int L, int R){
if(L > R || L >= arr.length || R >= arr.length) {
return -1;
}
if(L==R) {
return arr[L];
}
int[][] sumMap = new int[arr.length][arr.length];
for (int i = 0; i < arr.length; i++) {
for (int j = i; j < arr.length; j++) {
if(i == j) {
sumMap[i][j] = arr[i];
} else {
sumMap[i][j] = sumMap[i][j-1] + arr[j];
}
}
}
return sumMap[L][R];
}
前缀和
前缀和节省空间 但是多了一步计算,数据量非常大时没有二维数组计算快
- 循环一遍 计算0~i的和放到help数组中
- 计算arr[3,7] = h[7] - h[2]
public static int sumLR2(Integer[] arr, int L, int R){
if(L > R || L >= arr.length || R >= arr.length) {
return -1;
}
if(L==R) {
return arr[L];
}
int[] helper = new int[arr.length];
helper[0] = arr[0];
for (int i = 1; i < arr.length; i++) {
helper[i] = helper[i-1] + arr[i];
}
return L == 0 ? helper[R] : helper[R] - helper[L - 1];
}
测试类
定义测试样本
public class PreSumTest {
/**
* @param maxSize 数组大小
* @param maxValue 数组值范围
* @return 随机生成数组集合
*/
public static Integer[] generateRandomArray(int maxSize, int maxValue) {
Integer[] arr = new Integer[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
// [-? , +?]
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
/**
* 拷贝新的数组
*/
public static Integer[] copyArray(Integer[] arr) {
if (arr == null) {
return null;
}
Integer[] res = new Integer[arr.length];
System.arraycopy(arr, 0, res, 0, arr.length);
return res;
}
public static void printArray(Integer[] arr) {
if (arr == null) {
return;
}
StringBuffer sb = new StringBuffer("{");
for (int i= 0; i < arr.length; i++) {
if (i == arr.length - 1) {
sb.append(arr[i]);
} else {
sb.append(arr[i] + ",");
}
}
sb.append("}");
System.out.println(sb.toString());
}
public static Integer sum(Integer[] arr, int L, int R) {
int sum = 0;
for (int i = L; i <= R ; i++) {
sum += arr[i];
}
return sum;
}
public static void main(String[] args) {
//测试的次数
int testTime = 50000;
//测试的数组大小
int maxSize = 100;
//测试的数组值范围
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
Integer[] arr1 = generateRandomArray(maxSize, maxValue);
printArray(arr1);
if(arr1.length == 0) {
continue;
}
Random random = new Random();
//随机返回一个值在[0,num)的int类型的整数,包括0不包括num
int L = random.nextInt(arr1.length );
int R = random.nextInt(arr1.length );
int left = Math.min(L, R);
int right = Math.max(L, R);
//发现不相等R
int i1 = PreSum.sumLR(arr1, left, right);
int i2 = sum(arr1, left, right);
if (!Objects.equals(i1, i2)) {
succeed = false;
printArray(arr1);
System.out.println("left = " + left + "; right = " + right);
break;
}
}
System.out.println(succeed ? "Nice!" : "ERROR!");
}
}
等概率发生器
Math.random()是等概率发生器,在[0, 1)范围内等概率生成值
public static void main(String[] args) {
int testTimes = 10000000;
int count = 0;
for (int i = 0; i < testTimes; i++ ) {
if (Math.random() < 0.75) {
count++;
}
}
//打印的数据约等于0.75
System. out.println( (double) count / (double) testTimes);
}
通过这个可以做一些有意思的运算
如何等概率返回0到k范围内的值
/**
* 随机返回[0, k-1]范围的值
*/
public static int range(int k){
// [0, K) --> [0, k - 1]
return (int) (Math.random() * k);
}
public static void rangeTest(int k, int testTimes){
//测试
int[] arr = new int[k];
for (int i = 0; i < testTimes; i++) {
int ans = (int) (Math.random() * k);
arr[ans]++;
}
System.out.println(Arrays.toString(arr));
}
如何利用Math.random()函数,把得到[0,x)范围上的数的概率从x调整成x^2 ?
思路:Math.random()在[0,x)上的概率是 x ,如果两次的最大值 < x,说明产生一个数的概率变为x^2
public static double xToXPower2(){
return Math.max(Math.random(), Math.random());
}
public static void testXToXPower2(){
double x = 0.21;
int num = 100000;
int count = 0;
for(int i = 0;i < num;i++){
//返回一个最大数,如果最大数小于x ,说明 两个数都小于x,所以概率为x的平方
if(xToXPower2() < x){
count++;
}
}
System.out.println((double)count/(double)num);
System.out.println(Math.pow(x,2));//Math.pow(x,2) 平方函数
}
思考:
为什么不使用Math.min()函数
Math.random()在[0,x)上的概率是 x,如果使用min函数,只要有一个发生在[0,x)上就成功,只有都不在[0,x)上min函数的结果才会 >= x。
不在[0,x)上的概率是 1- x ; 两次都不在就是 (1-x)^2 ;最终的概率就是 1 - (1-x)^2
如何调整到x^3的概率
public static double xToXPower3(){
return Math.max(Math.max(Math.random(), Math.random()), Math.random());
}
1~5随机调整为1~7随机
有一个函数f1()等概率返回 1-5 范围的数,如何借助当前f1()函数实现1 - 7范围的等概率返回?
/**
* 等概率返回1~5
*/
private static int f1(){
return (int)(Math.random() * 5) + 1;
}
private static int f2(){
int ans = 0;
do {
ans = f1();
} while (ans == 3);
//改造f1函数为0到1的等概率发生器
return ans < 3 ? 0 : 1;
}
/**
* 得到000 ~ 111的等概率器 也就是0~7的概率
*
* 为什么不直接 f2() * 6 + 1 得到 1~7的概率? 因为f2只会返回0和1,这样只能得到1和7两个值而不是1到7
*/
private static int f3(){
return (f2() << 2) + (f2() << 1) + f2() ;
}
/**
* 0到6等概率发生器
*/
private static int f4(){
int ans = 0;
do {
ans = f3();
} while (ans == 7);
return ans;
}
/**
* 得到1到7的等概率器
*/
private static int g(){
return f4() + 1;
}
a~b随机调整为c~d随机
思路:参考上面的例子 比如3~19 调整为 17~56
- a~b取中间位数据 对半分割 转为0到1的等概率 (3~10 和 12到19)
- 计算c~d的范围需要左移的次数 (左移6次 最大值63)
- c~d 调整为 0 ~ d - c的范围 (17~56 调整为 0 到 39)
- 0到63的范围内数重做为只返回0到39之间的数据
- 返回0到39的随机 + 17 得到 17~56 的随机
01不等概率随机调整为01等概率随机
有一个函数x()返回 0 和 1 的概率为固定概率,但不是等概率,如何用该函数实现一个等概率发生器函数y()?
/**
* 一个固定概率 返回 0 和 1 的函数
* 但此函数不是等概率返回 0和1
*/
public static int x (){
return Math.random() < 0.85 ? 0 : 1;
}
x() 返回 0 的概率 为 p,返回1的概率为 1-p
则两次x()方法 返回 01 和 10的概率是相同的都是 p * (1-p)。只要代码能保证两次结果不一致 则是等概率返回0和1
public static int y(){
int ans = 0;
do{
ans = x();
}while (ans == x());
return ans;
}