左神算法第二章 桶排序、O(1)时间内求解栈内的最小元素

第二章

比较器

当比较的东西为一个对象时,系统不知道你要比较的是什么,会拿对象在内存中的地址去比较,但这并不是咱们的预期结果。所以要使用其他的比较办法,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函数越好

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值