本周学习:时间复杂度,二分查找,二分答案,一二维前缀和和差分。
前提:时间空间复杂度计算都遵循大O渐进法则
大O渐进法则:
1.常数1取代运行时间中的所有加法常数
例:5*N^2+2^N+8 ----> 5*N^2+N+1
2.只保留最高项
例:5*N^2+N+1 ------> 5*N^2
3.如果最高项存在且系数不是一,去除最高项常数
例:5*N^2 -----> N*2 最后得到O(n^2)
1.时间空间复杂度
1.1 时间复杂度:算法运算的时间,节省测试时间
从小到大分为O(1) ,O(log2 n) , O(n) , O(nlog2 n) , O(n^2),O(2^n) ,O(n!)
嵌套相乘,其他相加,计算的时候遵循大O渐进法则
1.2 空间复杂度:算法运行过程中空间存储的估计量
情况分为:普通定义O(1),数组O(n)/O(n^2),循环O(n)/...,递归(递归次数*层数
2.二分法
2.1 情况一 二分查找:适用于数组的排序和查找,给出数组类型题。定义两个指针,确定中间值,比对目标大小,移动指针,改变中间值,最终比对成功。位运算>>1,作用除以2^1,向下取整,时间复杂度O(logN)
算法模板:
//模板一:尽量往左找目标 while(l<r) { int mid = l+r >>1; //(l+r)/2 if(check(mid)) r = mid; else l = mid +1; } //模板二:尽量往右找目标 while(l<r) { int mid = l+r+1 >>1; //(l+r+1) /2 if(check(mid)) l = mid; else r = mid-1; }
eetcode练习:练习题1——模板练习
leetcode34:解题思路:题目要求时间复杂度为O(logN),所以使用二分来解决,找到第一个target,然后使target+1,找到比目标大一的数,最后减一就可以实现,题目所给数组已经为升序,最后返回数组即可。
class Solution { public int[] searchRange(int[] nums, int target) { int l = search(nums,target); int r = search(nums,target+1); if(l==nums.length || nums[l] != target) { return new int []{-1,-1}; } return new int []{l,r-1}; } public static int search(int nums[],int target) { int left = 0,right = nums.length; while(left<right) { int mid = left+(right-left)/2; int num = nums[mid]; if(num >= target) { right = mid; }else{ left = mid + 1; } } return left; } }
leetcode33:解题思路:题目对一个已经按照升序排好的数组中的某个下标进行数组反转,这样的话就形成了两个区域,[left,mid] 和 [mid,right],例如[4,5,6,7,0],形成的区域一个是有序的,另一个可能有序,也可能无序,所以解题步骤要先分析区域是否有序,然后再进行左右指针的移动。 1.先判断mid左右任意一区间是否是有序 2. 然后判断target在哪一块区间 。通过比对nums[mid] 和 nums[0] 的大小 分为左右两块,然后假设情况target所在的取值范围,一边是有序的的,一边可能有序,可能无序,对于另一边进行继续二分探索。
class Solution { public int search(int[] nums, int target) { int left = 0,right = nums.length; while(left<right) { int mid =left+(right-left)/2; if(nums[mid] == target) { return mid; } if(nums[mid] >= nums[0]) { if(target>=nums[0] && target < nums[mid]) { right = mid; }else{ left = mid +1; } }else{ if(target>nums[mid] && target<=nums[right-1]) { left = mid+1; }else{ right = mid; } } } return -1; }
2.2 情况二:二分答案
适用:直接搜索不好搜,判断一个数可不可行,答案在一个区间内
根据题意,所求为最大切割木块长度,选用模板二
package exercise; import java.util.Scanner; class two_search { static int n,k; static int a [] ; public static void main(String[]args) { Scanner s = new Scanner(System.in); n = s.nextInt(); k = s.nextInt(); a = new int [n]; int max = 0; int sum =0; for(int i = 0;i<n;i++) { a[i] = s.nextInt(); sum+=a[i]; max=Math.max(max,a[i]); } if(sum<k) { System.out.println(0); return; } int l=1,r=max; int mid =0; while(l<r) { mid = l+r+1 >>1; if(Check(mid)) { l =mid; }else { r = mid -1; } } System.out.println(l); } public static boolean Check(int mid) { int duan = 0; for(int i = 0;i<a.length;i++) { duan+=a[i]/mid; } return duan>=k; }
3.前缀和
3.1一维前缀和:时间复杂度O(n)
//定义一个数组sum,从一开始 //一维前缀和预处理公式: int length,m; int l = 0,r = 0; for(int i =1;i<length;i++) { sum[i] = scanner.nextInt(); sum[i] += sum[i-1]; } while(m-->0) { l =.... r = .... System.out.print(sum[r]-sum[l-1]);
3.2二维前缀和:时间复杂度O(nm)
遍历求取一个矩形块内的数值和,结论:左上角(x1,y1),右下角(x2,y2) 的子矩阵和为;s[x2][y2] - s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]; 用两个二维数组,一个用来存a[i][j], 一个用来存s[i][j],首先用含a的式子表示s,之后带入x1.y1,x2,y2,得到范围矩阵的数字和。
模板
// n行m列 int a [][] = new int [n+1][m+1]; int s [][] = new int [n+1][m+1]; for(int i =1 ;i<=n;i++) { for(int j =1;j<=m;j++) { a[i][j] = scanner.nextInt(); } } for(int i =1 ;i<=n;i++) { for(int j =1;j<=m;j++) { s[i][j] = s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1]; } } ...... //最后输出 System.out.print(s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1])
4.差分
4.1一维差分:时间复杂度O(n)
方法一:理解:有前缀和数组a,则有差分数组b[i] = a[i]-a[i-1] 举例:因为a[i]是前缀和,当如b[1]+c 后,每一个a[i]中都含有b[1],相当于a[i]每个数都+c,给定加减数范围之后,对于范围之内的数都+c,即b[l] += c,同时也满足了a[l到end]每个数都加+c,再让b[r+1] -=c,将范围之后的数全部恢复原状,就可以达到目的。
模板:
import java.util.Scanner; class two_search { static int b[]; public static void main(String[]args) { Scanner s = new Scanner(System.in); int n = s.nextInt(); int m = s.nextInt(); int a [] = new int [n+1]; b= new int [n+2]; for(int i =1;i<=n;i++) { a[i] = s.nextInt(); } for(int i =1;i<=n;i++) { b[i] = a[i] -a[i-1]; } while(m-->0) { int l = s.nextInt(); int r = s.nextInt(); int c = s.nextInt(); b[l] += c; b[r+1] -=c; } for(int i = 1;i<=n;i++) { b[i] += b[i-1]; System.out.print(b[i]+" "); } } }
方法二:通过引入insert()函数,来表示前缀和a和差分数组b的关系,通过键盘输入a数组,然后定义差分数组b,没有赋值,所以数组里b[i] = 0,通过调用insert(i,i,a[i]),来表明a与b之间的关系。举例:insert(1,1,a[1]),则有b[1] = a[1],b[2] = -a[1] ,当i=2时,b[2]=a[2]-a[1],b[3] = -a[2],由于我们时遍历到n,所以无需关心b[n+1],只需要扩大数组空间,防止溢出就可以。
模板:
import java.util.Scanner; class two_search { static int b[]; public static void main(String[]args) { Scanner s = new Scanner(System.in); int n = s.nextInt(); int m = s.nextInt(); int a [] = new int [n+1]; b= new int [n+2]; for(int i =1;i<=n;i++) { a[i] = s.nextInt(); insert(i, i, a[i]); } while(m-->0) { int l = s.nextInt(); int r = s.nextInt(); int c = s.nextInt(); insert(l, r, c); } for(int i = 1;i<=n;i++) { b[i] += b[i-1]; System.out.print(b[i]+" "); } } public static void insert(int l,int r,int c) { b[l] += c; b[r+1] -=c; }
4.2二维差分
左上角(x1,y1),右下角为(x2,y2)的矩阵中每个元素都加c为:
s[x1][y1] +=c , s[x2+1][y1] +=c , s[x1][y2+1] += c ,s[x2+1][y2+1] += c;
当数据规模过于庞大,可以使用BufferedReader来进行键盘读取,如果正常使用Scanner来读取数据,容易输入超时。
模板:类似于一维差分,只有方法有所变动
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; class two_search { public static void main(String[]args) throws IOException { //使用BufferedReader避免超时 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); String[] str1 = reader.readLine().split(" "); int n = Integer.parseInt(str1[0]); int m = Integer.parseInt(str1[1]); int q = Integer.parseInt(str1[2]); int N = 1010; int [][] a = new int [N][N]; int [][] b = new int [N][N]; //读入原数组 for(int i = 1;i<=n;i++) { String [] str2 = reader.readLine().split(" "); for(int j = 1;j<=m;j++) { a[i][j] = Integer.parseInt(str2[j-1]); insert(b, i, j, i, j, a[i][j]); } } while(q-->0) { String str3[] = reader.readLine().split(" "); int x1 = Integer.parseInt(str3[0]); int y1 = Integer.parseInt(str3[1]); int x2 = Integer.parseInt(str3[2]); int y2 = Integer.parseInt(str3[3]); int k = Integer.parseInt(str3[4]); insert(b, x1, y1, x2, y2, k); } for(int i =1;i<=n;i++) { for(int j = 1;j<=m;j++) { b[i][j] +=b[i][j-1]+b[i-1][j] -b[i-1][j-1]; writer.write(b[i][j]+" "); } writer.write("\n"); } writer.flush(); reader.close(); writer.close(); } public static void insert(int [][]b,int x1,int y1,int x2,int y2,int k) { b[x1][y1] += k; b[x2+1][y1] -= k; b[x1][y2+1] -=k; b[x2+1][y2+1] +=k; } }