数据结构与算法学习


学习路线

搞定1,2,3差不多达到面试水平

杂记

子问题规模一样的递归可以用Master公式求时间复杂度

T(N) = a * T(N/b) + O(N ^ d);

a:会有几次调用代码

b:每次调用规模的大小

  • 如果 log(b , a) < d 时间复杂度为: O(N^d)
  • 如果log (b , a) > d 时间复杂度为 : O(N^(log(b,a)))
  • 如果log(b , a)== d 时间复杂度为:O(N^d * log(2,n)))
    注:log(b,a) 指以b为地a为指数

基础的数据结构

实现栈和队列

用数组实现队列

// 给定一个长度被限制数组空间,如何实现队列?
public class RingArray{
    public static class MyQueue{
        private int[]arr;
        private int pushi;
        private int rolli;
        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("队列空了,不能再拿了");
            }
            int ans = arr[rolli];
            rolli = nextIndex(rolli);
            return ans;
        }
        
        public boolean isEmpty(){
            return size == 0;
        }
        
        private int nextIndex(int i){
            return i < limit - 1 ? i + 1: 0;
        }
    }
}

数组实现一个特殊的栈,可实时返回栈中最小值

//在基本功能上,再实现返回栈中最小元素的功能,且pop、push、getMin操作的时间复杂度是O(1).

//思路:维护一个栈和一个最小值栈,最小栈中存此时栈中的最小值。

public class GetMinStack {

	public static class MyStack {
		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();
		}
	}

}

如何用栈实现队列

// 思路:一个push栈,一个pop栈,把push栈的元素放进pop栈再取出来
// 为保证顺序,1、pop栈不为空时,push栈不能往pop栈里倒元素。2、push栈倒元素需要一次性倒完

public class TwoStacksImplementQueue {

	public static class TwoStacksQueue {
		public Stack<Integer> stackPush;
		public Stack<Integer> stackPop;

		public TwoStacksQueue() {
			stackPush = new Stack<Integer>();
			stackPop = new Stack<Integer>();
		}

		// push栈向pop栈倒入数据
		private void pushToPop() {
			if (stackPop.empty()) {
				while (!stackPush.empty()) {
					stackPop.push(stackPush.pop());
				}
			}
		}

		public void add(int pushInt) {
			stackPush.push(pushInt);
			pushToPop();
		}

		public int poll() {
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			}
			pushToPop();
			return stackPop.pop();
		}

		public int peek() {
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			}
			pushToPop();
			return stackPop.peek();
		}
	}
}

如何用队列实现栈

//实现poll或peek功能是第一个队列中除最后一个元素依次取出加进另一个队列,剩下的那个就是所需要的元素
public class Code07_TwoQueueImplementStack {

	public static class TwoQueueStack<T> {
		public Queue<T> queue;
		public Queue<T> help;

		public TwoQueueStack() {
			queue = new LinkedList<>();
			help = new LinkedList<>();
		}

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

		public T poll() {
			while (queue.size() > 1) {
				help.offer(queue.poll());
			}
			T ans = queue.poll();
			Queue<T> tmp = queue;
			queue = help;
			help = tmp;
			return ans;
		}

		public T peek() {
			while (queue.size() > 1) {
				help.offer(queue.poll());
			}
			T ans = queue.poll();
			help.offer(ans);
			Queue<T> tmp = queue;
			queue = help;
			help = tmp;
			return ans;
		}

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

public class Heap {

	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;
		}

		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;
			}
		}

		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;
		}

	}
}

排序

选择排序

0~N-1上找到最小值与下标为0的值交换

1~N-1上找到最小值与下标为1的值交换

2~N-1上找到最小值与下标为2的值交换

······

public static void selectionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 0 ~ N-1  找到最小值,在哪,放到0位置上
		// 1 ~ n-1  找到最小值,在哪,放到1 位置上
		// 2 ~ n-1  找到最小值,在哪,放到2 位置上
		for (int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}
	}

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

冒泡排序

public static void bubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 0 ~ N-1
		// 0 ~ N-2
		// 0 ~ N-3
		for (int e = arr.length - 1; e > 0; e--) { // 0 ~ e
			for (int i = 0; i < e; i++) {
				if (arr[i] > arr[i + 1]) {
					swap(arr, i, i + 1);
				}
			}
		}
	}

	// 交换arr的i和j位置上的值,i和j是一个位置的话,会出错
	public static 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 void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 不只1个数
		for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
			for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
				swap(arr, j, j + 1);
			}
		}
	}

	// i和j是一个位置的话,会出错
	public static 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 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];
		}
	}

变形1:

给定一个数组,求出每个元素左边比他小的元素之和,再求每个元素左边比他小的元素之和的累加和,要求小于O(N^2)

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;
	}

变形2:

求逆序对的数量

给定一个序列有n个数,求n个数中逆序对的个数,逆序对的定义:i < j && a[i] > a[j]。

int merge_sort(int a[], int l ,int r){
    //序列只有一个数
    if (l == r) return 0;
    //递归左边和右边
    int mid = l + r >> 1;
    int res = merge_sort(a, l , mid) + merge_sort(a, mid + 1, r);
    //归并的过程
    int i = l , j = mid + 1, k = 0;
    while (i <= mid && j <= r){
        if (a[i] <= a[j]) t[k++] = a[i++];
        else{
            t[k++] = a[j++];
            res += mid - i + 1;
        }
    }
    while (i <= mid) t[k++] = a[i++];
    while (j <= r) t[k++] = a[j++];
    
    //还原数组
    for (int i = 0 , j = l ; j <= r ; i ++ , j ++) a[j] = t[i];
    
    return res;
}

变形3:

翻转对

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

	public static int process(int[] nums, int l, int r) {
		if (l == r) {
			return 0;
		}
		// l < r
		int mid = l + ((r - l) >> 1);
		return process(nums, l, mid) + process(nums, mid + 1, r) + merge(nums, l, mid, r);
	}

	public static int merge(int[] nums, int L, int m, int r) {
		// [L....M] [M+1....R]
		int ans = 0;
		int windowR = m + 1;
		for (int i = L; i <= m; i++) {
			while (windowR <= r && (long) nums[i] > (long) nums[windowR] * 2) {
				windowR++;
			}
			ans += windowR - m - 1;
		}
		int[] help = new int[r - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = m + 1;
		while (p1 <= m && p2 <= r) {
			help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
		}
		while (p1 <= m) {
			help[i++] = nums[p1++];
		}
		while (p2 <= r) {
			help[i++] = nums[p2++];
		}
		for (i = 0; i < help.length; i++) {
			nums[L + i] = help[i];
		}
		return ans;
	}
}

堆排序

public class HeapSort{
    public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		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);
		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
			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;
		}
	}

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

位运算

int a = 7;
int b = -a = (~a) + 1 = -7;
// arr中,只有一种数,出现奇数次
public static void printOddTimesNum1(int[] arr) {
	int eor = 0;
	for (int i = 0; i < arr.length; i++) {
		eor ^= arr[i];
	}
	System.out.println(eor);
}

// arr中,有两种数,出现奇数次
public static void printOddTimesNum2(int[] arr) {
	int eor = 0;
	for (int i = 0; i < arr.length; i++) {
		eor ^= arr[i];
	}
	// a 和 b是两种数
	// eor != 0
	// eor最右侧的1,提取出来
	// eor :     00110010110111000
	// rightOne :00000000000001000
	int rightOne = eor & (-eor); // 提取出最右的1
	
	
	int onlyOne = 0; // eor'
	for (int i = 0 ; i < arr.length;i++) {
		//  arr[1] =  111100011110000
		// rightOne=  000000000010000
		if ((arr[i] & rightOne) != 0) {
			onlyOne ^= arr[i];
		}
	}
	System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
   // 输入一定能够保证,数组中所有的数都出现了M次,只有一种数出现了K次
   // 1 <= K < M
   // 返回这种数
   public static int km(int[] arr, int k, int m) {
       int[] help = new int[32];
       for (int num : arr) {
           for (int i = 0; i < 32; i++) {
               help[i] += (num >> i) & 1;
           }
       }
       int ans = 0;
       for (int i = 0; i < 32; i++) {
           help[i] %= m;
           if (help[i] != 0) {
               ans |= 1 << i;
           }
       }
       return ans;
   }

二叉树