第二章
比较器
当比较的东西为一个对象时,系统不知道你要比较的是什么,会拿对象在内存中的地址去比较,但这并不是咱们的预期结果。所以要使用其他的比较办法,java中有比较器Comparator,实现这个接口就可以进行比较。
public static class Student {
public String name;
public int id;
public int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
public static class IdAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
//返回负数 o1 放前面
}
}
public static void printStudents(Student[] students) {
for (Student student : students) {
System.out.println("Name : " + student.name + ", Id : " + student.id + ", Age : " + student.age);
}
System.out.println("===========================");
}
public static void main(String[] args) {
Student student1 = new Student("A", 1, 23);
Student student2 = new Student("B", 2, 21);
Student student3 = new Student("C", 3, 22);
Student[] students = new Student[] { student3, student2, student1 };
printStudents(students);
Arrays.sort(students, new IdAscendingComparator());
//按照自己定义的比较器策略进下排序
//利用student 中的id属性 升序进行排序
printStudents(students);
java中底层排序
size 大于60时 使用 merge 和quick进行综合排序,如果小于60,系统默认使用插入排序以求最快速度完成。
自己定义的class类型排序时,会使用merge排序。
桶排序
时间复杂度、空间复杂度 均为O(n),工程种不常用,但是笔试过程种可以使用这种办法来降低时间复杂度
对于给定的排序范围的数,进行排序。需要排序的数有n个。准备n+1个桶。
遍历每个数,将每个数装到对应的桶之后。再从第一个桶中取出,依次输出。
计数排序
每次统计当前数有多少个,然后给对应的元素位置中的数字+1,下次输出时按元素中的个数输出。
// only for 0~200 value
public static void bucketSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
//Integer的最大值
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int[] bucket = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
int i = 0;
for (int j = 0; j < bucket.length; j++) {
while (bucket[j]-- > 0) {
arr[i++] = j;
}
}
}
基数排序 (支持范围更大)
待补充
求最大差值
一个数组,无序,如果排序之后相邻两数的最大差值时多少?(如果数组是Long 类型)时间复杂度为O(n)。
例子: 0 99 66 2
排序后 0 2 66 99
最大差值为 66-2=64
假设这个数组里有 2^63-1 个数呢?
思路:
如果现在有9个数,准备9+1=10个桶,遍历这个数组,得到数的范围,如果数的范围是0-99,每个桶收集 范围数/准备排序的数的个数 即 100/10 ,只收集当前范围内的数。
至少一个桶中没有数,找到这个空桶,找到这个空桶的位置,找出这个桶左边的桶中的最大值,以及这个桶右边的桶中的最小值,两数相减,一定有
min-max>= 每个桶负责的范围(对于上面的例子是 相减之后的数一定大于11)
将每个空桶左右两边进行如上面的运算,求解这些数中最大的数,记录到一个数组a中。
再求解每两个非空桶中的 前一个桶中的最小值与后一个桶的最大值之间的差,记录到上面的那个数组a中。
答案一定在这两种情况中,联合在一起那就是,求解任意两个桶直接的最大差值,记录到数组中。求这个数组中的最大数。
就是要找的相邻两数的最大差值。
代码实现
public static int maxGap(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
int len = nums.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < len; i++) {
min = Math.min(min, nums[i]);
max = Math.max(max, nums[i]);
//找到最大、最小的值
}
if (min == max) {
return 0;
//只有一个数字
}
boolean[] hasNum = new boolean[len + 1];
//当前桶中是否有数
int[] maxs = new int[len + 1];
//当前桶中的最大数
int[] mins = new int[len + 1];
//当前桶中的最小数
int bid = 0;
for (int i = 0; i < len; i++) {
bid = bucket(nums[i], len, min, max);
//计算出桶号
mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i];
maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i];
hasNum[bid] = true;
}
int res = 0;
int lastMax = maxs[0];
//上一个非空桶的最大值
int i = 1;
for (; i <= len; i++) {
if (hasNum[i]) {
res = Math.max(res, mins[i] - lastMax);
lastMax = maxs[i];
}
}
return res;
}
public static int bucket(long num, long len, long min, long max) {
return (int) ((num - min) * len / (max - min));
}
//算出应该进第几号桶
只借用了桶的概念,但没有用到桶的排序。
用数组结构实现大小固定的栈和队列
数组实现栈
index代表当前栈中最上面数的上一个数
一开始,index=0,有一个数进来,index这个位置放新来的数,index++
如果出栈, --index。
如果index=size,栈满,只可以出栈,不可以进栈。
public static class ArrayStack {
private Integer[] arr;
private Integer size;
public ArrayStack(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[size - 1];
}
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
arr[size++] = obj;
}
public Integer pop() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
return arr[--size];
}
}
数组实现队列
先进先出
end负责新加的数的位置,start负责出队列的数
比如现在有一个5个元素的数组,end指向0,start指向0,有五个数进队列,size++执行5次,end指向4,此时 4=size-1,不能进行入队列,只能出。
如果size=0,此时队列为空不能出队列。
end和start 解耦,用size来约束这两个变量。
public static class ArrayQueue {
private Integer[] arr;
private Integer size;
private Integer first;
private Integer last;
public ArrayQueue(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
first = 0;
last = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[first];
}
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
size++;
arr[last] = obj;
last = last == arr.length - 1 ? 0 : last + 1;
}
public Integer poll() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
size--;
int tmp = first;
first = first == arr.length - 1 ? 0 : first + 1;
return arr[tmp];
}
}
O(1)时间内求解栈内的最小元素
pop push getMin操作的时间都是 O(1)
创建两个栈,一个是题目中给出的栈,一个记录最小数的栈。
当一个数据进栈时将这个数据压入最小数的栈,再压入下一个数,如果这个数比最小数中的栈顶大,那就压入一个和栈顶相同的数,如果这个数比栈顶小,则给最小数的栈压入一个和这个数相同的数,
public static class MyStack2 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack2() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {
this.stackMin.push(newNum);
} else {
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
this.stackMin.pop();
return this.stackData.pop();
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
只用队列结构实现栈
准备两个队列
第一个队 5 4 3 2 1 (顶)
先将1 2 3 4 出队
进入第二个队
然后 第一个队中 5 出
再将第二个队中的数出队 到第一个队
循环
即可实现 5 4 3 2 1 的出队顺序
public static class TwoQueuesStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueuesStack() {
queue = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
public void push(int pushInt) {
queue.add(pushInt);
}
public int peek() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}
public int pop() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
private void swap() {
Queue<Integer> tmp = help;
help = queue;
queue = tmp;
}
}
只用栈实现队列
同理,准备两个栈。
第一个栈中 1 2 3 4 5(栈顶)
将第一个栈中的数据一次性依次出栈 然后一次性压入第二个栈 5 4 3 2 1 (栈顶)
必须保证每次都出栈和入栈一次进行完,不可以留数。
这个就实现了 1 2 3 4 5的出队顺序
public static class TwoStacksQueue {
private Stack<Integer> stackPush;
private Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
public void push(int pushInt) {
stackPush.push(pushInt);
}
public int poll() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.pop();
}
public int peek() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.peek();
}
}
哈希函数
哈希函数是一个离散函数,
1、输入域几乎是无穷的,输出域是有范围的。
2、不是随机的,相同的输入得到相同的输出。
3、不同的输入也可能得到相同的输出。
4、不同的输入如果想得到整个s域的返回值会均匀分布。
比如 有100个数,假如有3个输出,那么就有大约33个数分别映射到这个3个输出上。
越均匀、越离散、这个hash函数越好