链表结构、栈、队列、递归行为、哈希表和有序表
链表节点结构
单向链表节点结构
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、反转单向链表和双向链表
package com.harrison.two; import java.util.ArrayList; import java.util.List; //反转单向链表和双向链表 public class Code01_ReverseList { public static class Node { public int value; public Node next; public Node(int data) {// 构造方法 this.value = data; } } public static class DoubleNode { public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int data) { this.value = data; } } // 反转单链表 // 1->2->3->4 public static Node reverseLinkedList(Node head) { Node pre = null; Node next = null; while (head != null) { next = head.next;// next指向2,意味着next就是2->3->4 head.next = pre;// head.next为null,意味着1和2断开,此时head就是1 pre = head;// pre为1,这是反转后的链表 head = next;// head重新赋值为next,也就是2->3->4 } 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 testReverseLinkedList(Node head) { if (head == null) { return null; } ArrayList<Node> list = new ArrayList<>(); while (head != null) { list.add(head); head = head.next; } list.get(0).next = null; int N = list.size(); for (int i = 1; i < N; i++) { list.get(i).next = list.get(i - 1); } return list.get(N - 1); } public static DoubleNode testReverseDoubleList(DoubleNode head) { if (head == null) { return null; } ArrayList<DoubleNode> list = new ArrayList<>(); while (head != null) { list.add(head); head = head.next; } list.get(0).next = null; DoubleNode pre = list.get(0); int N = list.size(); for (int i = 1; i < N; i++) { DoubleNode cur = list.get(i); cur.last = null; cur.next = pre; pre.last = cur; pre = cur; } return list.get(N - 1); } public static Node generateRandomLinkedList(int len, int value) { int size = (int) (Math.random() * (len + 1)); if (size == 0) { return null; } size--; Node head = new Node((int) (Math.random() * (value + 1))); Node pre = head; while (size != 0) { Node cur = new Node((int) (Math.random() * (value + 1))); pre.next = cur; pre = cur; size--; } return head; } public static DoubleNode generateRandomDoubleList(int len, int value) { int size = (int) (Math.random() * (len + 1)); if (size == 0) { return null; } size--; DoubleNode head = new DoubleNode((int) (Math.random() * (value + 1))); DoubleNode pre = head; while (size != 0) { DoubleNode cur = new DoubleNode((int) (Math.random() * (value + 1))); pre.next = cur; cur.last = pre; pre = cur; size--; } return head; } public static List<Integer> getLinkedListOriginOrder(Node head) { List<Integer> ans = new ArrayList<>(); while (head != null) { ans.add(head.value); head = head.next; } return ans; } public static boolean checkLinkedListReverse(List<Integer> origin, Node head) { for (int i = origin.size() - 1; i >= 0; i--) { if (!origin.get(i).equals(head.value)) { return false; } head = head.next; } return true; } public static List<Integer> getDoubleListOriginOrder(DoubleNode head) { List<Integer> ans = new ArrayList<>(); while (head != null) { ans.add(head.value); head = head.next; } return ans; } public static boolean checkDoubleListReverse(List<Integer> origin, DoubleNode head) { DoubleNode end = null; for (int i = origin.size() - 1; i >= 0; i--) { if (!origin.get(i).equals(head.value)) { return false; } end = head; head = head.next; } for (int i = 0; i < origin.size(); i++) { if (!origin.get(i).equals(end.value)) { return false; } end = end.last; } return true; } public static void main(String[] args) { int len = 50; int value = 100; int testTime = 100000; System.out.println("test begin!"); for (int i = 0; i < testTime; i++) { Node node1 = generateRandomLinkedList(len, value); List<Integer> list1 = getLinkedListOriginOrder(node1); node1 = reverseLinkedList(node1); if (!checkLinkedListReverse(list1, node1)) { System.out.println("Oops1!"); } Node node2 = generateRandomLinkedList(len, value); List<Integer> list2 = getLinkedListOriginOrder(node2); node2 = testReverseLinkedList(node2); if (!checkLinkedListReverse(list2, node2)) { System.out.println("Oops2!"); } DoubleNode node3 = generateRandomDoubleList(len, value); List<Integer> list3 = getDoubleListOriginOrder(node3); node3 = reverseDoubleList(node3); if (!checkDoubleListReverse(list3, node3)) { System.out.println("Oops3!"); } DoubleNode node4 = generateRandomDoubleList(len, value); List<Integer> list4 = getDoubleListOriginOrder(node4); node4 = reverseDoubleList(node4); if (!checkDoubleListReverse(list4, node4)) { System.out.println("Oops4!"); } } System.out.println("test finish!"); } }
2、把给定值都删除
package com.harrison.two; //删除给定值 public class Code02_DeleteGivenValue { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } 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; } }
栈和队列的实际实现
其底层实现原理双向链表的调整,而且时间复杂度都是O(1),跟数据量没有关系,因为总是在操作一个头和一个尾。实现方式有两种:双向链表实现和数组实现
栈和队列常见面试题:怎么用数组实现不超过固定大小的队列和栈?栈——正常使用 队列——环形数组
1、双向链表实现
package com.harrison.two; import java.util.Stack; import java.util.LinkedList; import java.util.Queue; //栈和队列的实际实现:1、双向链表实现 //使用双向链表节点类型实现一个队列 public class Code03_DoubleEndsQueueToStackAndQueue { // 双向链表节点类型 public static class Node<T> { public T value; public Node<T> last; public Node<T> next; public Node(T data) { this.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 void main(String[] args) { int oneTestDataNum = 100; int value = 10000; int testTimes = 100000; for (int i = 0; i < testTimes; i++) { MyStack<Integer> myStack = new MyStack<>(); MyQueue<Integer> myQueue = new MyQueue<>(); Stack<Integer> stack = new Stack<>(); Queue<Integer> queue = new LinkedList<>(); for (int j = 0; j < oneTestDataNum; j++) { int nums = (int) (Math.random() * value); if (stack.isEmpty()) { myStack.push(nums); stack.push(nums); } else { if (Math.random() < 0.5) { myStack.push(nums); stack.push(nums); } else { if (!isEqual(myStack.pop(), stack.pop())) { System.out.println("oops!"); } } } int numq = (int) (Math.random() * value); if (stack.isEmpty()) { myQueue.push(numq); queue.offer(numq); } else { if (Math.random() < 0.5) { myQueue.push(numq); queue.offer(numq); } else { if (!isEqual(myQueue.poll(), queue.poll())) { System.out.println("oops!"); } } } } } System.out.println("finish!"); } }
2、数组实现
package com.harrison.two; //栈和队列的实际实现:2、数组实现 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; } } }
3、实现一个特殊的栈,在基本功能(pop()和push())的基础上,再实现返回栈中最小元素的功能
- pop()、push()、getMin()操作的时间都是O(1)
- 设计的栈类型可以使用现成的栈结构
分析1):设计两个栈,data栈和min栈,data栈是普通的栈,正常加数据,第一个数两个栈都加,从第二个数开始,min栈加数据的原则为:当前的数和目前最小栈的栈顶谁小加谁。所以说,data栈和min栈同步上升,弹出数据的时候两个栈一起弹,弹出数据没有规则。这种方法费一点空间,省一点时间,因为弹出时不要判断。
分析2):前面相同,从第二个数开始,如果当前数比最小栈的栈顶大,不压入min栈;如果当前数小于等于最小栈的栈顶,压入min栈。弹出规则:当前要弹出的数与最小栈的栈顶相等的时候弹出。这种方法省一点空间,费一点时间
package com.harrison.two; import java.util.Stack; //实现一个特殊的栈,在基本功能(pop()和push())的基础上,再实现返回栈中最小元素的功能 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 static void main(String[] args) { MyStack1 stack1 = new MyStack1(); stack1.push(3); System.out.println(stack1.getmin()); stack1.push(4); System.out.println(stack1.getmin()); stack1.push(1); System.out.println(stack1.getmin()); System.out.println(stack1.pop()); System.out.println(stack1.getmin()); System.out.println("============="); MyStack1 stack2 = new MyStack1(); stack2.push(3); System.out.println(stack2.getmin()); stack2.push(4); System.out.println(stack2.getmin()); stack2.push(1); System.out.println(stack2.getmin()); System.out.println(stack2.pop()); System.out.println(stack2.getmin()); } }
如何用栈结构实现队列结构(用两个栈)
如何用两个栈拼队列结构:设计两个栈,push栈和pop栈,压入pop栈的时候,pop必须为空。push栈倒出数据的时候,一定要一次全都倒空
package com.harrison.two; import java.util.Stack; //如何用栈结构实现队列结构:用两个栈拼队列结构 public class Code06_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(); } } public static void main(String[] args) { TwoStacksQueue test = new TwoStacksQueue(); test.add(1); test.add(2); test.add(3); System.out.println(test.peek()); System.out.println(test.poll()); System.out.println(test.peek()); System.out.println(test.poll()); System.out.println(test.peek()); System.out.println(test.poll()); } }
如何用队列结构实现栈结构(用两个队列)
package com.harrison.two; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; //如何用队列结构实现栈结构(用两个队列) 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 static void main(String[] args) { System.out.println("test begin"); TwoQueueStack<Integer> myStack = new TwoQueueStack<>(); Stack<Integer> test = new Stack<>(); int testTime = 1000000; int max = 1000000; for (int i = 0; i < testTime; i++) { if (myStack.isEmpty()) { if (!test.isEmpty()) { System.out.println("Oops"); } int num = (int) (Math.random() * max); myStack.push(num); test.push(num); } else { if (Math.random() < 0.25) { int num = (int) (Math.random() * max); myStack.push(num); test.push(num); } else if (Math.random() < 0.5) { if (!myStack.peek().equals(test.peek())) { System.out.println("Oops"); } } else if (Math.random() < 0.75) { if (!myStack.poll().equals(test.pop())) { System.out.println("Oops"); } } else { if (myStack.isEmpty() != test.isEmpty()) { System.out.println("Oops"); } } } } System.out.println("test finish!"); } }
递归
- 怎么从思想上理解递归
- 怎么从实际实现的角度出发理解递归
递归是有实际结构支持其运行的,任何递归行为都能改成非递归行为,方法就是不让系统帮忙压栈,而是自己去压栈模拟这个行为。因为递归实际上运用的是系统栈。
例:求数组arr[L…R]中的最大值,怎么用递归方法实现:
- 将arr[L…R]范围分成左右两半,左边[L…Mid],右边[Mid+1…R]
- 左部分求最大值,右部分求最大值
- [L…R]范围上的最大值就是max{左部分最大值,右部分最大值}
注意:2是个递归过程,当范围上只有一个数,就可以不用再递归了
package com.harrison.two; 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); } }
哈希表和有序表
哈希表
Java中int、double、float等基本类型是按值传递;Integer、Double、Float等包裹出来的大类型,使用的时候是按引用传递,但是如果值在-128~127范围时,又变成按值传递。超过此范围才按引用传递。但是这些大类型在哈希表里一律按值传递!!!但是哈希表并不总是按值传递,非系统规定的类型,如自己定义的类型就会按引用传递。
哈希表在使用层面可以理解为一种集合结构,如果只有key,没有伴随数据value,可以使用HashSet结构;如果都有,可以使用HashMap结构。有无伴随数据是二者唯一的区别,实际结构是一回事。
使用哈希表增删改查的操作,可以认为时间复杂度为O(1),但是常数时间比较大。
有序表
哈希表所有的增删改查有序表都支持,次外,可以把记录乱序存入有序表,但在有序表内部是按顺序组织好的,从小到大的顺序。但是以上操作的时间复杂度为O(logn)。
package com.harrison.two; import java.util.HashMap; import java.util.HashSet; import java.util.TreeMap; public class HashMapAndSortedMap { public static class Node{ public int value; public Node(int v) { value=v; } } public static void main(String[] args) { // key value HashMap<Integer, String>map=new HashMap<>(); map.put(1,"我是1"); map.put(2,"我是2"); map.put(3,"我是3"); map.put(4,"我是4"); System.out.println(map.containsKey(1)); System.out.println(map.containsKey(10)); System.out.println(map.get(4)); System.out.println(map.get(10)); System.out.println(map.get(10)==null); map.put(4,"他是4"); System.out.println(map.get(4)); map.remove(4); System.out.println(map.get(4)); // key HashSet<String> set=new HashSet<>(); set.add("abc"); set.contains("abc"); set.remove("abc"); //哈希表 增、删、改、查,在使用时,时间复杂度都是O(1) System.out.println("====================="); int a=100000; int b=100000; System.out.println(a==b); Integer c=100000; Integer d=100000; System.out.println(c==d); Integer e=127; Integer f=127; System.out.println(e==f); Integer g=128; Integer h=128; System.out.println(g==h); HashMap<Node, String> map2=new HashMap<>(); Node node1=new Node(1); Node node2=node1; map2.put(node1, "我是node1"); map2.put(node2, "我是node1"); System.out.println(map2.size()); System.out.println("======================"); TreeMap<Integer, String> treeMap=new TreeMap<>(); treeMap.put(10,"我是10"); treeMap.put(2,"我是2"); treeMap.put(40,"我是40"); treeMap.put(7,"我是7"); System.out.println(treeMap.containsKey(1)); System.out.println(treeMap.containsKey(10)); System.out.println(treeMap.get(4)); System.out.println(treeMap.get(10)); System.out.println(treeMap.get(10)==null); treeMap.put(4,"他是4"); System.out.println(treeMap.get(4)); treeMap.remove(4); System.out.println(treeMap.get(4)); System.out.println(treeMap.firstKey()); System.out.println(treeMap.lastKey()); //<=8 System.out.println(treeMap.floorKey(8)); //>=8 System.out.println(treeMap.ceilingKey(8)); } }