【算法训练营】 - ② 链表结构、栈、队列、递归行为、哈希表和有序表

【算法训练营】 - ② 链表结构、栈、队列、递归行为、哈希表和有序表

https://www.bilibili.com/video/BV1Ef4y1T7Qi
https://github.com/algorithmzuo

链表

单向链表

单向链表节点结构(可以实现成泛型)

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

双向链表节点结构

public class DoubleNode{
	public int value;
	public DoubleNode last;
	public DoubleNode next;
	public DoubleNode(int data){
		value = data;
	}
}

单向链表和双向链表最简单的练习
链表相关的问题几乎都是coding问题

  1. 单链表和双链表如何翻转
  2. 把给定值都删除

这里就是熟悉结构。链表还有哪些常见面试题,后续有专门一节来系统学习。

例子1:翻转链表

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

例子2:删除给定值

// head = removeValue(head, 2);
public static Node removeValue(Node head, int num) {
    // head来到第一个不需要删的位置
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    // 1 ) head == null
    // 2 ) head != null
    Node pre = head;
    Node cur = head;
    while (cur != null) {
        if (cur.value == num) {
            pre.next = cur.next;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}

栈和队列

逻辑概念
栈:数据先进后出,犹如弹匣
队列:数据先进先出,好似排队

实际实现

  1. 双向链表
	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();
        }

    }
  1. 数组实现
public class Code04_RingArray {

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

	}

}

既然语言都有这些结构和API,为什么还需要手撸练习?

  1. 算法问题无关语言
  2. 语言提供的API都是有限的,当有新的功能是API不提供的,就需要改写
  3. 任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的

栈和队列的常见面试题

怎么用数组实现不超过固定大小的队列和栈?
栈:正常使用
队列:环形数组,见上方代码

实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
注意: 1) pop、push、getMin操作的时间复杂度都是O(1); 2) 设计的栈类型可以使用现成的栈结构;

public class Code05_GetMinStack {

	public static class MyStack1 {
		private Stack<Integer> stackData;
		private Stack<Integer> stackMin;

		public MyStack1() {
			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);
			}
			this.stackData.push(newNum);
		}

		public int pop() {
			if (this.stackData.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			int value = this.stackData.pop();
			if (value == this.getmin()) {
				this.stackMin.pop();
			}
			return value;
		}

		public int getmin() {
			if (this.stackMin.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			return this.stackMin.peek();
		}
	}

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

如何用栈结构实现队列结构?

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

	}
}

递归

· 怎么从思想上理解递归?
· 怎么从实践操作上理解递归?

例子:求数组arr[L…R]中的最大值,怎么用递归方法实现。

  1. 将[L…R] 范围分成左右两半。左 [L…Mid] 右 [Mid+1…R]
  2. 左部分求最大值,右部分求最大值
  3. [L…R] 范围上的最大值,是max{ 左部分最大值,右部分最大值 }
    注意: 2)是个递归过程,当范围上只有一个数,就可以不用再递归了
public class Code08_GetMax {

	// 求arr中的最大值
	public static int getMax(int[] arr) {
		return process(arr, 0, arr.length - 1);
	}

	// arr[L..R]范围上求最大值  L ... R   N
	public static int process(int[] arr, int L, int R) {
		// arr[L..R]范围上只有一个数,直接返回,base case
		if (L == R) { 
			return arr[L];
		}
		// L...R 不只一个数
		// mid = (L + R) / 2
		int mid = L + ((R - L) >> 1); // 中点   	1
		int leftMax = process(arr, L, mid);
		int rightMax = process(arr, mid + 1, R);
		return Math.max(leftMax, rightMax);
	}

}

递归的脑图和实际实现
对于新手来说,把调用的过程画出结构图是必须的,这十分有利于分析递归。
递归并不是玄学,递归底层是利用系统栈来实现的。
任何递归函数都一定可以改成非递归。

Master 公式
形如
T(N) = a * T(N/b) + O(N^d) (其中的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)

哈希表

HashMap, HashSet

  1. 哈希表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用HashSet结构
  3. 如果既有key,又有伴随数据value,可以使用HashMap结构
  4. 有无伴随数据,是HashMap和HashSet唯一的区别,实际结构是一回事
  5. 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为o(1),但是常数时间比较大
  6. 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小
  7. 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是8字节

有序表

TreeMap -> AVL树,SB树,红黑树

  1. 有序表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用TreeSet结构
  3. 如果既有key,又有伴随数据value,可以使用TreeMap结构
  4. 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事5)有序表把key按照顺序组织起来,而哈希表完全不组织
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值