单调队列
单调队列概念:
队列中的元素全都是单调递增(或递减)的。
算法框架
class MonotonicQueue {
// 双链表,支持头部和尾部增删元素
private LinkedList<Integer> q = new LinkedList<>();
// 在队尾添加元素 n
public void push(int n) {
// 将前面小于自己的元素都删除
while (!q.isEmpty() && q.getLast() < n) {
q.pollLast();
}
q.addLast(n);
}
// 返回当前队列中的最大值
public int max() {
// 队头的元素肯定是最大的
return q.getFirst();
}
// 队头元素如果是 n,删除它
public void pop(int n) {
if (n == q.getFirst()) {
q.pollFirst();
}
}
}
例题
239.滑动窗口最大值(困难)
/* 解题函数的实现 */
int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
List<Integer> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (i < k - 1) {
//先填满窗口的前 k - 1
window.push(nums[i]);
} else {
// 窗口向前滑动,加入新数字
window.push(nums[i]);
// 记录当前窗口的最大值
res.add(window.max());
// 移出旧数字
window.pop(nums[i - k + 1]);
}
}
// 需要转成 int[] 数组再返回
int[] arr = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
arr[i] = res.get(i);
}
return arr;
}
二分查找算法的运用
例题
一
解题思路:
1.暴力遍历,将速度从最小到最大遍历,第一个符合条件的速度就是答案
int minEatingSpeed(int[] piles, int H) {
// piles 数组的最大值
int max = getMax(piles);
for (int speed = 1; speed < max; speed++) {
// 以 speed 是否能在 H 小时内吃完香蕉
if (canFinish(piles, speed, H))
return speed;
}
return max;
}
2.搜索左侧边界的二分查找
class Solution {
// 核心代码
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = max;
while(left <= right){
int mid = (right - left) / 2 + left;
if(canFinish(piles, mid, h)){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
public boolean canFinish(int[] piles, int speed, int h){
int time = 0;
for(int pile : piles){
time += timeOf(pile, speed);
}
return time <= h;
}
public int timeOf(int pile, int speed){
return pile / speed + (pile % speed == 0 ? 0 : 1);
}
public int getMax(int[] piles){
int max = 0;
for(int pile : piles){
max = Math.max(max, pile);
}
return max;
}
}
二
final,static,this,super关键字总结
final 关键字
使用场景
final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:
final 修饰的类不能被继承,final 类中的所有成员方法都会被隐式的指定为 final 方法;
final 修饰的方法不能被重写;
final 修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
设计思想
- 把方法锁定,以防任何继承类修改它的含义;
- 效率问题。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
static 关键字
使用场景
1.修饰成员变量和成员方法:
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。
补充:
1.静态方法和非静态方法:静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。
2.静态代码块:
静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次
补充
1.一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM 加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM 将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
2.静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问.
3.静态代码块可能在第一次 new 对象的时候执行,但不一定只在第一次 new 的时候执行。比如通过 Class.forName(“ClassDemo”)创建 Class 对象的时候也会执行,即 new 或者 Class.forName(“ClassDemo”) 都会执行静态代码块。
4.静态代码块和静态方法比较:一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays 类,Character 类,String 类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
5.非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
3.静态内部类(static 修饰类的话只能修饰内部类):
静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
例如:静态内部类实现单例
public class Singleton {
//声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
测试
public class Singleton {
//声明为 private 避免调用默认构造方法创建对象
private Singleton() {
System.out.println("xxxx");
}
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
// private static final Singleton INSTANCE = new Singleton(); // xxxx
private static final Singleton INSTANCE = null; // 无
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) {
getUniqueInstance();
}
}
这个测试说明了静态内部类中静态成员或方法的调用和外部类对象的创建无关,除非主动创建
4.静态导包(用来导入类中的静态资源,1.5 之后的新特性):
格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
this 关键字
this 关键字用于引用类的当前实例。 例如:
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
在上面的示例中,this 关键字用于两个地方:
- this.employees.length:访问类 Manager 的当前实例的变量。
- this.report():调用类 Manager 的当前实例的方法。
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
super 关键字
super 关键字用于从子类访问父类的变量和方法。 例如:
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
在上面的例子中,Sub 类访问父类成员变量 number 并调用其父类 Super 的 showNumber() 方法。
使用 this 和 super 要注意的问题:
- 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
- this、super 不能用在 static 方法中。
简单解释一下:
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西。