單向鏈表的節點結構(能夠實現成泛型) :java
public class Node { public int value; public Node next; public Node(int data) { value = data; } }
雙向鏈表的節點結構(能夠實現成功泛型):node
public static class DoubleNode { public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int data) { value = data; } }
1 -> 2 -> 3 轉換爲 3 -> 2 -> 1面試
package class02; import java.util.ArrayList; public class Code01_ReverseList { 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; } } // 翻轉單向鏈表,傳入頭結點 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 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 boolean checkLinkedListEqual(Node head1, Node head2) { while (head1 != null && head2 != null) { if (head1.value != head2.value) { return false; } head1 = head1.next; head2 = head2.next; } return head1 == null && head2 == null; } // 要求無環,有環別用這個函數 public static boolean checkDoubleListEqual(DoubleNode head1, DoubleNode head2) { boolean null1 = head1 == null; boolean null2 = head2 == null; if (null1 && null2) { return true; } if (null1 ^ null2) { return false; } if (head1.last != null || head2.last != null) { return false; } DoubleNode end1 = null; DoubleNode end2 = null; while (head1 != null && head2 != null) { if (head1.value != head2.value) { return false; } end1 = head1; end2 = head2; head1 = head1.next; head2 = head2.next; } if (head1 != null || head2 != null) { return false; } while (end1 != null && end2 != null) { if (end1.value != end2.value) { return false; } end1 = end1.last; end2 = end2.last; } return end1 == null && end2 == null; } public static void main(String[] args) { int len = 50; int value = 100; int testTime = 100000; for (int i = 0; i < testTime; i++) { Node node1 = generateRandomLinkedList(len, value); Node reverse1 = reverseLinkedList(node1); Node back1 = testReverseLinkedList(reverse1); if (!checkLinkedListEqual(node1, back1)) { System.out.println("oops!"); break; } DoubleNode node2 = generateRandomDoubleList(len, value); DoubleNode reverse2 = reverseDoubleList(node2); DoubleNode back2 = testReverseDoubleList(reverse2); if (!checkDoubleListEqual(node2, back2)) { System.out.println("oops!"); break; } } System.out.println("finish!"); } }
好比給定一個鏈表頭結點,刪除該節點上值爲3的節點,那麼可能頭結點就是3,存在刪頭部的狀況,這裏最終返回應該是刪除全部值爲3的節點以後的新的頭部數組
package class02; 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) { while (head != null) { if (head.value != num) { break; } head = head.next; } // head來到 第一個不須要刪的位置 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; } }
Tips: Java中也有可能產生內存泄漏,與CPP不一樣,CPP的內存泄漏有多是咱們開闢了內存空間忘記釋放。而Java的內存泄漏大多是程序中的變量的生存週期引發的,若是該程序是一個相似定時任務的7*24小時不間斷運行,那麼申請的變量(數據結構)就有可能不會被及時釋放。若是不注意往裏面添加一些沒必要要的變量,這些變量就是內存泄漏數據結構
棧:數據先進後出,猶如彈夾dom
隊列: 數據先進先出,排隊函數
雙向鏈表實現oop
package class02; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; public class Code03_DoubleEndsQueueToStackAndQueue { 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 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!"); } }
數組實現,對於棧特別簡單,對於隊列,以下this
package class02; public class Code04_RingArray { public static class MyQueue { // 數組結構 private int[] arr; // 往當前隊列添加數的下標位置 private int pushi; // 當前隊列須要出隊列的位置 private int polli; // 當前隊列使用的空間大小 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,返回下一個位置,該實現能夠實現環形的ringbuffer private int nextIndex(int i) { return i < limit - 1 ? i + 1 : 0; } } }
1、實現一個特殊的棧,在基本功能的基礎上,再實現返回棧中最小元素的功能更設計
一、pop、push、getMin操做的時間複雜度都是O(1)
二、設計的棧類型可使用現成的棧結構
思路:準備兩個棧,一個data棧,一個min棧。數據壓data棧,min棧對比min棧頂元素,誰小加誰。這樣的話data棧和min棧是同步上升的,元素個數同樣多,且min棧的棧頂,是data棧全部元素中最小的那個。數據彈出data棧,咱們同步彈出min棧,保證個數相等,切min棧彈出的就是最小值
package class02; import java.util.Stack; 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."); } // 彈出操做,同步彈出,保證大小一致,只返回給用戶data棧中的內容便可 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()); } }
2、如何用棧結構實現隊列結構,如何用隊列結構實現棧結構
這兩種結構的應用實在太多,刷題時會大量見到
/** * 兩個棧實現隊列 **/ package class02; 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 class02; 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]中的最大值,怎麼用遞歸方法實現
一、 將[L...R]範圍分紅左右兩半。左[L...Mid],右[Mid+1...R]
二、 左部分求最大值,右部分求最大值
三、[L...R]範圍上的最大值,就是max{左部分最大值,右部分最大值}
2步驟是個遞歸過程,當範圍上只有一個數,就能夠不用再遞歸了
package class02; 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) { if (L == R) { // arr[L..R]範圍上只有一個數,直接返回,base case return arr[L]; } int mid = L + ((R - L) >> 1); // 中點 // 左部分最大值 int leftMax = process(arr, L, mid); // 右部分最大值 int rightMax = process(arr, mid + 1, R); return Math.max(leftMax, rightMax); } }
遞歸在系統中是怎麼實現的?遞歸實際上利用的是系統棧來實現的。保存當前調用現場,去執行子問題,子問題的返回做爲現場的須要的參數填充,最終構建還原棧頂的現場,返回。因此遞歸行爲不是玄學,任何遞歸均可以改成非遞歸實現,咱們本身壓棧用迭代等實現就行
對於知足
T(N) = aT(N/b) + O(N^d)
其中: a,b,d爲常數
公式表示,子問題的規模是一致的,該子問題調用了a次,N/b表明子問題的規模,O(N^d)爲除去遞歸調用剩餘的時間複雜度。
好比上述問題的遞歸,[L...R]上有N個數,第一個子問題的規模是N/2,第二個子問題的規模也是N/2。子問題調用了2次。額爲複雜度爲O(1),那麼公式爲:
T(N) = 2T(N/2) + O(N^0)
結論:若是咱們的遞歸知足這種公式,那麼該遞歸的時間複雜度(Master公式)爲
logb^a > d => O(N ^ (logb^a)) logb^a < d => O(N^d) logb^a == d => O(N^d * logN)
那麼上述問題的a=2, b=2,d=0,知足第一條,遞歸時間複雜度爲:O(N)
Hash表的增刪改查,在使用的時候,一概認爲時間複雜度是O(1)的
在Java中,int double float基礎類型,按值傳遞; Integer, Double, Float按引用傳遞的,比較包裝類型的值是否相等,使用equals方法。
注意:在Java底層,包裝類若是範圍比較小,底層仍然採用值傳遞,好比Integer若是範圍在-128~127之間,是按值傳遞的
可是在Hash表中,即便是包裝類型的key,咱們也一概按值傳遞,例如Hash<Integer,String>若是咱們put相同的key的值,那麼不會產生兩個值相等的key而是覆蓋操做。可是Hash表並非一直是按值傳遞的,只是針對包裝類型,若是是咱們自定義的引用類型,那麼仍然按引用傳遞
順序表比哈希表功能多,可是順序表的不少操做時間複雜度是O(logN)
有序表的底層能夠有不少結構實現,好比AVL樹,SB樹,紅黑樹,跳錶。其中AVL,SB,紅黑都是具有各自平衡性的搜索二叉樹
因爲平衡二叉樹每時每刻都會維持自身的平衡,因此操做爲O(logN)。暫時理解,後面會單獨整理
因爲知足去重排序功能來維持底層樹的平衡,因此若是是基礎類型和包裝類型的key直接按值來作比較,可是若是咱們的key是本身定義的類型,那麼咱們要本身制定比較規則(比較器),用來讓底層的樹保持比較後的平衡
package class02; 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) { // UnSortedMap HashMap<Integer, String> map = new HashMap<>(); map.put(1000000, "我是1000000"); map.put(2, "我是2"); map.put(3, "我是3"); map.put(4, "我是4"); map.put(5, "我是5"); map.put(6, "我是6"); map.put(1000000, "我是1000001"); System.out.println(map.containsKey(1)); System.out.println(map.containsKey(10)); System.out.println(map.get(4)); System.out.println(map.get(10)); 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.equals(d)); Integer e = 127; // - 128 ~ 127 Integer f = 127; System.out.println(e == f); 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(3, "我是3"); treeMap.put(4, "我是4"); treeMap.put(8, "我是8"); treeMap.put(5, "我是5"); treeMap.put(7, "我是7"); treeMap.put(1, "我是1"); treeMap.put(2, "我是2"); System.out.println(treeMap.containsKey(1)); System.out.println(treeMap.containsKey(10)); System.out.println(treeMap.get(4)); System.out.println(treeMap.get(10)); 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()); // <= 4 System.out.println(treeMap.floorKey(4)); // >= 4 System.out.println(treeMap.ceilingKey(4)); // O(logN) } }