合并两个有序的数组
感觉这题怪怪的,也就是说A的length和A里面数字的个数m是不相等的?这是一个int[]开始就应该固定长度了嘛…感觉还是怪怪的
还有一个常用的写法,right–这种因为是返回了原本的right值以后再让right减-,所以可以直接写在括号里面变成一行
public class Solution {
public void merge(int A[], int m, int B[], int n) {
int right=A.length-1;
int curA=m-1;
int curB=n-1;
while(curA>=0 && curB >=0){
if(A[curA]>B[curB]){
A[right]=A[curA];
right--;
curA--;
}else{
A[right]=B[curB];
right--;
curB--;
}
}
while(curA >= 0){
A[right--]=A[curA--];
}
while(curB >=0){
A[right--]=B[curB--];
}
}
}
滑动窗口的最大值
这里用到的是一个单调递减栈的思路,只要一直维护着一个单调递减栈(只不过和平时不同的是,这个栈有大小限制,超过限制的时候把最前面的弹出去就好了,所以用java实现的时候用到的不是stack而是linkedlist的数据结构),就能保证滑动窗口内最大的永远是队列头那个数了
import java.util.*;
//用一个双端队列来代表滑动窗口
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> ret=new ArrayList<>();
if(num==null || num.length<size || size<1) return ret;
LinkedList<Integer> indexDeque=new LinkedList<>();
for(int i=0;i<size-1;i++){
while(!indexDeque.isEmpty() && num[i]>num[indexDeque.getLast()]){
indexDeque.removeLast();
}
indexDeque.addLast(i);
}
for(int i=size-1;i<num.length;i++){
while(!indexDeque.isEmpty() && num[i]>num[indexDeque.getLast()]){
indexDeque.removeLast();
}
indexDeque.addLast(i);
//超过size了就把前面的直接移掉,因为里面维持的是一个单调递减的双端队列,所以最大的仍然是队列最开头的那一个元素
if(i-indexDeque.getFirst()+1>size){
indexDeque.removeFirst();
}
ret.add(num[indexDeque.getFirst()]);
}
return ret;
}
}
背包问题
- 第一种背包问题:在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
首先传统的写法是这个样子的,写一个二维数组的动态规划
另外需要注意一些边界问题,dp[i][j]表示第i个物品在容量为j的背包里的最大容量,那么i的范围可以取0~n-1,因为有n个物品,而j的范围可以取0到m,容量也可以是0,这个点还需要初始化,所以数组的大小应该是dp[n][m+1]
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
public int backPack(int m, int[] A) {
// write your code herem
//dp[i][j]表示第i个物品在容量为j的背包里的最大容量
if(A.length==0||m==0){
return 0;
}
int n=A.length;
int[][] dp=new int[n][m+1];
//初始化
for(int i=0;i<n;i++){
dp[i][0]=0;
}
//递推
for(int i=0;i<n;i++){
for(int j=0;j<=m;j++){
if(i==0){
dp[i][j]=j>=A[i]?A[i]:0;
}else{
dp[i][j]=dp[i-1][j];
if(j>=A[i]){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-A[i]]+A[i]);
}
}
}
}
return dp[n-1][m];
}
}
然后我们发现这个写法存在浪费时间和空间的地方
空间上:因为dp[i]只与dp[i-1]有关,所以可以直接去掉i这个维度,直接在原来的基础上进行更新就好了
时间上:j从0开始循环到m,每次都要做j>A[i]的判断,不如直接改成从m一直减到A[i],这样可以减少不需要的比较次数,另外因为从大的总容量开始往小的容量开始减,最大的容量当然只与次小的容量有关,所以这个j逆序的写法也是十分合理的,如果j不使用逆序反而会出错
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
public int backPack(int m, int[] A) {
if (A.length==0 || m==0){
return 0;
}
int n=A.length;
int[] dp=new int[m+1];
for(int i=0;i<n;i++){
for(int j=m;j>=A[i];j--){
dp[j]=Math.max(dp[j],dp[j-A[i]]+A[i]);
}
}
return dp[m];
}
}
- 第二种带了value的背包问题:有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值,问最多能装入背包的总价值是多大?
解法还是分二维数组和一维数组两种,只是递推公式变了一下,变成计算最大的价值
用一维数组优化的时候,仍然需要把j逆序
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A & V: Given n items with size A[i] and value V[i]
* @return: The maximum value
*/
public int backPackII(int m, int[] A, int V[]) {
// write your code here
int[][] dp = new int[A.length + 1][m + 1];
for (int i = 0; i <= A.length; i++) {
for (int j = 0; j <= m; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else if (A[i - 1] > j) {
dp[i][j] = dp[(i - 1)][j];
} else {
dp[i][j] = Math.max(dp[(i - 1)][j], dp[(i - 1)][j - A[i - 1]] + V[i - 1]);
}
}
}
return dp[A.length][m];
}
}
// 方法二
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A & V: Given n items with size A[i] and value V[i]
*/
public int backPackII(int m, int[] A, int V[]) {
// write your code here
int[] f = new int[m + 1];
for (int i = 0; i <= m ; ++i) f[i] = 0;
int n = A.length , i, j;
for (i = 0; i < n; i++) {
for (j = m; j >= A[i]; j--) {
if (f[j] < f[j - A[i]] + V[i])
f[j] = f[j - A[i]] + V[i];
}
}
return f[m];
}
}
以上降到的问题都属于0-1背包问题,也就是一个物品只有一个,要么放要么不放,而还有一种变体是每种物体有无限个
- 第三种背包问题,给定 n 种物品, 每种物品都有无限个. 第 i 个物品的体积为 A[i], 价值为 V[i].再给定一个容量为 m
的背包. 问可以装入背包的最大价值是多少?
二维数组的写法:
这个和0-1背包写法不同的地方在于递推公式:
f[i][j] 直接由 f[i][j - A[i]] 转移, 并且从小到大枚举 j, 这样做的含义就是在已经拿过第 i 个物品的之后还可以再拿它. 也就是说: 计算 f[i][j] 时, 初始设置为 f[i - 1][j], 然后 f[i][j] = max(f[i][j], f[i][j - A[i]] + V[i])
public class Solution {
/**
* @param A an integer array
* @param V an integer array
* @param m an integer
* @return an array
*/
public int backPackIII(int[] A, int[] V, int m) {
// Write your code here
int n = A.length;
int[][] f = new int[n + 1][m + 1];
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= m; ++j) {
f[i][j] = f[i - 1][j];
if (j >= A[i - 1])
f[i][j] = Math.max(f[i][j - A[i - 1]] + V[i - 1], f[i][j]);
}
return f[n][m];
}
}
然后优化成一维数组,注意这里就显示除了和0-1背包的不同,由于可以叠加无限次,j使用的是顺序而不是逆序了
public class Solution {
/**
* @param A an integer array
* @param V an integer array
* @param m an integer
* @return an array
*/
public int backPackIII(int[] A, int[] V, int m) {
// Write your code here
int n = A.length;
int[] f = new int[m + 1];
for (int i = 0; i < n; ++i)
for (int j = A[i]; j <= m; ++j)
if (f[j - A[i]] + V[i] > f[j])
f[j] = f[j - A[i]] + V[i];
return f[m];
}
}
编译器的两点优化
1.自动补强转:对于byte/short/char三种类型来说,如果右侧赋值的数值没有超过范围,那么java编译器将会自动隐含地为我们补上一个(byte)(short),补上一个强转(没有超过数据范围的时候才会补上强转,右边超过了左边的数据范围的话就会报错)
2.常量优化:如果右侧的表达式全是常量,没有任何变量,那么编译器javac将会直接得到计算结果,所以下面哪一行编译后得到的.class字节码文件中相当于直接就是1+2的结果3
short a=1;
short b=1;
//short+short-->int+int
short result=a+b;//会报错
short result=1+2;//不会报错
java当中的数据类型
Java只有两种,要么基本数据类型,要么引用
基本:
整数型:byte-128-127 short2byte int4byte long8byte
浮点型:float单精度4byte double双精度8byte
字符型:char
布尔型:boolean
引用:除了基本的其他都是引用
字符串String/数组/类/接口/Lambda
1.String是引用啊
2.浮点型可能只是一个近似值,并非精确的值
3.浮点数默认是double,如果一定要用float需要后缀加一个F,而整数默认为int,如果要用long后缀L
java当中的内存划分
划分成的五个部分
1.栈:存放的都是方法中的局部变量
局部变量:方法的参数/方法的内部变量
作用域:一旦超出作用域,立刻从栈内存中消失
2.堆:凡是new出来的东西,都在new当中
堆内存里面的东西都有一个地址值:16进制
堆内存的数据,都有默认值
整数:0
浮点数:0.0
字符:’\u0000’
布尔:false
引用类型:null
3.方法区:存储.class相关信息,包含方法的信息
4.本地方法栈:与操作系统相关
5.寄存器PC:与CPU相关
一个对象的内存图
内存总共有五块,讲对象的时候我们主要用到三个:栈/堆/方法区
方法区包含的是.class相关数据,有每个类的成员变量和成员方法
然后先运行方法区里面的main方法,于是main方法就进栈(压栈)了
main里面,首先创建了一个对象,new了一个object,注意栈里面存放的是这个对象在堆里面的地址,指向的是堆
这个创建的对象是放在堆里面的,堆里面存放了对象的成员变量和成员方法,需要注意的是,堆里面存储的是成员方法的地址值,这个地址值指向的是方法区里面的该方法
我们可以看出:
1.找到一个成员变量的步骤是先从栈找到对象再找到堆里面的成员变量
2.找到一个成员方法的步骤是先从栈找到对象再从堆找到方法区那边去
找到了对象的方法以后,我们继续压栈,于是刚才的main方法在栈底,新的方法在栈顶
新的方法执行完了以后,就马上弹出了,于是main又变成了栈顶,然后压main里面调用的下一个方法
最后main也弹出,程序就停止运行了
字符串的常量池
字符串是可以共享使用的
程序当中直接写上的双引号字符串,就在字符串常量池中,字符串常量池这个东西在堆里面
对于基本类型来说,==是进行数值的比较
对于引用类型来说,==是进行地址值的比较
字符串常量池里面保存的是字符串对象的地址值,所以创建"abc"的时候其实是先创建了一个字节数组byte[],然后这个字节数组里面的每个元素,a的字节是97,b的字节是98,c是99,而字符串常量池里面存放的是这个字节数组的地址
又创建一个"abc"的时候,发现已经有一个一样的字节数组了,所以str2这个引用就直接指向原来的那个了
而char[]创建的是一个char数组,char[]是怎么转换成String的呢?它是new了一个新的String对象,构造时候的输入是一个char[]
首先我们把char数组转换成了一个字节数组byte[],这个byte[]是char[]转换而来的,所以和之前以后的那个byte[]是不一样的,不会说因为有了一个一样的就不转换了,所以堆里面会创建一个新的字符串对象String来存放这个新的byte[]的地址,从而指向它,然后这个地址(也就是引用值),就是str3这个引用变量的值
所以我们可以看出,==比较的是引用类型的地址值,而str123都是引用类型,所以str1和2相等,而2和3不等
public class Lianxi {
public static void main (String[] args) /*throws ParseException8*/ {
String str1="abc";
String str2="abc";
char[] chars={'a','b','c'};
String str3=new String(chars);
System.out.println(str1==str2);//true
System.out.println(str2==str3);//false
}
}
private
将需要被保护的成员变量进行修饰,本类当中可以随意访问,超出了本类的时候就不能访问了
包装类
我们使用一个类,把基本类型的数据包装起来,这个类叫做包装类
这个和c++就不一样了,java多了个包装
对于包装类,我们有装箱和拆箱的操作(基本数据类型->包装类)
装箱:把基本类型的数据,包装到包装类中,它包含了一个构造方法和静态方法
构造方法Integer(int value)构造一个新的Integer对象,他表示指定的int值
构造方法Integer(String s)构造一个新的Integer对象,他表示String参数所指示的int值
静态方法static Integer valueOf(int i)返回一个表示指定的int值的Integer实例
静态方法static Integer valueOf(String s)返回一个表示指定的String值的Integer实例
拆箱:在包装类中取出基本类型的数据(包装类->基本数据类型)
intValue()以int类型返回该Integer的值
JDK1.5以后可以自动装箱与自动拆箱,也就是基本类型和包装类之间可以自动的互相转换
ArrayList不能直接存储int整数,但可以存储Integer包装类
所以list.add(1)就是自动装箱list.add(new Integer(1))
而int a=list.get(0)就是自动拆箱list.get(0).inValue()
==
还有几个重点算法题:
并查集/线段树/红黑树/LRU/各种排序的写法/背包
反正都要睡到中午再去实验室,不如干脆晚点睡多刷点题算了…
6号得开始看java-web的部分了(直接做项目了,教程随便看看),明天看看一些java常见的优化问题