找出数组中出现奇数次的数

使用异或来解决,非常巧妙。在二进制中,负数等于它的反码加一。

  1. public class EvenOddTimes {
  2. /**
  3. * 数组中只有一种数字,出现奇数次,找出这个数字
  4. */
  5. public static void printOddTimesNum1(int[] arr) {
  6. int xor = 0;
  7. for (int i = 0; i < arr.length; i++) {
  8. xor ^= arr[i]; // 所有数字异或结果
  9. }
  10. System.out.println("数组中唯一出现奇数次的值为:" + xor);
  11. }
  12. public static void printOddTimesNum2(int[] arr) {
  13. int xor = 0;
  14. for (int i = 0; i < arr.length; i++) {
  15. xor ^= arr[i]; // 所有数字异或结果
  16. }
  17. int rightOne = xor & (~xor + 1);
  18. int onlyOne = 0;
  19. for (int i = 0; i < arr.length; i++) {
  20. if ((arr[i] & rightOne) != 0) {
  21. onlyOne ^= arr[i];
  22. }
  23. }
  24. System.out.println("数组中出现奇数次的两个数分别是:" +onlyOne + "," + (xor ^ onlyOne));
  25. }
  26. public static void main(String[] args) {
  27. int a = 5;
  28. int b = 7;
  29. a = a ^ b; // 使用异或来交换两个数的值
  30. b = a ^ b;
  31. a = a ^ b;
  32. System.out.println("交换过后:" + a);
  33. System.out.println("交换过后:" + b);
  34. int[] arr1 = {1, 1, 1, 1, 2, 2, 3};
  35. printOddTimesNum1(arr1);
  36. int[] arr2 = {1, 1, 1, 1, 2, 2, 3, 5, 5, 5};
  37. printOddTimesNum2(arr2);
  38. }
  39. }

检验链表是否是回文结构

三种方法,用栈或者是修改链表再复原。

  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. // 检验一个链表是否是回文链表
  9. // 方法1:使用栈
  10. public static boolean isPalindrome1(Node head) {
  11. Stack<Node> stack = new Stack<Node>();
  12. Node cur = head;
  13. while (cur != null) {
  14. stack.push(cur); // 全部压入栈中
  15. cur = cur.next;
  16. }
  17. while (head != null) {
  18. if (head.value != stack.pop().value) {
  19. return false;
  20. }
  21. head = head.next;
  22. }
  23. return true;
  24. }
  25. // 方法2:用栈,只压入一半数据
  26. public static boolean isPalindrome2(Node head) {
  27. if (head == null || head.next == null) {
  28. return true;
  29. }
  30. Node cur = head;
  31. Node right = head.next;
  32. // 异步指针,不论链表总数是奇数或偶数,right到达的位置很巧妙
  33. while (cur.next != null && cur.next.next != null) {
  34. cur = cur.next.next;
  35. right = right.next; // right到达的位置很巧妙
  36. }
  37. Stack<Node> stack = new Stack<>();
  38. while (right != null) {
  39. stack.push(right); // 把右半边的数据都压入栈中
  40. right = right.next;
  41. }
  42. while (!stack.isEmpty()) {
  43. if (head.value != stack.pop().value) {
  44. return false;
  45. }
  46. head = head.next;
  47. }
  48. return true;
  49. }
  50. // 方法3:快慢指针,反向修改链表,逐一比较,最后再把链表恢复
  51. public static boolean isPalindrome3(Node head) {
  52. if (head == null || head.next == null) {
  53. return true;
  54. }
  55. Node n1 = head;
  56. Node n2 = head;
  57. while (n2.next != null && n2.next.next != null) {
  58. n1 = n1.next; // 偶数时,n1和n2到中间两个结点
  59. n2 = n2.next.next; // 奇数时,n1到中间结点,n2到末尾结点
  60. }
  61. n2 = n1.next; // n2 -> 重新赋值到有半边开头
  62. n1.next = null; // 断开链表,mid.next -> null
  63. Node n3 = null;
  64. while (n2 != null) { // 逆转右半边链表
  65. n3 = n2.next; // n3 -> save next node
  66. n2.next = n1; // next of right node convert
  67. n1 = n2; // n1 move
  68. n2 = n3; // n2 move
  69. }
  70. n3 = n1; // n3 -> 保存原始链表的末尾结点
  71. n2 = head; // n2 -> left first node
  72. boolean res = true;
  73. while (n1 != null && n2 != null) { // 从两边开始对比
  74. if (n1.value != n2.value) {
  75. res = false;
  76. break;
  77. }
  78. n1 = n1.next; // left to mid
  79. n2 = n2.next; // right to mid
  80. }
  81. // 恢复链表
  82. n1 = n3.next;
  83. n3.next = null;
  84. while (n1 != null) { // recover list
  85. n2 = n1.next;
  86. n1.next = n3;
  87. n3 = n1;
  88. n1 = n2;
  89. }
  90. return res;
  91. }

有环链表的相交结点

判断两个链表是否有环。
判断两个链表是否相交。

  1. package problems;
  2. public class ListWithCircle {
  3. public static class Node {
  4. public int value;
  5. public Node next;
  6. public Node(int data) {
  7. this.value = data;
  8. }
  9. }
  10. public static Node getCircleNode(Node head1, Node head2) {
  11. if (head1 == null || head2 == null) {
  12. return null;
  13. }
  14. // 先判断两个链表是否有环
  15. Node circle1 = ringList(head1);
  16. Node circle2 = ringList(head2);
  17. // 都是无环链表,如果相交,返回相交结点,否则返回null
  18. if (circle1 == null && circle2 == null) {
  19. return joinPoint(head1, head2);
  20. }
  21. // 都是有环链表
  22. // 三种情况:
  23. if (circle1 != null && circle2 != null) {
  24. return boothRing(head1, circle1, head2, circle2);
  25. }
  26. return null;
  27. }
  28. // 1. 判断链表是否有环
  29. // 有环返回第一个入环结点,否则返回null
  30. public static Node ringList(Node head) {
  31. if (head == null || head.next == null || head.next.next == null) {
  32. return null;
  33. }
  34. Node slow = head.next;
  35. Node fast = head.next.next;
  36. // 快慢指针,如果有环,它们会在环中某个结点相遇,否则返回null
  37. while (slow != fast) {
  38. if (fast.next == null || fast.next.next == null) {
  39. return null;
  40. }
  41. slow = slow.next;
  42. fast = fast.next.next;
  43. }
  44. // 有环,两个指针相遇,一个指针回到开头,两个指针一起走,刚好走到入环结点
  45. slow = head;
  46. while (slow != fast) {
  47. slow = slow.next;
  48. fast = fast.next;
  49. }
  50. return slow;
  51. }
  52. // 2. 判断两个无环链表是否相交
  53. // 如果相交,返回第一个相交结点
  54. public static Node joinPoint(Node head1, Node head2) {
  55. if (head1 == null || head2 == null) {
  56. return null;
  57. }
  58. Node cur1 = head1;
  59. Node cur2 = head2;
  60. int n = 0;
  61. while (cur1.next != null) {
  62. n++;
  63. cur1 = cur1.next;
  64. }
  65. while (cur2.next != null) {
  66. n--;
  67. cur2 = cur2.next;
  68. }
  69. // 如果两个末尾结点不相同,则这两个单链表不相交
  70. if (cur1 != cur2) {
  71. return null;
  72. }
  73. cur1 = head1;
  74. cur2 = head2;
  75. // 长的先走 n 步
  76. if (n > 0) {
  77. cur1 = head1; // 从末尾重定位到开头
  78. while (n != 0) { // 先走n步
  79. n--;
  80. cur1 = cur1.next;
  81. }
  82. } else {
  83. // 不能放到这里,如果走if语句,就不会走到这一步,那么cur2就没有从末尾重定位到开头。。。
  84. // cur2 = head2;
  85. n = Math.abs(n);
  86. while (n != 0) {
  87. n--;
  88. cur2 = cur2.next;
  89. }
  90. }
  91. while (cur1 != cur2) {
  92. cur1 = cur1.next;
  93. cur2 = cur2.next;
  94. }
  95. return cur1;
  96. }
  97. // 3. 寻找两个有环链表的相交结点
  98. public static Node boothRing(Node head1, Node loop1, Node head2, Node loop2) {
  99. Node cur1 = null;
  100. Node cur2 = null;
  101. if (loop1 == loop2) {
  102. cur1 = head1;
  103. cur2 = head2;
  104. int n = 0;
  105. while (cur1 != loop1) {
  106. n++;
  107. cur1 = cur1.next;
  108. }
  109. while (cur2 != loop2) {
  110. n--;
  111. cur2 = cur2.next;
  112. }
  113. cur1 = n > 0 ? head1 : head2;
  114. cur2 = cur1 == head1 ? head2 : head1;
  115. n = Math.abs(n);
  116. while (n != 0) {
  117. n--;
  118. cur1 = cur1.next;
  119. }
  120. while (cur1 != cur2) {
  121. cur1 = cur1.next;
  122. cur2 = cur2.next;
  123. }
  124. return cur1;
  125. } else {
  126. cur1 = loop1.next;
  127. while (cur1 != loop1) {
  128. if (cur1 == loop2) {
  129. return loop1;
  130. }
  131. cur1 = cur1.next;
  132. }
  133. return null;
  134. }
  135. }
  136. public static void main(String[] args) {
  137. // 1->2->3->4->5->6->7->null
  138. Node head1 = new Node(1);
  139. head1.next = new Node(2);
  140. head1.next.next = new Node(3);
  141. head1.next.next.next = new Node(4);
  142. head1.next.next.next.next = new Node(5);
  143. head1.next.next.next.next.next = new Node(6);
  144. head1.next.next.next.next.next.next = new Node(7);
  145. // 0->9->8->6->7->null
  146. Node head2 = new Node(0);
  147. head2.next = new Node(9);
  148. head2.next.next = new Node(8);
  149. head2.next.next.next = head1.next.next.next.next.next; // 8->6
  150. System.out.println(getCircleNode(head1, head2).value);
  151. // 1->2->3->4->5->6->7->4...
  152. head1 = new Node(1);
  153. head1.next = new Node(2);
  154. head1.next.next = new Node(3);
  155. head1.next.next.next = new Node(4);
  156. head1.next.next.next.next = new Node(5);
  157. head1.next.next.next.next.next = new Node(6);
  158. head1.next.next.next.next.next.next = new Node(7);
  159. head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
  160. // 0->9->8->2...
  161. head2 = new Node(0);
  162. head2.next = new Node(9);
  163. head2.next.next = new Node(8);
  164. head2.next.next.next = head1.next; // 8->2
  165. System.out.println(getCircleNode(head1, head2).value);
  166. // 0->9->8->6->4->5->6..
  167. head2 = new Node(0);
  168. head2.next = new Node(9);
  169. head2.next.next = new Node(8);
  170. head2.next.next.next = head1.next.next.next.next.next; // 8->6
  171. System.out.println(getCircleNode(head1, head2).value);
  172. }
  173. }

遍历二叉树

对二叉树进行遍历,先序、中序、后序和宽度遍历。
非递归版本,用到栈结构和队列结构。

  1. package problems;
  2. import java.util.Deque;
  3. import java.util.LinkedList;
  4. import java.util.Queue;
  5. /**
  6. * 遍历二叉树
  7. * 先序、中序、后序
  8. */
  9. public class TraversalTree {
  10. public static class Node {
  11. private int value;
  12. private Node left;
  13. private Node right;
  14. public Node(int value) {
  15. this.value = value;
  16. }
  17. }
  18. // 层次遍历
  19. // 宽度遍历,每层从左到右顺序遍历
  20. public static void levelOrder(Node cur) {
  21. if (cur == null) {
  22. return;
  23. }
  24. System.out.print("宽度遍历:");
  25. Queue<Node> queue = new LinkedList<>(); // 队列
  26. queue.add(cur);
  27. while (!queue.isEmpty()){
  28. cur = queue.poll(); // 先进先出
  29. System.out.print(cur.value + ", ");
  30. if (cur.left != null) {
  31. queue.add(cur.left);
  32. }
  33. if (cur.right != null) {
  34. queue.add(cur.right);
  35. }
  36. }
  37. }
  38. // 先序遍历
  39. // 用栈结构,先压右,再压左
  40. public static void preOrder(Node cur) {
  41. System.out.print("先序遍历:");
  42. if (cur != null) {
  43. // Java中stack已过时,推荐用Deque
  44. Deque<Node> deque = new LinkedList<>();
  45. deque.push(cur);
  46. while (!deque.isEmpty()) {
  47. cur = deque.pop(); // 弹出栈顶元素并打印
  48. System.out.print(cur.value + ",");
  49. // 先压入右子树进栈,再压入左子树
  50. if (cur.right != null) {
  51. deque.push(cur.right);
  52. }
  53. if (cur.left != null) {
  54. deque.push(cur.left);
  55. }
  56. }
  57. }
  58. }
  59. // 中序遍历
  60. // 用栈结构,先把左子树全部压入栈,为空就弹出栈顶元素,再向右走
  61. public static void inOrder(Node cur) {
  62. System.out.print("中序遍历:");
  63. if (cur != null) {
  64. Deque<Node> deque = new LinkedList<>();
  65. while (!deque.isEmpty() || cur != null) {
  66. if (cur != null) {
  67. deque.push(cur);
  68. cur = cur.left;
  69. } else {
  70. cur = deque.pop();
  71. System.out.print(cur.value + ",");
  72. cur = cur.right;
  73. }
  74. }
  75. }
  76. }
  77. // 后序遍历
  78. // 用两个栈,很巧妙,能看懂,但写不出
  79. public static void postOrder(Node cur) {
  80. System.out.print("后序遍历:");
  81. if (cur != null) {
  82. Deque<Node> deque1 = new LinkedList<>();
  83. Deque<Node> deque2 = new LinkedList<>();
  84. deque1.push(cur);
  85. while (!deque1.isEmpty() ) {
  86. cur = deque1.pop();
  87. deque2.push(cur); // 全部压入栈2,顺序是头 右 左
  88. if (cur.left != null) { // 先压入栈1,又被压入栈2
  89. deque1.push(cur.left);
  90. }
  91. if (cur.right != null) {
  92. deque1.push(cur.right);
  93. }
  94. }
  95. // 栈2,打印结果:左 右 头
  96. while (!deque2.isEmpty()) {
  97. cur = deque2.pop();
  98. System.out.print(cur.value + ",");
  99. }
  100. }
  101. }
  102. public static void postOrder2(Node h) {
  103. System.out.print("后序遍历2: ");
  104. if (h != null) {
  105. Deque<Node> stack = new LinkedList<>();
  106. stack.push(h);
  107. Node c = null;
  108. while (!stack.isEmpty()) {
  109. c = stack.peek();
  110. if (c.left != null && h != c.left && h != c.right) {
  111. stack.push(c.left);
  112. } else if (c.right != null && h != c.right) {
  113. stack.push(c.right);
  114. } else {
  115. System.out.print(stack.pop().value + " ");
  116. h = c;
  117. }
  118. }
  119. }
  120. }
  121. public static void main(String[] args) {
  122. // 创建一个满二叉树
  123. Node head = new Node(1);
  124. head.left = new Node(2);
  125. head.right = new Node(3);
  126. head.left.left = new Node(4);
  127. head.left.right = new Node(5);
  128. head.right.left = new Node(6);
  129. head.right.right = new Node(7);
  130. // 经过先序遍历后,cur会指向最末尾的结点,为什么head的指向不变呢?
  131. // 网上查了一下,说Java是值传递,基本类型传递值,引用类型传递的是引用的拷贝
  132. preOrder(head); // 先序遍历
  133. System.out.println();
  134. inOrder(head);
  135. System.out.println();
  136. postOrder(head);
  137. System.out.println();
  138. postOrder2(head);
  139. System.out.println();
  140. levelOrder(head);
  141. System.out.println();
  142. }
  143. }

TraversalTree.java

2021年12月上传的算法视频课:面试官急了,算法大神左神(左程云)耗时3个月打造100道算法与数据结构大厂高频面试题精讲,不出意外十年后仍是最好的算法刷题教程!
https://www.bilibili.com/video/BV1vi4y1R7g9?p=4&spm_id_from=pageDriver