左神数据结构与算法 笔记(一)

左神数据结构与算法 笔记

一、第一节课

时间复杂度:

意义:衡量算法流程的复杂度指标

评估算法的核心指标

时间复杂度 额外空间复杂度 常数项时间

常见的常数时间操作
  • 常见的算数用算(+ - * % /)
  • 常见的位运算(>>, >>>, <<, |, &, ^ )
  • 赋值,比较自增,自减操作 以及数组的寻址操作

: 前者右移:带动符号位右移并且最左侧拿符号位补

确定时间复杂度:

​ 拆分代码流程,直至每一个基本动作,保证每个动作是是常数时间操作列出表达式,留下最高阶的项。

三个排序

选择排序:
public static void selectSort(int[] arr)
{
	if(arr == null || arr.length<2)
    {
        return;
    }
    for(int i = 0;i < arr.length - 1;i++)
    {
        minIndex = i; 
     	for(int j = i + 1;j < arr.length;j++)
        {
            minvalue = arr[minIndex] > arr[j] ? j :minIndex; 
		}
        swap(arr,i,minIndex)
    }
}
冒泡排序:
public static void BubbleSort(int[] arr)
{
    if(arr == null || arr.length < 2)
    {
        return ;
	}
    for(int i = arr.length-1;i > 0;i--)
    {
        for(int j = 0;j < i;j++ )
        {
         	if(arr[j] > arr[j +1])
            {
                swap(arr,j,j+1);
			}
        }
    }
    
}
插入排序:
public static void InsertSort(int[] arr)
{
    if(arr == null || arr.length < 2)
    {
        return ;
    }
    for(int i = 1;i < arr.length;i++)
    {
        if(int j = i -1;j >= 0 && arr[j] < arr[j + 1]; j--)
        {
            swap(arr,j,j+1);
		}
    }
    
}
交换骚操作
public void swap(int[] arr,int i,int j)
{
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

二分法

有序数组找某个数字是否存在
public static boolean Two01Exist(int[] arr,int num)
{
    if(arr.length == null || arr.length == 0)
    {
        return false;
	}
    int right = arr.length -1;   //arr.length  数组长度   长度-1: 数组最后一个数字的下标
    int left = 0;  //数组 开始下标
    while(left < right)  
    {
     	int mid = left + ((right - left) >> 1);   //数组中间数字  下标
        if(num == arr[mid])
        {
            return true;
		}else if(arr[mid] < num)
        {
            left = mid +1;
		}else{
            right = mid -1;
        }
    }
    return num == arr[left];
}
在一个有序数组中,找>=某个数最左侧的位置
public static int leftExit(int[] arr,int value)
{
    int Index = -1;
    int left = 0;
    int right = arr.length -1;
    while(left <= right)
    {
        mid = left + ((right - left) >> 1);
        if(arr[mid] >= value)
        {
            Index = mid;
            right = mid -1;
		}else{
            left = mid +1;
		}
    }
    return Index;
}
在一个无序数组中,找<=某个数最右侧的位置
局部最小值问题
public static int getLessIndex(int[] arr)/*数组无序*/
{
    if(arr == null || arr.length == 0)
    {
        return ;
    }
    if(arr.length == 1 ||  arr[1] >arr[o])
    {
        return 0;
	}
    if(arr[arr.length-2]>arr[arr.length-1])
    {
        return arr.length-1;
	}
    int left = 1;
    int right = arr.length - 2;
    while(left < right)
    {
        mid = left - ((right - left) >> 1);
        if(arr[mid] > arr[mid+1])
        {
            left = mid +1;
        }else if(arr[mid] > arr[mid -1]){
            right = mid -1
		}
        else{
            return mid;
        }
		return left;
    }
}

认识异或运算

异或运算记成(相同为0,不同为1):无进位相加 满足交换律与结合律

同或运算:相同为1,不同为0

数组中,其他数都是偶数,找到出现奇数次的数:用一个x= 0 去异或所有数, 得到的x

public static void printTimes(int[] arr)
{
    int eor = 0;
    for(int i = 0; i < arr.length;i++)
    {
        eor ^= arr[i];  
    }
    return eor;
}
  1. 怎么把一个int 的数字,提取出最右侧的1来 eor & (~eor + 1)

  2. 一个数组中,有两种数出现了奇数次,其他数字都出现了偶数次,找到这两个数字

public static void printOddTimesNum1(int[] arr) {
		int eO = 0;
		for (int cur : arr) {
			eO ^= cur;
		}
		System.out.println(eO);
	}

	public static void printOddTimesNum2(int[] arr) {
		int eO = 0, eOhasOne = 0;
		for (int curNum : arr) {
			eO ^= curNum;
		}
		int rightOne = eO & (~eO + 1);
		for (int cur : arr) {
			if ((cur & rightOne) != 0) {
				eOhasOne ^= cur;
			}
		}
		System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
	}

二、第二节课

一 链表

链表的反转
public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			value = data;
		}
	}

	public static class DoubleNode {
		public int value;
		public DoubleNode last;
		public DoubleNode next;

		public DoubleNode(int data) {
			value = data;
		}
	}

	//  head
	//   a    ->   b    ->  c  ->  null
	//   c    ->   b    ->  a  ->  null
	public static Node reverseLinkedList(Node head) {
		Node pre = null;
		Node next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			pre = head;
			head = next;
		}
		return pre;
	}

	public static DoubleNode reverseDoubleList(DoubleNode head) {
		DoubleNode pre = null;
		DoubleNode next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			head.last = next;
			pre = head;
			head = next;
		}
		return pre;
	}
给定值删除
public static Node removeValue(Node head,int data)
{
    while(head != null){
    	if(head.value != data)
        {
			break;
        }
        head = head.next;
	}
    
    Node pre = head;
    Node cur = head; 
    while(cur != null)
    {
        if(data == cur.value)
        {
            pre.next = cur.next;
        }else{
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
    
}

二、栈和队列

双向链表实现其结构
public static class Node<T> {
		public T value;
		public Node<T> last;
		public Node<T> next;

		public Node(T data) {
			value = data;
		}
	}

	public static class DoubleEndsQueue<T> {
		public Node<T> head;
		public Node<T> tail;

		public void addFromHead(T value) {
			Node<T> cur = new Node<T>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				cur.next = head;
				head.last = cur;
				head = cur;
			}
		}

		public void addFromBottom(T value) {
			Node<T> cur = new Node<T>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				cur.last = tail;
				tail.next = cur;
				tail = cur;
			}
		}

		public T popFromHead() {
			if (head == null) {
				return null;
			}
			Node<T> cur = head;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				head = head.next;
				cur.next = null;
				head.last = null;
			}
			return cur.value;
		}

		public T popFromBottom() {
			if (head == null) {
				return null;
			}
			Node<T> cur = tail;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				tail = tail.last;
				tail.next = null;
				cur.last = null;
			}
			return cur.value;
		}

		public boolean isEmpty() {
			return head == null;
		}

	}

	public static class MyStack<T> {
		private DoubleEndsQueue<T> queue;

		public MyStack() {
			queue = new DoubleEndsQueue<T>();
		}

		public void push(T value) {
			queue.addFromHead(value);
		}

		public T pop() {
			return queue.popFromHead();
		}

		public boolean isEmpty() {
			return queue.isEmpty();
		}

	}

	public static class MyQueue<T> {
		private DoubleEndsQueue<T> queue;

		public MyQueue() {
			queue = new DoubleEndsQueue<T>();
		}

		public void push(T value) {
			queue.addFromHead(value);
		}

		public T poll() {
			return queue.popFromBottom();
		}

		public boolean isEmpty() {
			return queue.isEmpty();
		}

	}

	public static boolean isEqual(Integer o1, Integer o2) {
		if (o1 == null && o2 != null) {
			return false;
		}
		if (o1 != null && o2 == null) {
			return false;
		}
		if (o1 == null && o2 == null) {
			return true;
		}
		return o1.equals(o2);
	}
数组实现其结构
public static class MyQueue {
		private int[] arr;
		private int pushi;// end
		private int polli;// begin
		private int size;
		private final int limit;

		public MyQueue(int limit) {
			arr = new int[limit];
			pushi = 0;
			polli = 0;
			size = 0;
			this.limit = limit;
		}

		public void push(int value) {
			if (size == limit) {
				throw new RuntimeException("队列满了,不能再加了");
			}
			size++;
			arr[pushi] = value;
			pushi = nextIndex(pushi);
		}

		public int pop() {
			if (size == 0) {
				throw new RuntimeException("队列空了,不能再拿了");
			}
			size--;
			int ans = arr[polli];
			polli = nextIndex(polli);
			return ans;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		// 如果现在的下标是i,返回下一个位置
		private int nextIndex(int i) {
			return i < limit - 1 ? i + 1 : 0;
		}

	}

四、递归

求数组最大值
/*
复杂度   O(N)
*/
public static int get Max(int[] arr)   
{
    return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int left,int right)
{
    if(left == right)
    {
        return arr[left];
    }
    int mid = left + ((rigrt - left)>>1);
    int leftMax = process(arr,left,mid);
    int rightMax = process(arr,mid + 1,right);
	return Math.max(rightMax,leftMax);
}
一类递归的时间复杂度公式

​ T(N) = aT(N/b) + O(N^d) 子问题规模:b 调用的次数:a 其余时间复杂度:O(N^d)

​ 形如

​ T ( N )= a * T ( N / b )+ O ( NAd )(其中的 a 、 b 、 d 都是常数)的递归函数,可以直接通过 Master 公式来确定时间复杂度

​ 如果 log ( b , a )< d ,复杂度为 O ( N^d )

​ 如果 log ( b , a ) > d ,复杂度为 O ( N^log ( b , a ))

​ 如果 log ( b , a )== d ,复杂度为 O ( N^d * logN )

哈希表

​ 1)哈希表在使用层面上可以理解为一种集合结构

​ 2)如果只有 key ,没有伴随数据 value ,可以使用 HashSet 结构

​ 3)如果既有 key ,又有伴随数据 value ,可以使用 HashMap 结构

​ 4)有无伴随数据,是 HashMap 和 HashSet 唯一的区别,实际结构是一回事

​ 5)使用哈希表增( put )、删( remove )、改( put )和查( get )的操作,可以认为时间复杂度为 O (1),但是常数时间比较大

​ 6)放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小

​ 7)放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是8字节

第三节课

归并排序

用处: 纠结每个数字的左边或者右边的数字比自己大或者小 例如 求数组小和

整体是个递归:左右分别排好序 然后merge让整体有序

// 递归方法实现
	public static void mergeSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	// 请把arr[L..R]排有序
	// l...r N
	// T(N) = 2 * T(N / 2) + O(N)
	// O(N * logN)
	public static void process(int[] arr, int L, int R) {
		if (L == R) { // base case
			return;
		}
		int mid = L + ((R - L) >> 1);
		process(arr, L, mid);
		process(arr, mid + 1, R);
		merge(arr, L, mid, R);
	}

	public static void merge(int[] arr, int L, int M, int R) {
		int[] help = new int[R - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = M + 1;
		while (p1 <= M && p2 <= R) {
			help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
		}
		// 要么p1越界了,要么p2越界了
		while (p1 <= M) {
			help[i++] = arr[p1++];
		}
		while (p2 <= R) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
	}

非递归方法

public static void mergeSort(int[] arr)
{
    if(arr == null || arr.length < 2)
    {
        return ;
    }
    int N = arr.length;
    int size = 1;
    while( size < N)
    {
        int L = 0;
        while(L < N)
        {
            int mid = L +size -1;
            if(mid > N)
            {
                break;
            }
            int R = Math.min(mid +size,N-1)
            mergr(arr,L,mid,R);
            L =  R +1;
        }
        if(size > N/ 2)
        {
            brea
        }
        size >> 1;        
    }
}

求数组小和

​ 在一个数组中,一个数左边比她小的数的总和,叫做数的小和所有数字的小和累加起来叫做数组小和

public static int smallSum(int[] arr) {
		if (arr == null || arr.length < 2) {
			return 0;
		}
		return process(arr, 0, arr.length - 1);
	}

	// arr[L..R]既要排好序,也要求小和返回
	// 所有merge时,产生的小和,累加
	// 左 排序   merge
	// 右 排序  merge
	// merge
	public static int process(int[] arr, int l, int r) {
		if (l == r) {
			return 0;
		}
		// l < r
		int mid = l + ((r - l) >> 1);
		return 
				process(arr, l, mid) 
				+ 
				process(arr, mid + 1, r) 
				+ 
				merge(arr, l, mid, r);
	}

	public static int merge(int[] arr, int L, int m, int r) {
		int[] help = new int[r - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = m + 1;
		int res = 0;
		while (p1 <= m && p2 <= r) {
			res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
		return res;
	}

快速排序

Partition过程

​ 给定一个数组 和整数,把小于整数的放在左边 大于的放在右边

要求: 时间复杂度O(N) 额外空间复杂度O(1)

public static int partition(int[] arr, int L, int R) {
		if (L > R) {
			return -1;
		}
		if (L == R) {
			return L;
		}
		int lessEqual = L - 1;
		int index = L;
		while (index < R) {
			if (arr[index] <= arr[R]) {
				swap(arr, index, ++lessEqual);
			}
			index++;
		}
		swap(arr, ++lessEqual, R);
		return lessEqual;
	}

	// arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值
	// <arr[R] ==arr[R] > arr[R]
	public static int[] netherlandsFlag(int[] arr, int L, int R) {
		if (L > R) { // L...R L>R
			return new int[] { -1, -1 };
		}
		if (L == R) {
			return new int[] { L, R };
		}
		int less = L - 1;  
		int more = R; 
		int index = L;
		while (index < more) { 
			if (arr[index] == arr[R]) {
				index++;
			} else if (arr[index] < arr[R]) {					
				swap(arr, index++, ++less);
			} else {
				swap(arr, index, --more);
			}
		}
		swap(arr, more, R); // <[R]   =[R]   >[R]
		return new int[] { less + 1, more };
	}

	public static void quickSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process1(arr, 0, arr.length - 1);
	}

	public static void process1(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		// L..R partition arr[R] [ <=arr[R] arr[R] >arr[R] ]
		int M = partition(arr, L, R);
		process1(arr, L, M - 1);
		process1(arr, M + 1, R);
	}
 
	public static void quickSort2(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process2(arr, 0, arr.length - 1);
	}

	// arr[L...R] 排有序,快排2.0方式
	public static void process2(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		// [ equalArea[0]  ,  equalArea[0]]
		int[] equalArea = netherlandsFlag(arr, L, R);
		process2(arr, L, equalArea[0] - 1);
		process2(arr, equalArea[1] + 1, R);
	}
 
	
	public static void quickSort3(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process3(arr, 0, arr.length - 1);
	}

	public static void process3(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
		int[] equalArea = netherlandsFlag(arr, L, R);
		process3(arr, L, equalArea[0] - 1);
		process3(arr, equalArea[1] + 1, R);
	}
荷兰国旗问题
// 荷兰国旗问题
	public static int[] netherlandsFlag(int[] arr, int L, int R) {
		if (L > R) {
			return new int[] { -1, -1 };
		}
		if (L == R) {
			return new int[] { L, R };
		}
		int less = L - 1;
		int more = R;
		int index = L;
		while (index < more) {
			if (arr[index] == arr[R]) {
				index++;
			} else if (arr[index] < arr[R]) {
				swap(arr, index++, ++less);
			} else {
				swap(arr, index, --more);
			}
		}
		swap(arr, more, R);
		return new int[] { less + 1, more };
	}

	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
快速排序1.0
	在 arr [ L . R ]范围上,进行快速排序的过程:

​ 1)用 arr [ R ]对该范围做 partition ,<= arr [ R ]的数在左部分并且保证 arr [ R ]最后来到左部分的最后一个位置,记为 M ;<= arr [ R ]的数在右部分( arr [ M +1… R ])

​ 2)对 arr [ L … M -1]进行快速排序(递归)

​ 3)对 arr [ M +1.R]进行快速排序(递归)因为每一次 partition 都会搞定一个数的位置且不会再变动,所以排序能完成

// 快排递归版本
	public static void quickSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	public static void process(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
		int[] equalArea = netherlandsFlag(arr, L, R);
		process(arr, L, equalArea[0] - 1);
		process(arr, equalArea[1] + 1, R);
	}

第四节课

堆结构

​ 1)堆结构就是用数组实现的完全二叉树结构

​ 2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆

​ 3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆

​ 4)堆结构的 heaplnsert 与 heapify 操作

​ 5)堆结构的增大和减少

​ 6)优先级队列结构,就是堆结构

public static class MyMaxHeap {
		private int[] heap;
		private final int limit;
		private int heapSize;

		public MyMaxHeap(int limit) {
			heap = new int[limit];
			this.limit = limit;
			heapSize = 0;
		}

		public boolean isEmpty() {
			return heapSize == 0;
		}

		public boolean isFull() {
			return heapSize == limit;
		}

		public void push(int value) {
			if (heapSize == limit) {
				throw new RuntimeException("heap is full");
			}
			heap[heapSize] = value;
			// value heapSize
			heapInsert(heap, heapSize++);
		}

		// 用户此时,让你返回最大值,并且在大根堆中,把最大值删掉
		// 剩下的数,依然保持大根堆组织
		public int pop() {
			int ans = heap[0];
			swap(heap, 0, --heapSize);
			heapify(heap, 0, heapSize);
			return ans;
		}

		// 新加进来的数,现在停在了index位置,请依次往上移动,
		// 移动到0位置,或者干不掉自己的父亲了,停!
		private void heapInsert(int[] arr, int index) {
			// [index] [index-1]/2
			// index == 0
			while (arr[index] > arr[(index - 1) / 2]) {
				swap(arr, index, (index - 1) / 2);
				index = (index - 1) / 2;
			}
		}

		// 从index位置,往下看,不断的下沉
		// 停:较大的孩子都不再比index位置的数大;已经没孩子了
		private void heapify(int[] arr, int index, int heapSize) {
			int left = index * 2 + 1;
			while (left < heapSize) { // 如果有左孩子,有没有右孩子,可能有可能没有!
				// 把较大孩子的下标,给largest
				int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
				largest = arr[largest] > arr[index] ? largest : index;
				if (largest == index) {
					break;
				}
				// index和较大孩子,要互换
				swap(arr, largest, index);
				index = largest;
				left = index * 2 + 1;
			}
		}

		private void swap(int[] arr, int i, int j) {
			int tmp = arr[i];
			arr[i] = arr[j];
			arr[j] = tmp;
		}

	}
堆排序
		1,先让整个数组都变成大根堆结构,建立堆的过程:

​ 1)从上到下的方法,时间复杂度为 O ( N * logN )

​ 2)从下到上的方法,时间复杂度为 O ( N )

​ 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为 O ( N * logN )

​ 3,堆的大小减小成0之后,排序完成

// 堆排序额外空间复杂度O(1)
	public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// O(N*logN)
//		for (int i = 0; i < arr.length; i++) { // O(N)
//			heapInsert(arr, i); // O(logN)
//		}
		// O(N)
		for (int i = arr.length - 1; i >= 0; i--) {
			heapify(arr, i, arr.length);
		}
		int heapSize = arr.length;
		swap(arr, 0, --heapSize);
		// O(N*logN)
		while (heapSize > 0) { // O(N)
			heapify(arr, 0, heapSize); // O(logN)
			swap(arr, 0, --heapSize); // O(1)
		}
	}

	// arr[index]刚来的数,往上
	public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

	// arr[index]位置的数,能否往下移动
	public static void heapify(int[] arr, int index, int heapSize) {
		int left = index * 2 + 1; // 左孩子的下标
		while (left < heapSize) { // 下方还有孩子的时候
			// 两个孩子中,谁的值大,把下标给largest
			// 1)只有左孩子,left -> largest
			// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
			// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
			int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
			// 父和较大的孩子之间,谁的值大,把下标给largest
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}
相关题目:

​ 已知一个几乎游戏的数组。几乎:若数组排好序的话每个元素移动的距离一定不超过k,并且k相当于对数租的长度来说是比较小的

  public static void sortedArrDistanceLessK(int[] arr, int k) {
		if (k == 0) {
			return;
		}
		// 默认小根堆
		PriorityQueue<Integer> heap = new PriorityQueue<>();
		int index = 0;
		// 0...K-1
		for (; index <= Math.min(arr.length - 1, k - 1); index++) {
			heap.add(arr[index]);
		}
		int i = 0;
		for (; index < arr.length; i++, index++) {
			heap.add(arr[index]);
			arr[i] = heap.poll();
		}
		while (!heap.isEmpty()) {
			arr[i++] = heap.poll();
		}
	}
比较器

​ 1)比较器的实质就是重载比较运算符

​ 2)比较器可以很好的应用在特殊标准的排序上

​ 3)比较器可以很好的应用在根据特殊标准排序的结构上

​ 4)写代码变得异常容易,还用于范型编程

/*
 * T一定要是非基础类型,有基础类型需求包一层
 */
public class HeapGreater<T> {

	private ArrayList<T> heap;
	private HashMap<T, Integer> indexMap;
	private int heapSize;
	private Comparator<? super T> comp;

	public HeapGreater(Comparator<? super T> c) {
		heap = new ArrayList<>();
		indexMap = new HashMap<>();
		heapSize = 0;
		comp = c;
	}

	public boolean isEmpty() {
		return heapSize == 0;
	}

	public int size() {
		return heapSize;
	}

	public boolean contains(T obj) {
		return indexMap.containsKey(obj);
	}

	public T peek() {
		return heap.get(0);
	}

	public void push(T obj) {
		heap.add(obj);
		indexMap.put(obj, heapSize);
		heapInsert(heapSize++);
	}

	public T pop() {
		T ans = heap.get(0);
		swap(0, heapSize - 1);
		indexMap.remove(ans);
		heap.remove(--heapSize);
		heapify(0);
		return ans;
	}

	public void remove(T obj) {
		T replace = heap.get(heapSize - 1);
		int index = indexMap.get(obj);
		indexMap.remove(obj);
		heap.remove(--heapSize);
		if (obj != replace) {
			heap.set(index, replace);
			indexMap.put(replace, index);
			resign(replace);
		}
	}

	public void resign(T obj) {
		heapInsert(indexMap.get(obj));
		heapify(indexMap.get(obj));
	}

	// 请返回堆上的所有元素
	public List<T> getAllElements() {
		List<T> ans = new ArrayList<>();
		for (T c : heap) {
			ans.add(c);
		}
		return ans;
	}

	private void heapInsert(int index) {
		while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
			swap(index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

	private void heapify(int index) {
		int left = index * 2 + 1;
		while (left < heapSize) {
			int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left;
			best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index;
			if (best == index) {
				break;
			}
			swap(best, index);
			index = best;
			left = index * 2 + 1;
		}
	}

	private void swap(int i, int j) {
		T o1 = heap.get(i);
		T o2 = heap.get(j);
		heap.set(i, o2);
		heap.set(j, o1);
		indexMap.put(o2, i);
		indexMap.put(o1, j);
	}

}

第五节课

前缀树

​ 1)单个字符串中,字符从前到后的加到一棵多叉树上

​ 2)字符放在路上,节点上有专属的数据项(常见的是 pass 和 end 值)

​ 3)所有样本都这样填加,如果没有路就新建,如有路就复用

​ 4)沿途节点的 pass 值增加1,每个字符串结束时来到的节点 end 值增加1可以完成前缀相关的查询

例子

​ 设计一种结构。用户可以:

		1) voidinsert ( Stringstr )頃加某个字符串,可以重复頃加,每次算1个
		2) intsearch ( Stringstr )查询某个字符串在结构中还有几个
		3)  voiddelete ( Stringstr )删掉某个字符串,可以重复删除,每次算1个
		4)  intprefixNumber ( Stringstr )查询有多少个字符串,是以 str 做前缀的

固定数组实现:

class Trie {

		class Node {
			public int pass;
			public int end;
			public Node[] nexts;

			public Node() {
				pass = 0;
				end = 0;
				nexts = new Node[26];
			}
		}

		private Node root;

		public Trie() {
			root = new Node();
		}
		//加入word
		public void insert(String word) {
			if (word == null) {
				return;
			}
			char[] str = word.toCharArray();
			Node node = root;
			node.pass++;
			int path = 0;
			for (int i = 0; i < str.length; i++) { // 从左往右遍历字符
				path = str[i] - 'a'; // 由字符,对应成走向哪条路
				if (node.nexts[path] == null) {
					node.nexts[path] = new Node();
				}
				node = node.nexts[path];
				node.pass++;
			}
			node.end++;
		}
		//删除word
		public void erase(String word) {
			if (countWordsEqualTo(word) != 0) {
				char[] chs = word.toCharArray();
				Node node = root;
				node.pass--;
				int path = 0;
				for (int i = 0; i < chs.length; i++) {
					path = chs[i] - 'a';
					if (--node.nexts[path].pass == 0) {
						node.nexts[path] = null;
						return;
					}
					node = node.nexts[path];
				}
				node.end--;
			}
		}
		//word单词加入过几次
		public int countWordsEqualTo(String word) {
			if (word == null) {
				return 0;
			}
			char[] chs = word.toCharArray();
			Node node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = chs[i] - 'a';
				if (node.nexts[index] == null) {
					return 0;
				}
				node = node.nexts[index];
			}
			return node.end;
		}
		
		public int countWordsStartingWith(String pre) {
			if (pre == null) {
				return 0;
			}
			char[] chs = pre.toCharArray();
			Node node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = chs[i] - 'a';
				if (node.nexts[index] == null) {
					return 0;
				}
				node = node.nexts[index];
			}
			return node.pass;
		}
	}

哈希表实现:

class Trie {

		class Node {
			public int pass;
			public int end;
			public HashMap<Integer, Node> nexts;

			public Node() {
				pass = 0;
				end = 0;
				nexts = new HashMap<>();
			}
		}

		private Node root;

		public Trie() {
			root = new Node();
		}

		public void insert(String word) {
			if (word == null) {
				return;
			}
			char[] chs = word.toCharArray();
			Node node = root;
			node.pass++;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = (int) chs[i];
				if (!node.nexts.containsKey(index)) {
					node.nexts.put(index, new Node());
				}
				node = node.nexts.get(index);
				node.pass++;
			}
			node.end++;
		}

		public void erase(String word) {
			if (countWordsEqualTo(word) != 0) {
				char[] chs = word.toCharArray();
				Node node = root;
				node.pass--;
				int index = 0;
				for (int i = 0; i < chs.length; i++) {
					index = (int) chs[i];
					if (--node.nexts.get(index).pass == 0) {
						node.nexts.remove(index);
						return;
					}
					node = node.nexts.get(index);
				}
				node.end--;
			}
		}

		public int countWordsEqualTo(String word) {
			if (word == null) {
				return 0;
			}
			char[] chs = word.toCharArray();
			Node node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = (int) chs[i];
				if (!node.nexts.containsKey(index)) {
					return 0;
				}
				node = node.nexts.get(index);
			}
			return node.end;
		}

		public int countWordsStartingWith(String pre) {
			if (pre == null) {
				return 0;
			}
			char[] chs = pre.toCharArray();
			Node node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = (int) chs[i];
				if (!node.nexts.containsKey(index)) {
					return 0;
				}
				node = node.nexts.get(index);
			}
			return node.pass;
		}
	}

不基于比较的排序(桶排序)

​ 桶排序思想下的排序:计数排序&基数排序

​ 1)桶排序思想下的排序都是不基于比较的排序

​ 2)时间复杂度为 O ( N ),额外空间负载度 O ( M )

​ 3)应用范围有限,需要样本的数据状况满足桶的划分

计数排序—一般要求:样本整数而且范围较窄:

public static void countSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int max = Integer.MIN_VALUE;
		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;
			}
		}
	}

基数排序—一般是样本十进制的正整数

// only for no-negative value
	public static void radixSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		radixSort(arr, 0, arr.length - 1, maxbits(arr));
	}

	public static int maxbits(int[] arr) {
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int res = 0;
		while (max != 0) {
			res++;
			max /= 10;
		}
		return res;
	}

	// arr[L..R]排序  ,  最大值的十进制位数digit
	public static void radixSort(int[] arr, int L, int R, int digit) {
		final int radix = 10;
		int i = 0, j = 0;
		// 有多少个数准备多少个辅助空间
        //不再用桶来逐个放入, 而是直接算出桶里面应该有多少个元素, 这样就直接知道排序后下标的位置了.也就不需要桶了.
		int[] help = new int[R - L + 1];
		for (int d = 1; d <= digit; d++) { // 有多少位就进出几次
			// 10个空间
		    // count[0] 当前位(d位)是0的数字有多少个
			// count[1] 当前位(d位)是(0和1)的数字有多少个
			// count[2] 当前位(d位)是(0、1和2)的数字有多少个
			// count[i] 当前位(d位)是(0~i)的数字有多少个
			int[] count = new int[radix]; // count[0..9]
			for (i = L; i <= R; i++) {
				// 103  1   3
				// 209  1   9
				j = getDigit(arr[i], d);
				count[j]++;
			}
			for (i = 1; i < radix; i++) {
				count[i] = count[i] + count[i - 1];
			}
			for (i = R; i >= L; i--) {
				j = getDigit(arr[i], d);
				help[count[j] - 1] = arr[i];
				count[j]--;
			}
			for (i = L, j = 0; i <= R; i++, j++) {
				arr[i] = help[j];
			}
		}
	}

	public static int getDigit(int x, int d) {
		return ((x / ((int) Math.pow(10, d - 1))) % 10);
	}

排序算法的稳定性

​ 稳定性是指同样大小的样本再排序之后不会改变相对次序

​ 对基础类型来说,稳定性毫无意义

​ 对非基础类型来说,稳定性有重要意义

​ 有些排序算法可以实现成稳定的,而有些排序算法无论如何都实现不成稳定的

排序算法总结时间复杂度

后两者是不基于比较时间复杂度额外空间复杂度稳定性
选择排序O (NA2)O (1)
冒泡排序O (NA2)O (1)
插入排序O (NA2)O (1)
归并排序O ( N * logN )O ( N )
随机快排O ( N * logN )O ( logN )
堆排序O ( N * ogN )O ( 1 )
计数排序O ( N )O ( M )
基数排序O ( N )O ( N )

排序算法总结

​ 1)不基于比较的排序,对样本数据有严格要求,不易改写

​ 2)基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用

​ 3)基于比较的排序,时间复杂度的极限是 O ( N * logN )°4)时间复杂度 O ( N * logN )、额外空间复杂度低于 O ( N )、且稳定的基于比较的排序是不存在的。

​ 5)为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并

按需使用:稳定性 :选择归并 额外空间:堆排序 速度 :快排

第六节课

1.链表

使用用容器

1.1快慢指针

	   1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点

​ 2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点

​ 3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个

​ 4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个

public static class Node {
		public int value;
		public Node next;

		public Node(int v) {
			value = v;
		}
	}

	// head 头
	public static Node midOrUpMidNode(Node head) {
		if (head == null || head.next == null || head.next.next == null) {
			return head;
		}
		// 链表有3个点或以上
		Node slow = head.next;
		Node fast = head.next.next;
		while (fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}

	public static Node midOrDownMidNode(Node head) {
		if (head == null || head.next == null) {
			return head;
		}
		Node slow = head.next;
		Node fast = head.next;
		while (fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}

	public static Node midOrUpMidPreNode(Node head) {
		if (head == null || head.next == null || head.next.next == null) {
			return null;
		}
		Node slow = head;
		Node fast = head.next.next;
		while (fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}

	public static Node midOrDownMidPreNode(Node head) {
		if (head == null || head.next == null) {
			return null;
		}
		if (head.next.next == null) {
			return head;
		}
		Node slow = head;
		Node fast = head.next;
		while (fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}
		//使用用容器
	public static Node right1(Node head) {
		if (head == null) {
			return null;
		}
		Node cur = head;
		ArrayList<Node> arr = new ArrayList<>();
		while (cur != null) {
			arr.add(cur);
			cur = cur.next;
		}
		return arr.get((arr.size() - 1) / 2);
	}

1.2给定一个单链表head,判断单链表是否为回文结构

public static boolean isPalindrome1(Node head) {
		Stack<Node> stack = new Stack<Node>();
		Node cur = head;
		while (cur != null) {
			stack.push(cur);
			cur = cur.next;
		}
		while (head != null) {
			if (head.value != stack.pop().value) {
				return false;
			}
			head = head.next;
		}
		return true;
	}
public static boolean isPalindrome2(Node head) {
		if (head == null || head.next == null) {
			return true;
		}
		Node right = head.next;
		Node cur = head;
		while (cur.next != null && cur.next.next != null) {
			right = right.next;
			cur = cur.next.next;
		}
		Stack<Node> stack = new Stack<Node>();
		while (right != null) {
			stack.push(right);
			right = right.next;
		}
		while (!stack.isEmpty()) {
			if (head.value != stack.pop().value) {
				return false;
			}
			head = head.next;
		}
		return true;
	}

修改原链表

// need O(1) extra space
	public static boolean isPalindrome3(Node head) {
		if (head == null || head.next == null) {
			return true;
		}
		Node n1 = head;
		Node n2 = head;
		while (n2.next != null && n2.next.next != null) { // find mid node
			n1 = n1.next; // n1 -> mid
			n2 = n2.next.next; // n2 -> end
		}
		// n1 中点
		
		
		n2 = n1.next; // n2 -> right part first node
		n1.next = null; // mid.next -> null
		Node n3 = null;
		while (n2 != null) { // right part convert
			n3 = n2.next; // n3 -> save next node
			n2.next = n1; // next of right node convert
			n1 = n2; // n1 move
			n2 = n3; // n2 move
		}
		n3 = n1; // n3 -> save last node
		n2 = head;// n2 -> left first node
		boolean res = true;
		while (n1 != null && n2 != null) { // check palindrome
			if (n1.value != n2.value) {
				res = false;
				break;
			}
			n1 = n1.next; // left to mid
			n2 = n2.next; // right to mid
		}
		n1 = n3.next;
		n3.next = null;
		while (n1 != null) { // recover list
			n2 = n1.next;
			n1.next = n3;
			n3 = n1;
			n1 = n2;
		}
		return res;
	}

1.3将单链表遵照某值左边小,中间等于,右边大

将单链表放于数组里,在数组里面做partition

public static Node listPartition1(Node head, int pivot) {
		if (head == null) {
			return head;
		}
		Node cur = head;
		int i = 0;
		while (cur != null) {
			i++;
			cur = cur.next;
		}
		Node[] nodeArr = new Node[i];
		i = 0;
		cur = head;
		for (i = 0; i != nodeArr.length; i++) {
			nodeArr[i] = cur;
			cur = cur.next;
		}
		arrPartition(nodeArr, pivot);
		for (i = 1; i != nodeArr.length; i++) {
			nodeArr[i - 1].next = nodeArr[i];
		}
		nodeArr[i - 1].next = null;
		return nodeArr[0];
	}
public static void arrPartition(Node[] nodeArr, int pivot) {
		int small = -1;
		int big = nodeArr.length;
		int index = 0;
		while (index != big) {
			if (nodeArr[index].value < pivot) {
				swap(nodeArr, ++small, index++);
			} else if (nodeArr[index].value == pivot) {
				index++;
			} else {
				swap(nodeArr, --big, index);
			}
		}
	}

	public static void swap(Node[] nodeArr, int a, int b) {
		Node tmp = nodeArr[a];
		nodeArr[a] = nodeArr[b];
		nodeArr[b] = tmp;
	}

分成小中大三部分,各部分串起来

public static Node listPartition2(Node head, int pivot) {
		Node sH = null; // small head
		Node sT = null; // small tail
		Node eH = null; // equal head
		Node eT = null; // equal tail
		Node mH = null; // big head
		Node mT = null; // big tail
		Node next = null; // save next node
		// every node distributed to three lists
		while (head != null) {
			next = head.next;
			head.next = null;
			if (head.value < pivot) {
				if (sH == null) {
					sH = head;
					sT = head;
				} else {
					sT.next = head;
					sT = head;
				}
			} else if (head.value == pivot) {
				if (eH == null) {
					eH = head;
					eT = head;
				} else {
					eT.next = head;
					eT = head;
				}
			} else {
				if (mH == null) {
					mH = head;
					mT = head;
				} else {
					mT.next = head;
					mT = head;
				}
			}
			head = next;
		}
		// 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头
		if (sT != null) { // 如果有小于区域
			sT.next = eH;
			eT = eT == null ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT
		}
		// 下一步,一定是需要用eT 去接 大于区域的头
		// 有等于区域,eT -> 等于区域的尾结点
		// 无等于区域,eT -> 小于区域的尾结点
		// eT 尽量不为空的尾巴节点
		if (eT != null) { // 如果小于区域和等于区域,不是都没有
			eT.next = mH;
		}
		return sH != null ? sH : (eH != null ? eH : mH);
	}

1.4一种特殊的单链表节点类

描述如下

public static class Node {
		int val;
		Node next;
		Node random;

		public Node(int val) {
			this.val = val;
			this.next = null;
			this.random = null;
		}
	}

​ rand 指针是单链表节点结构中新增的指针, rand 可能指向链表中的任意一个节点,也可能指向 null 。

​ 给定一个由 Node 节点类型组成的无环单链表的头节点 head ,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。

【要求】

​ 时间复杂度 O ( N ),额外空间复杂度 O (1)

哈希表

public static Node copyRandomList1(Node head) {
		// key 老节点
		// value 新节点
		HashMap<Node, Node> map = new HashMap<Node, Node>();
		Node cur = head;
		while (cur != null) {
			map.put(cur, new Node(cur.val));
			cur = cur.next;
		}
		cur = head;
		while (cur != null) {
			// cur 老
			// map.get(cur) 新
			// 新.next ->  cur.next克隆节点找到
			map.get(cur).next = map.get(cur.next);
			map.get(cur).random = map.get(cur.random);
			cur = cur.next;
		}
		return map.get(head);
	}

public static Node copyRandomList2(Node head) {
		if (head == null) {
			return null;
		}
		Node cur = head;
		Node next = null;
		// 1 -> 2 -> 3 -> null
		// 1 -> 1' -> 2 -> 2' -> 3 -> 3'
		while (cur != null) {
			next = cur.next;
			cur.next = new Node(cur.val);
			cur.next.next = next;
			cur = next;
		}
		cur = head;
		Node copy = null;
		// 1 1' 2 2' 3 3'
		// 依次设置 1' 2' 3' random指针
		while (cur != null) {
			next = cur.next.next;
			copy = cur.next;
			copy.random = cur.random != null ? cur.random.next : null;
			cur = next;
		}
		Node res = head.next;
		cur = head;
		// 老 新 混在一起,next方向上,random正确
		// next方向上,把新老链表分离
		while (cur != null) {
			next = cur.next.next;
			copy = cur.next;
			cur.next = next;
			copy.next = next != null ? next.next : null;
			cur = next;
		}
		return res;
	}

1.5链表

​ 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回 nul
【要求】
如果两个链表长度之和为 N ,时间复杂度请达到 O ( N ),额外空间复杂度请达到 O (1)。’

哈希set方法::一小时半

public static Node getIntersectNode(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node loop1 = getLoopNode(head1);
		Node loop2 = getLoopNode(head2);
		if (loop1 == null && loop2 == null) {
			return noLoop(head1, head2);
		}
		if (loop1 != null && loop2 != null) {
			return bothLoop(head1, loop1, head2, loop2);
		}
		return null;
	}

	// 找到链表第一个入环节点,如果无环,返回null
	public static Node getLoopNode(Node head) {
		if (head == null || head.next == null || head.next.next == null) {
			return null;
		}
		// n1 慢  n2 快
		Node slow = head.next; // n1 -> slow
		Node fast = head.next.next; // n2 -> fast
		while (slow != fast) {
			if (fast.next == null || fast.next.next == null) {
				return null;
			}
			fast = fast.next.next;
			slow = slow.next;
		}
		// slow fast  相遇
		fast = head; // n2 -> walk again from head
		while (slow != fast) {
			slow = slow.next;
			fast = fast.next;
		}
		return slow;
	}

	// 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
	public static Node noLoop(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node cur1 = head1;
		Node cur2 = head2;
		int n = 0;
		while (cur1.next != null) {
			n++;
			cur1 = cur1.next;
		}
		while (cur2.next != null) {
			n--;
			cur2 = cur2.next;
		}
		if (cur1 != cur2) {
			return null;
		}
		// n  :  链表1长度减去链表2长度的值
		cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1
		cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
		n = Math.abs(n);
		while (n != 0) {
			n--;
			cur1 = cur1.next;
		}
		while (cur1 != cur2) {
			cur1 = cur1.next;
			cur2 = cur2.next;
		}
		return cur1;
	}

	// 两个有环链表,返回第一个相交节点,如果不想交返回null
	public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
		Node cur1 = null;
		Node cur2 = null;
		if (loop1 == loop2) {
			cur1 = head1;
			cur2 = head2;
			int n = 0;
			while (cur1 != loop1) {
				n++;
				cur1 = cur1.next;
			}
			while (cur2 != loop2) {
				n--;
				cur2 = cur2.next;
			}
			cur1 = n > 0 ? head1 : head2;
			cur2 = cur1 == head1 ? head2 : head1;
			n = Math.abs(n);
			while (n != 0) {
				n--;
				cur1 = cur1.next;
			}
			while (cur1 != cur2) {
				cur1 = cur1.next;
				cur2 = cur2.next;
			}
			return cur1;
		} else {
			cur1 = loop1.next;
			while (cur1 != loop1) {
				if (cur1 == loop2) {
					return loop1;
				}
				cur1 = cur1.next;
			}
			return null;
		}
	}

1.6链表(一)

​ 能不能不给单链表头结点,只给想要删除的节点就能做到在链表上把这个点删掉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值