既然语言都有这些结构和api,为什么还需要手撸练习? 1)算法问题无关语言 2)语言提供的api是有限的,当有新的功能是api不提供的,就需要改写 3)任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的 4)面试考

链表

单向链表

  1. 节点结构(可以实现成范型)

    1. public class Node {
    2. public int value; I
    3. public Node next;
    4. public Node(int data) {
    5. value = data;
    6. }
    7. }
  2. 内存结构

    1. 要么是紧密结构:像数组一样,可以迅速算出偏移量,寻址之后把需要的元素拿出来
    2. 要么是跳转结构:一个数据域也可以往外指七八条节点(类似于图一样的结构),但是单链表除了自己的数据域外,还有一个往外跳转的指针,记录下一个节点的内存地址,通过这个地址往外跳会跳到下一个内存区域上…… ===>调指针不能调乱

image.png

  1. 单链表的链接问题,不能连断掉,也不能连错了
  2. 链表出现主要看coding能力,出现难的不多,但是也会有

双向链表

  1. 节点结构

    1. public class DoubleNode {
    2. public int value;
    3. public DoubleNode last; // 向前指的指针
    4. public DoubleNode next; // 向后指的指针
    5. public DoubleNode(int data) {
    6. value = data;
    7. }
    8. }

    单向链表和双向链表最简单的练习

  2. 链表相关的问题几乎都是coding问题(主要是用的技巧要记住、牢记)

  3. 这里就是熟悉结构。
  4. 链表还有哪些常见面试题,后续有专门一节来系统学习。
  5. null是系统中很干净的一个空区域,所有的null都指向那里
  6. 链表无小事,感觉题目很简单,但是一写就错(链表断了、链接错了)===>要多练、多写、多做题目

    反转

  • 单链表
    • 递归
    • 循环

      用pre和next两个变量进行reverse

  1. package class02;
  2. import java.util.ArrayList;
  3. public class Code01_ReverseList {
  4. public static class Node {
  5. public int value;
  6. public Node next;
  7. public Node(int data) {
  8. value = data;
  9. }
  10. }
  11. public static Node reverseLinkedList(Node head) {
  12. Node pre = null;
  13. Node next = null;
  14. while (head != null) {
  15. next = head.next;
  16. head.next = pre;
  17. pre = head;
  18. head = next;
  19. }
  20. return pre;
  21. }
  22. // 对数器测试是用的容器(有额外空间)
  23. // 使用动态数组,对链表遍历,将每个数组元素放到容器中去,然后再从N-1进行遍历,进行重连,最后返回N-1的节点即可
  24. // 面试时不能用这种方法,可以用这种方式做对数器
  25. public static Node testReverseLinkedList(Node head) {
  26. if (head == null) {
  27. return null;
  28. }
  29. ArrayList<Node> list = new ArrayList<>();
  30. while (head != null) {
  31. list.add(head);
  32. head = head.next;
  33. }
  34. list.get(0).next = null;
  35. int N = list.size();
  36. for (int i = 1; i < N; i++) {
  37. list.get(i).next = list.get(i - 1);
  38. }
  39. return list.get(N - 1);
  40. }
  41. public static Node generateRandomLinkedList(int len, int value) {
  42. int size = (int) (Math.random() * (len + 1));
  43. if (size == 0) {
  44. return null;
  45. }
  46. size--;
  47. Node head = new Node((int) (Math.random() * (value + 1)));
  48. Node pre = head;
  49. while (size != 0) {
  50. Node cur = new Node((int) (Math.random() * (value + 1)));
  51. pre.next = cur;
  52. pre = cur;
  53. size--;
  54. }
  55. return head;
  56. }
  57. // 要求无环,有环别用这个函数
  58. public static boolean checkLinkedListEqual(Node head1, Node head2) {
  59. while (head1 != null && head2 != null) {
  60. if (head1.value != head2.value) {
  61. return false;
  62. }
  63. head1 = head1.next;
  64. head2 = head2.next;
  65. }
  66. return head1 == null && head2 == null;
  67. }
  68. public static void main(String[] args) {
  69. int len = 50;
  70. int value = 100;
  71. int testTime = 100000;
  72. for (int i = 0; i < testTime; i++) {
  73. Node node1 = generateRandomLinkedList(len, value);
  74. Node reverse1 = reverseLinkedList(node1);
  75. Node back1 = testReverseLinkedList(reverse1);
  76. if (!checkLinkedListEqual(node1, back1)) {
  77. System.out.println("oops!");
  78. break;
  79. }
  80. }
  81. System.out.println("finish!");
  82. }
  83. }
  • 双链表

    用pre和next两个变量进行reverse,与单链表类似,但是要做的工作更多一些,更复杂一些

  1. package class02;
  2. import java.util.ArrayList;
  3. public class Code01_ReverseList {
  4. public static class DoubleNode {
  5. public int value;
  6. public DoubleNode last;
  7. public DoubleNode next;
  8. public DoubleNode(int data) {
  9. value = data;
  10. }
  11. }
  12. public static DoubleNode reverseDoubleList(DoubleNode head) {
  13. DoubleNode pre = null;
  14. DoubleNode next = null;
  15. while (head != null) {
  16. next = head.next;
  17. head.next = pre;
  18. head.last = next;
  19. pre = head;
  20. head = next;
  21. }
  22. return pre;
  23. }
  24. public static DoubleNode testReverseDoubleList(DoubleNode head) {
  25. if (head == null) {
  26. return null;
  27. }
  28. ArrayList<DoubleNode> list = new ArrayList<>();
  29. while (head != null) {
  30. list.add(head);
  31. head = head.next;
  32. }
  33. list.get(0).next = null;
  34. DoubleNode pre = list.get(0);
  35. int N = list.size();
  36. for (int i = 1; i < N; i++) {
  37. DoubleNode cur = list.get(i);
  38. cur.last = null;
  39. cur.next = pre;
  40. pre.last = cur;
  41. pre = cur;
  42. }
  43. return list.get(N - 1);
  44. }
  45. public static DoubleNode generateRandomDoubleList(int len, int value) {
  46. int size = (int) (Math.random() * (len + 1));
  47. if (size == 0) {
  48. return null;
  49. }
  50. size--;
  51. DoubleNode head = new DoubleNode((int) (Math.random() * (value + 1)));
  52. DoubleNode pre = head;
  53. while (size != 0) {
  54. DoubleNode cur = new DoubleNode((int) (Math.random() * (value + 1)));
  55. pre.next = cur;
  56. cur.last = pre;
  57. pre = cur;
  58. size--;
  59. }
  60. return head;
  61. }
  62. // 要求无环,有环别用这个函数
  63. public static boolean checkDoubleListEqual(DoubleNode head1, DoubleNode head2) {
  64. boolean null1 = head1 == null;
  65. boolean null2 = head2 == null;
  66. if (null1 && null2) {
  67. return true;
  68. }
  69. if (null1 ^ null2) {
  70. return false;
  71. }
  72. if (head1.last != null || head2.last != null) {
  73. return false;
  74. }
  75. DoubleNode end1 = null;
  76. DoubleNode end2 = null;
  77. while (head1 != null && head2 != null) {
  78. if (head1.value != head2.value) {
  79. return false;
  80. }
  81. end1 = head1;
  82. end2 = head2;
  83. head1 = head1.next;
  84. head2 = head2.next;
  85. }
  86. if (head1 != null || head2 != null) {
  87. return false;
  88. }
  89. while (end1 != null && end2 != null) {
  90. if (end1.value != end2.value) {
  91. return false;
  92. }
  93. end1 = end1.last;
  94. end2 = end2.last;
  95. }
  96. return end1 == null && end2 == null;
  97. }
  98. public static void main(String[] args) {
  99. int len = 50;
  100. int value = 100;
  101. int testTime = 100000;
  102. for (int i = 0; i < testTime; i++) {
  103. DoubleNode node2 = generateRandomDoubleList(len, value);
  104. DoubleNode reverse2 = reverseDoubleList(node2);
  105. DoubleNode back2 = testReverseDoubleList(reverse2);
  106. if (!checkDoubleListEqual(node2, back2)) {
  107. System.out.println("oops!");
  108. break;
  109. }
  110. }
  111. System.out.println("finish!");
  112. }
  113. }

把给定值都删除(把所有与给定值相等的节点删除)

  • 单链表
    • 一定要返回头节点,因为不能保证头节点不被删掉
    • 也可能会删掉多个节点
    • 删除的时候不是真的移除,而是碰到要删除的节点直接跳过,而没有碰到的话,就将节点依次往链表上挂
    • 对数器可以通过容器来实现,在数组里把所有为要删除的值都剃掉,在数组中重新连好然后返回即可
    • 对数器也可以用jdk中的linkedlist来实现
    • 直接用linkedlist可以但是练题还是要自己实现,因为系统提供的结构只是常用的功能封装进去了,工作中或者面试中可能会有个性化的需求 ```java package class02;

public class Code02_DeleteGivenValue {

  1. public static class Node {
  2. public int value;
  3. public Node next;
  4. public Node(int data) {
  5. this.value = data;
  6. }
  7. }
  8. // 1)head == null
  9. // 2)head != null
  10. public static Node removeValue(Node head, int num) {
  11. // head来到第一个不需要删除的位置
  12. // 链表的边界条件处理
  13. while (head != null) {
  14. if (head.value != num) {
  15. break;
  16. }
  17. head = head.next;
  18. }
  19. // head来到 第一个不需要删的位置
  20. Node pre = head;
  21. Node cur = head;
  22. // 1)head == null ===> 整个链表都没了
  23. // 2)head != null
  24. while (cur != null) {
  25. // 是要删除的值就跳过
  26. // 不是要删除的值就往链表上挂===>前一节点的next指向该节点
  27. if (cur.value == num) {
  28. pre.next = cur.next;
  29. } else {
  30. pre = cur;
  31. }
  32. cur = cur.next;
  33. }
  34. return head;
  35. }

}

  1. - 双链表
  2. ```java
  3. 与单链表类似……

队列和栈

  1. 只是一种逻辑上的数据结构
  2. 逻辑概念
    1. 栈:数据先进后出,犹如弹匣
    2. 队列:数据先进先出,好似排队
  3. 用什么来实现数据和栈?===>数组和链表都能实现队列和栈

队列

双链表实现(也可以实现双端队列)

  1. 加入队列,一般从后面加
  2. 弹出队列,一般从前面弹
  3. 精髓为头尾指针的指向(移动)===>尾指针可以往回退,因为有向前指的last指针
  1. package class02;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. import java.util.Stack;
  5. public class Code03_DoubleEndsQueueToStackAndQueue {
  6. public static class Node<T> {
  7. public T value;
  8. public Node<T> last;
  9. public Node<T> next;
  10. public Node(T data) {
  11. value = data;
  12. }
  13. }
  14. public static class DoubleEndsQueue<T> {
  15. public Node<T> head;
  16. public Node<T> tail;
  17. public void addFromHead(T value) {
  18. Node<T> cur = new Node<T>(value);
  19. if (head == null) {
  20. head = cur;
  21. tail = cur;
  22. } else {
  23. cur.next = head;
  24. head.last = cur;
  25. head = cur;
  26. }
  27. }
  28. public void addFromBottom(T value) {
  29. Node<T> cur = new Node<T>(value);
  30. if (head == null) {
  31. head = cur;
  32. tail = cur;
  33. } else {
  34. cur.last = tail;
  35. tail.next = cur;
  36. tail = cur;
  37. }
  38. }
  39. public T popFromHead() {
  40. if (head == null) {
  41. return null;
  42. }
  43. Node<T> cur = head;
  44. if (head == tail) {
  45. head = null;
  46. tail = null;
  47. } else {
  48. head = head.next;
  49. cur.next = null;
  50. head.last = null;
  51. }
  52. return cur.value;
  53. }
  54. public T popFromBottom() {
  55. if (head == null) {
  56. return null;
  57. }
  58. Node<T> cur = tail;
  59. if (head == tail) {
  60. head = null;
  61. tail = null;
  62. } else {
  63. tail = tail.last;
  64. tail.next = null;
  65. cur.last = null;
  66. }
  67. return cur.value;
  68. }
  69. public boolean isEmpty() {
  70. return head == null;
  71. }
  72. }
  73. public static class MyQueue<T> {
  74. private DoubleEndsQueue<T> queue;
  75. public MyQueue() {
  76. queue = new DoubleEndsQueue<T>();
  77. }
  78. public void push(T value) {
  79. queue.addFromHead(value);
  80. }
  81. public T poll() {
  82. return queue.popFromBottom();
  83. }
  84. public boolean isEmpty() {
  85. return queue.isEmpty();
  86. }
  87. }
  88. public static boolean isEqual(Integer o1, Integer o2) {
  89. if (o1 == null && o2 != null) {
  90. return false;
  91. }
  92. if (o1 != null && o2 == null) {
  93. return false;
  94. }
  95. if (o1 == null && o2 == null) {
  96. return true;
  97. }
  98. return o1.equals(o2);
  99. }
  100. public static void main(String[] args) {
  101. int oneTestDataNum = 100;
  102. int value = 10000;
  103. int testTimes = 100000;
  104. for (int i = 0; i < testTimes; i++) {
  105. MyQueue<Integer> myQueue = new MyQueue<>();
  106. Queue<Integer> queue = new LinkedList<>();
  107. for (int j = 0; j < oneTestDataNum; j++) {
  108. int numq = (int) (Math.random() * value);
  109. if (queue.isEmpty()) {
  110. myQueue.push(numq);
  111. queue.offer(numq);
  112. } else {
  113. if (Math.random() < 0.5) {
  114. myQueue.push(numq);
  115. queue.offer(numq);
  116. } else {
  117. if (!isEqual(myQueue.poll(), queue.poll())) {
  118. System.out.println("oops!");
  119. }
  120. }
  121. }
  122. }
  123. }
  124. System.out.println("finish!");
  125. }
  126. }

数组实现(常见面试题,容易错)

  1. 谷歌常考笔试题:循环数组实现一个队列(ring array)
    1. 用户从end往后加,从begin开始往后拿
    2. end追赶begin,begin追赶end

image.png

  1. end与begin之间留了一个空间===>该空间就为空闲空间(追赶的感觉)
  2. 虽然是追赶的感觉,但是这种实现方式辣鸡,脑子搅到追赶的想法中去就完蛋了,面试或笔试中用追赶写就完蛋了(写不出来或者时间长)
  3. 正确思路:不去扯begin和end,而是加一个size,作为容量大小标记;不看追没追上,size只要没到最大容量就能加,加到end位置上;size只要不为0就能拿值,拿begin位置上的数===>将begin和end彻底解耦掉了
  1. package class02;
  2. public class Code04_RingArray {
  3. public static class MyQueue {
  4. private int[] arr;
  5. private int pushi; // 进来的数放哪
  6. private int polli; // 弹出的数从哪拿
  7. private int size; // 总容量
  8. private final int limit;
  9. public MyQueue(int limit) {
  10. arr = new int[limit];
  11. pushi = 0;
  12. polli = 0;
  13. size = 0;
  14. this.limit = limit; // 把limit记住
  15. }
  16. public void push(int value) {
  17. if (size == limit) {
  18. throw new RuntimeException("队列满了,不能再加了");
  19. }
  20. size++;
  21. arr[pushi] = value;
  22. // 不要管追没追上,只要size管着,只要size没到limit必然可以加东西
  23. pushi = nextIndex(pushi);
  24. }
  25. public int pop() {
  26. if (size == 0) {
  27. // 运行时异常
  28. throw new RuntimeException("队列空了,不能再拿了");
  29. }
  30. size--;
  31. int ans = arr[polli];
  32. // 都用nextIndex,因为都是向上移动===>队列
  33. polli = nextIndex(polli);
  34. return ans;
  35. }
  36. public boolean isEmpty() {
  37. return size == 0;
  38. }
  39. // 如果现在的下标是i,返回下一个位置
  40. // 没到顶加1,到了顶就回到0===>循环
  41. private int nextIndex(int i) {
  42. return i < limit - 1 ? i + 1 : 0;
  43. }
  44. }
  45. }

双链表实现

  1. 压栈,一般从头节点加
  2. 弹栈,一般也从头节点弹
  3. 精髓为头尾指针的指向(移动)===>头指针可以向后移,因为有向后指的next指针
  1. package class02;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. import java.util.Stack;
  5. public class Code03_DoubleEndsQueueToStackAndQueue {
  6. public static class Node<T> {
  7. public T value;
  8. public Node<T> last;
  9. public Node<T> next;
  10. public Node(T data) {
  11. value = data;
  12. }
  13. }
  14. public static class MyStack<T> {
  15. private DoubleEndsQueue<T> queue;
  16. public MyStack() {
  17. queue = new DoubleEndsQueue<T>();
  18. }
  19. public void push(T value) {
  20. queue.addFromHead(value);
  21. }
  22. public T pop() {
  23. return queue.popFromHead();
  24. }
  25. public boolean isEmpty() {
  26. return queue.isEmpty();
  27. }
  28. }
  29. public static boolean isEqual(Integer o1, Integer o2) {
  30. if (o1 == null && o2 != null) {
  31. return false;
  32. }
  33. if (o1 != null && o2 == null) {
  34. return false;
  35. }
  36. if (o1 == null && o2 == null) {
  37. return true;
  38. }
  39. return o1.equals(o2);
  40. }
  41. public static void main(String[] args) {
  42. int oneTestDataNum = 100;
  43. int value = 10000;
  44. int testTimes = 100000;
  45. for (int i = 0; i < testTimes; i++) {
  46. MyStack<Integer> myStack = new MyStack<>();
  47. Stack<Integer> stack = new Stack<>();
  48. for (int j = 0; j < oneTestDataNum; j++) {
  49. int nums = (int) (Math.random() * value);
  50. if (stack.isEmpty()) {
  51. myStack.push(nums);
  52. stack.push(nums);
  53. } else {
  54. if (Math.random() < 0.5) {
  55. myStack.push(nums);
  56. stack.push(nums);
  57. } else {
  58. if (!isEqual(myStack.pop(), stack.pop())) {
  59. System.out.println("oops!");
  60. }
  61. }
  62. }
  63. }
  64. }
  65. System.out.println("finish!");
  66. }
  67. }

数组实现(常见面试题,容易错)

  1. index表示新进来的数放什么位置(index++)
  2. 弹出index-1那个数同时index—,因为原来位置放的值已经不用了,下一个要放的位置向前一个(之后放入的数会将之前的值盖掉,不需要将之前的值清理掉)
  3. index下标越界,就给用户报错,告诉用户栈满了,不能再加了

栈和队列的常见面试题

  1. 怎么用数组实现不超过固定大小的队列和栈?
    1. 栈:正常使用、用一个指针,加的时候往上涨,弹的时候往下降===>这是两个不同的方向,因此不需要使用循环数组
    2. 队列:环形数组、循环数组
  2. 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能。

1) pop、push、getMin操作的时间复杂度都是0(1)。===>不能遍历栈,使用双栈结构 2)设计的栈类型可以使用现成的栈结构。.

递归

master公式

哈希表

  1. 哈希表中基础类型和自定义类型的区别
  2. 哈希表中非基础类型占用的空间很少,只存放内存地址(引用传递)(一般为8byte,jvm中可以压缩内存地址4byte)
  3. String及基础类型及其包装类型,有多大hash表就开多大内存装这个数据(值传递)
  4. Integer等包装类型也按值传递(比的是值,不是地址)(其他地方按引用来用,hash表里按值来用)
  5. HashSet和HashMap除了没有Value其他都一样
  6. 复杂度:O(1)

有序表

  1. TreeMap有序表:接口名
  2. 红黑树、avl树、sb树(sides balance)、跳表可以实现有序表
  3. 增删改查:O(logN)
  4. ❓java中原生的(基本类型的意思?还是原生有序表)有序表按值传递,???有序表不会存同样的值的东西,按大小组织
  5. 有序表比哈希表多的内容:最小的值,最大的值(firstKey、lastKey)
  6. 小于等于4离4最近的Key,大于等于4离4最近的Key
  7. 有序表比hash表强大,但是复杂度高O(logN)===>有序表是通过树实现的,而哈希表是通过散列表实现的
  8. 对于基础类型,有序表知道怎么排序,原生类型有序表天生知道怎么排序(String类型、整型)知道key怎么排序
  9. 对于非基础类型,必须自己写出比较规则,否则有序表会出错!(要提供比较器,不提供会报错)===>可以实现Comparable接口,也可以自己定义Comparator
  10. 一定要实现比较器,而不是equal方法,那个一样无法得到大小
  11. 有序表中,非基本类型(非内置结构)的空间占用很小,记内存地址