本文内容基于《Java程序员面试笔试宝典》,何昊、薛鹏、叶向阳著。


1. 链表

1.1 如何实现单链表的增删操作?

1.2 如何从链表中删除重复元素?

1.3 如何找出单链表中的倒数第k个元素?

1.4 如何实现链表的反转?

1.5 如何从尾到头输出单链表?

1.6 如何寻找单链表的中间节点?

1.7 如何检测一个链表是否有环?

1.8 如何在不知道头指针的情况下删除指定节点?

1.9 如何判断两个链表是否相交?

2. 栈与队列

2.1 如何实现栈?

2.2 如何用O(1)的时间复杂度求栈中最小的元素?

2.3 如何实现队列?

2.4 如何用两个栈模拟队列操作?

3. 排序

3.1 如何进行冒泡排序?

3.2 如何进行选择排序?

3.3 如何进行插入排序?

3.4 如何进行希尔排序?

3.5 如何进行快速排序?

3.6 如何进行归并排序?

3.7 如何进行堆排序?

3.8 排序算法总结

4. 位运算

4.1 如何用移位操作实现乘法运算?

4.2 如何判断一个数是否为2的n次方?

4.3 如何求二进制数中1的个数?

5. 数组

5.1 如何寻找数组中的最小值与最大值?

5.2 如何找出数组中第2大的数?

5.3 如何求最大子数组之和?(动态规划)

5.4 如何找出数组中重复元素最多的数?

5.5 如何求数组中两两相加等于k的组合种数?

5.6 如何把一个数组循环右移k位?

5.7 如何找出数组中第k个最小的数?

5.8 如何找到数组中只出现一次的数字?

5.9 如何找出数组中唯一的重复元素?

5.10 如何求数对之差的最大值?(动态规划)

5.11 如何求绝对值最小的数?

5.12 如何求数组中两个元素的最小距离?

5.13 如何求指定数字在数组中第一次出现的位置?

5.14 如何对数组的两个子有序段进行合并?

5.15 如何计算两个有序整型数组的交集?

5.16 如何求解数组中反序对的个数?

5.17 如何求解最小三元组距离?

6. 字符串

6.1 如何实现字符串单词的反转?

6.2 如何判断两个字符串是否由相同的字符组成?

6.3 如何删除字符串中重复的字符?

6.4 如何输出所有不重复字符的全排列?

6.5 如何输出字符串的所有组合?

7. 二叉树

7.1 二叉树的基本概念、实现、遍历

7.2 已知先序遍历和中序遍历,如何求后序遍历?

8. 其他

8.1 如何不使用比较运算就求出两个数哪个大哪个小?


1. 链表

1.1 如何实现单链表的增删操作?

public class MyLinkedList {private class Node {public Object data;public Node next;public Node(Object data) {this.data = data;}@Overridepublic String toString() {return "Node{" +"data=" + data +", next=" + next +'}';}}private Node root;@Overridepublic String toString() {return "MyLinkedList{" +"root=" + root +'}';}public void add(Object data) {if (root == null) {root = new Node(data);} else {Node last = root;while (last.next != null) {last = last.next;}last.next = new Node(data);}}public boolean delete(int index) {if (root != null) {if (index == 0) {root = root.next;return true;}int i = 1;Node temp1 = root;Node temp2 = temp1.next;while (temp2 != null) {if (i == index) {temp1.next = temp2.next;return true;}temp1 = temp2;temp2 = temp2.next;i++;}}return false;}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);myLinkedList.delete(0);System.out.println(myLinkedList);myLinkedList.delete(1);System.out.println(myLinkedList);myLinkedList.delete(1);System.out.println(myLinkedList);}
}

1.2 如何从链表中删除重复元素?

  • 哈希表;
  • 双重遍历,假设外循环当前节点为cur,内循环从cur开始向后遍历,若碰到相同的节点,则删除;
  • 双重遍历,假设外循环当前节点为cur,内循环从root开始向后遍历到cur之前的节点,若碰到相同的节点,则删除,内循环结束。
public class MyLinkedList {//...public void deleteDuplicate() {HashMap<Object, Object> map = new HashMap<>();Node temp = root;Node pre = null;while (temp != null) {if (!map.containsKey(temp.data)) {map.put(temp.data, temp.data);pre = temp;} else {pre.next = temp.next;}temp = temp.next;}}public void deleteDuplicate1() {Node p = root;while (p != null) {Node q = p;while (q.next != null) {if (q.next.data == p.data) {q = q.next.next;} else {q = q.next;}}p = p.next;}}public void deleteDuplicate2() {int i = 0;Node p = root;while (p != null) {int j = 0;Node q = root;Node qPre = null;while (q != null && j < i) {if (q.data == p.data) {if (qPre == null) {//头节点root = root.next;i--;} else {qPre.next = q.next;}break;} else {qPre = q;}q = q.next;j++;}i++;p = p.next;}}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);//myLinkedList.deleteDuplicate();//myLinkedList.deleteDuplicate1();myLinkedList.deleteDuplicate2();System.out.println(myLinkedList);}
}

1.3 如何找出单链表中的倒数第k个元素?

  • 首先遍历一遍单链表,求出整个单链表的长度n,然后将倒数第k个,转换为正数第n-k个,接下来遍历一次就可以得到结果;
  • 从头节点开始,依次对链表的每一个节点遍历它后面的k个元素,判断是否到达链表尾,直到找到;
  • 设置两个引用,让其中一个引用比另一个引用先前移k步,然后让两个引用同时往后移动,直到先前移的那个指针为null,没有前移的引用就是倒数第k个元素。
public class MyLinkedList {//...public Node findLastKElement(int k) {Node node = root;//获取链表长度int n = 0;while (node != null) {n++;node = node.next;}System.out.println(n);//找倒数第k个节点,即找正数第n-k个节点node = root;int m = 0;while (node != null) {m++;node = node.next;if (m == n - k) {return node;}}return null;}public Node findLastKElement1(int k) {Node node = root;while (node != null) {//找到该节点后面的第k个节点Node temp = node;for (int i = 0; i < k; i++) {temp = temp.next;}if (temp == null) {return node;}node = node.next;}return null;}public Node findLastKElement2(int k) {Node node1 = root;Node node2 = root;//一个引用前移K步for (int i = 0; i < k; i++) {node2 = node2.next;}//两个引用同时前移,直到先前移的节点为nullwhile (node2 != null) {node1 = node1.next;node2 = node2.next;}return node1;}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);System.out.println(myLinkedList.findLastKElement2(2));}
}

1.4 如何实现链表的反转?

  • 利用栈,先让所有节点入栈,然后再出栈组成新的单链表;
  • 直接遍历,需要提前保存三个节点的信息:当前节点、当前节点的上一个节点、当前节点的下一个节点。
public class MyLinkedList {//...public void reverseIteratively() {Stack<Node> stack = new Stack<>();Node temp = root;//所有节点入栈while (temp != null) {stack.push(temp);temp = temp.next;}//所有节点出栈组成新的单链表Node newRoot = stack.pop();temp = newRoot;while (!stack.isEmpty()) {Node node = stack.pop();node.next = null;temp.next = node;temp = temp.next;}root = newRoot;}public void reverseIteratively1() {Node pReversedHead = root;Node pNode = root;Node pPrev = null;while (pNode != null) {Node pNext = pNode.next;if (pNext == null) {pReversedHead = pNode;}pNode.next = pPrev;pPrev = pNode;pNode = pNext;}root = pReversedHead;}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);myLinkedList.reverseIteratively1();System.out.println(myLinkedList);}
}

1.5 如何从尾到头输出单链表?

  • 用1.4中的方法反转链表后输出;
  • 递归实现,每访问到一个节点,先递归输出它后面的节点,再输出该节点自身。
public class MyLinkedList {//...public void printListReversely(Node node) {if (node != null) {printListReversely(node.next);System.out.println(node.data);}}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);myLinkedList.printListReversely(myLinkedList.root);}
}

1.6 如何寻找单链表的中间节点?

【注1】与1.3很相似,完全可以参考1.3的方法,做一点变形。

【注2】长度为奇数,中间节点只有1个,长度为偶数,中间节点有2个。

  • 先遍历一遍获取单链表的长度,然后遍历到长度一半的位置,就是中间节点;
  • 记录头节点到当前节点的距离,然后遍历当前节点后面所有节点,计算到尾节点的距离,比较距离;
  • 设置两个引用,一个引用一次走1步,一个引用一次走2步,当走的快的引用到达尾节点,走的慢的引用就是中间节点。
public class MyLinkedList {//...public Node[] searchMid() {//获取链表长度Node temp = root;int n = 0;while (temp != null) {n++;temp = temp.next;}//找到链表长度一半的节点temp = root;//长度为偶数时,找到第一个中间节点,长度为奇数时,找到中间节点int k = n % 2 == 0 ? n / 2 - 1 : n / 2;for (int i = 0; i < k; i++) {temp = temp.next;}Node[] nodes = new Node[2];nodes[0] = temp;if (n % 2 == 0) {nodes[1] = temp.next;}return nodes;}public Node[] searchMid1() {//记录头节点到当前节点的距离Node temp = root;int i = 0;while (temp != null) {i++;//记录当前节点到尾节点的距离Node temp1 = temp.next;int j = 0;while (temp1 != null) {j++;if (j > i) {break;}temp1 = temp1.next;}//如果长度为偶数,则有2个中间节点,如果长度为奇数,则有1个中间节点if (i == j || i == j + 1) {Node[] nodes = new Node[2];nodes[0] = temp;if (i == j) {nodes[1] = temp.next;}return nodes;}temp = temp.next;}return null;}public Node[] searchMid2() {//两个引用,一个每次前进2步,一个每次前进1步,直到快引用不能再往后移动Node p = root;Node q = root;while (p != null && p.next != null && p.next.next != null) {p = p.next.next;q = q.next;}//如果长度为偶数,则有2个中间节点,如果长度为奇数,则有1个中间节点Node[] nodes = new Node[2];nodes[0] = q;if (p.next != null) {nodes[1] = q.next;}return nodes;}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);System.out.println(Arrays.toString(myLinkedList.searchMid2()));}
}

1.7 如何检测一个链表是否有环?

https://blog.csdn.net/qq_28958301/article/details/93192577#1.%20%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E9%93%BE%E8%A1%A8%E6%9C%89%E7%8E%AF%EF%BC%9F

1.8 如何在不知道头指针的情况下删除指定节点?

public class MyLinkedList {//...public boolean deleteNode(Node node) {//尾节点无法删除if (node == null || node.next == null) {return false;}//当前节点的值设置为下一个节点的值node.data = node.next.data;//下一个节点设置为下下一个节点node.next = node.next.next;return true;}public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(2);System.out.println(myLinkedList);myLinkedList.add(3);System.out.println(myLinkedList);myLinkedList.add(4);System.out.println(myLinkedList);myLinkedList.deleteNode(myLinkedList.root);System.out.println(myLinkedList);}
}

1.9 如何判断两个链表是否相交?

如果两个链表相交,那么它们一定有着相同的尾节点。

public class MyLinkedList {//...public boolean isIntersect(Node p, Node q) {if (p == null || q == null) {return false;}//找到链表p的尾节点Node pTemp = p;while (pTemp.next != null) {pTemp = pTemp.next;}//找到链表q的尾节点Node qTemp = q;while (qTemp.next != null) {qTemp = qTemp.next;}//返回尾节点是否相同,即链表p、q是否相交return pTemp == qTemp;}public static void main(String[] args) {Node node1 = new Node(1);Node node2 = new Node(2);Node node3 = new Node(3);Node node4 = new Node(4);node1.next = node2;node2.next = node3;node3.next = node4;Node node5 = new Node(5);node5.next = node2;System.out.println(myLinkedList.isIntersect(node5, node1));}
}

【扩展】如果两个链表相交,如何找到它们的相交点?

【思路】如果两个链表A、B的长度分别为len1、len2,并且len1 > len2,设置两个引用p、q,分别指向链表A、B的头节点,p向后先走len1 - len2步,此时p到相交点的距离和q到相交点的距离相同。

public class MyLinkedList {//...public Node getMeetNode(Node p, Node q) {if (!isIntersect(p, q)) {return null;}//求链表p的长度int pLength = 0;Node pTemp = p;while (pTemp != null) {pLength++;pTemp = pTemp.next;}//求链表q的长度int qLength = 0;Node qTemp = q;while (qTemp != null) {qLength++;qTemp = qTemp.next;}//让长的链表先走长度差的距离,此时它们到相遇点的距离相同pTemp = p;qTemp = q;for (int i = 0; i < Math.abs(pLength - qLength); i++) {if (pLength > qLength) {pTemp = pTemp.next;} else if (pLength < qLength) {qTemp = qTemp.next;}}//同时走,如果相同,则是相遇点while (pTemp.next != null && qTemp.next != null) {if (pTemp == qTemp) {return pTemp;}pTemp = pTemp.next;qTemp = qTemp.next;}return null;}public static void main(String[] args) {Node node1 = new Node(1);Node node2 = new Node(2);Node node3 = new Node(3);Node node4 = new Node(4);node1.next = node2;node2.next = node3;node3.next = node4;Node node5 = new Node(5);node5.next = node2;System.out.println(myLinkedList.getMeetNode(node5, node1));}
}

2. 栈与队列

2.1 如何实现栈?

【注】用数组实现栈,没有实现扩容。

public class MyStack {private int size;private int[] array;private int top;private int currentSize;public MyStack(int size) {this.size = size;this.array = new int[size];this.top = -1;this.currentSize = 0;}public void push(int e) throws Exception {if (currentSize >= size) {throw new Exception("full");}array[++top] = e;currentSize++;}public int pop() throws Exception {if (currentSize <= 0) {throw new Exception("empty");}currentSize--;return array[top--];}public void print() {for (int i = 0; i < top + 1; i++) {System.out.print(array[i] + " ");}System.out.println();}public static void main(String[] args) throws Exception {MyStack myStack = new MyStack(5);myStack.push(1);myStack.print();myStack.push(2);myStack.print();myStack.push(3);myStack.print();myStack.push(4);myStack.print();myStack.push(5);myStack.print();//myStack.push(6);System.out.println(myStack.pop());System.out.println(myStack.pop());System.out.println(myStack.pop());System.out.println(myStack.pop());System.out.println(myStack.pop());//System.out.println(myStack.pop());myStack.push(6);myStack.print();myStack.push(7);myStack.print();}
}

2.2 如何用O(1)的时间复杂度求栈中最小的元素?

https://blog.csdn.net/qq_28958301/article/details/93192577#2.%C2%A0%E6%9C%80%E5%B0%8F%E6%A0%88%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9F

2.3 如何实现队列?

【注】用数组实现循环队列,没有实现扩容。

public class MyQueue {private int size;private int[] array;private int head;private int end;public MyQueue(int size) {this.size = size;this.array = new int[size];this.head = -1;this.end = -1;}public void enQueue(int e) throws Exception {if (head - end == 1 || end - head + 1 == size) {throw new Exception("full");}if (end == size - 1) {end = -1;}if (head == end && head == -1) {head++;}array[++end] = e;}public int deQueue() throws Exception {int result;if (head == end) {if (head == -1) {throw new Exception("empty");}//最后一个元素出队result = array[head];head = -1;end = -1;} else {result = array[head++];if (head >= size) {head = 0;}}return result;}private void print() {if (head != -1) {if (end >= head) {for (int i = head; i < end + 1; i++) {System.out.print(array[i] + " ");}System.out.println();} else {for (int i = head; i < end + 1 + size; i++) {System.out.print(array[i % size] + " ");}System.out.println();}}}public static void main(String[] args) throws Exception {MyQueue myQueue = new MyQueue(5);myQueue.enQueue(1);myQueue.print();myQueue.enQueue(2);myQueue.print();myQueue.enQueue(3);myQueue.print();myQueue.enQueue(4);myQueue.print();myQueue.enQueue(5);myQueue.print();//myQueue.enQueue(6);myQueue.deQueue();myQueue.print();myQueue.deQueue();myQueue.print();/*myQueue.deQueue();myQueue.print();myQueue.deQueue();myQueue.print();myQueue.deQueue();myQueue.print();myQueue.deQueue();*/myQueue.enQueue(6);myQueue.print();myQueue.enQueue(7);myQueue.print();myQueue.enQueue(8);}
}

2.4 如何用两个栈模拟队列操作?

https://blog.csdn.net/qq_28958301/article/details/93192577#6.%C2%A0%E5%A6%82%E4%BD%95%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97%EF%BC%9F

3. 排序

3.1 如何进行冒泡排序?

https://blog.csdn.net/qq_28958301/article/details/91876603#2.%C2%A0%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F

3.2 如何进行选择排序?

【思想】每一轮排序都选择当前序列的最小值,把最小值与第一个值交换。

  • 待排序:[38, 65, 97, 76, 13, 27, 49]
  • 第1轮:[13, 65, 97, 76, 38, 27, 49]
  • 第2轮:[13, 27, 97, 76, 38, 65, 49]
  • 第3轮:[13, 27, 38, 76, 97, 65, 49]
  • 第4轮:[13, 27, 38, 49, 97, 65, 76]
  • 第5轮:[13, 27, 38, 49, 65, 97, 76]
  • 第6轮:[13, 27, 38, 49, 65, 76, 97]
  • 第7轮:[13, 27, 38, 49, 65, 76, 97]
public class SortTest {public static void selectSort(int[] array) {for (int i = 0; i < array.length; i++) {//找最小值int minIndex = i;for (int j = i + 1; j < array.length; j++) {if (array[minIndex] > array[j]) {minIndex = j;}}//如果第一个数就是最小值,就没必要交换if (minIndex != i) {int temp = array[i];array[i] = array[minIndex];array[minIndex] = temp;}}}public static void main(String[] args) {int[] array = {38, 65, 97, 76, 13, 27, 49};selectSort(array);System.out.println(Arrays.toString(array));}
}

【优化】以上算法第6轮的结果就已经有序了,还是进行了第7轮,针对这个问题可以创建一个boolean变量,如果一轮排序结束后没有进行交换,那么说明整个序列都已经是有序的了,具体代码不再贴出。

3.3 如何进行插入排序?

【思想】每一轮排序都将序列看作有序序列和无序序列两部分,然后从无序序列中取中取第一个值,插入到有序序列中。

  • 待排序:[38, 65, 97, 76, 13, 27, 49]
  • 第1轮:[38, 65, 97, 76, 13, 27, 49]
  • 第2轮:[38, 65, 97, 76, 13, 27, 49]
  • 第3轮:[38, 65, 97, 76, 13, 27, 49]
  • 第4轮:[38, 65, 76, 97, 13, 27, 49]
  • 第5轮:[13, 38, 65, 76, 97, 27, 49]
  • 第6轮:[13, 27, 38, 65, 76, 97, 49]
  • 第7轮:[13, 27, 38, 49, 65, 76, 97]
public class SortTest {public static void insertSort(int[] array) {for (int i = 0; i < array.length; i++) {int temp = array[i];int j = i;while (j > 0 && temp < array[j - 1]) {array[j] = array[j - 1];j--;}array[j] = temp;}}public static void main(String[] args) {int[] array = {38, 65, 97, 76, 13, 27, 49};insertSort(array);System.out.println(Arrays.toString(array));}
}

3.4 如何进行希尔排序?

【思想】希尔排序基于插入排序,又叫缩小增量排序。首先取一个增量k,根据增量k将待排序序列分为几个子序列,对这几个子序列分别进行插入排序,然后缩小增量,直到增量为1最后对整个序列进行一次插入排序。

  • 待排序:[38, 65, 97, 76, 13, 27, 49]
  • 取增量k = length / 2 = 3,那么可分为子序列:[38, 76], [65, 13], [97, 27], [76, 49]
  • 第1轮:[38, 65, 97, 76, 13, 27, 49]
  • 第2轮:[38, 13, 97, 76, 65, 27, 49]
  • 第3轮:[38, 13, 27, 76, 65, 97, 49]
  • 第4轮:[38, 13, 27, 49, 65, 97, 76]
  • 取增量k = k / 2 = 1,就相当于是对整个序列进行一次插入排序,参考3.3插入排序过程。
public class SortTest {public static void shellSort(int[] array) {int k = array.length / 2;while (k > 0) {for (int i = k; i < array.length; i++) {int temp = array[i];int j = i;while (j >= k && temp < array[j - k]) {array[j] = array[j - k];j -= k;}array[j] = temp;System.out.println(Arrays.toString(array));}k /= 2;}}public static void main(String[] args) {int[] array = {38, 65, 97, 76, 13, 27, 49};shellSort(array);System.out.println(Arrays.toString(array));}
}

3.5 如何进行快速排序?

https://blog.csdn.net/qq_28958301/article/details/91876603#3.%C2%A0%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F

【补充】当初始的序列整体或局部有序时,快速排序的性能将会下降。特别的当选择的基准关键字是待排序的所有记录中最小或者最大的,时间复杂度能达到O(n^2)。

3.6 如何进行归并排序?

【思想】对于给定的一组n个记录,首先将每两个相邻的长度为1的子序列进行归并,得到n/2个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列。

简单来说,就是先递归划分子序列,一直划分到不能再划分,然后合并两个子序列。

public class SortTest {public static int[] mergeSort(int[] array) {//如果少于2个元素,不需要再分if (array.length < 2) {return array;}//分成两个子序列int center = array.length / 2;int[] leftArray = Arrays.copyOfRange(array, 0, center);int[] rightArray = Arrays.copyOfRange(array, center, array.length);//递归、合并return merge(mergeSort(leftArray), mergeSort(rightArray));}public static int[] merge(int[] leftArray, int[] rightArray) {//将两个数组按顺序合并成一个数组int[] resultArray = new int[leftArray.length + rightArray.length];int i = 0, j = 0, k = 0;while (i < leftArray.length && j < rightArray.length) {if (leftArray[i] < rightArray[j]) {resultArray[k++] = leftArray[i++];} else {resultArray[k++] = rightArray[j++];}}while (i < leftArray.length) {resultArray[k++] = leftArray[i++];}while (j < rightArray.length) {resultArray[k++] = rightArray[j++];}return resultArray;}public static void main(String[] args) {int[] array = {38, 65, 97, 76, 13, 27, 49};System.out.println(Arrays.toString(mergeSort(array)));}
}

3.7 如何进行堆排序?

https://blog.csdn.net/qq_28958301/article/details/91876603#4.%C2%A0%E5%A0%86%E6%8E%92%E5%BA%8F

3.8 排序算法总结

排序算法 最好时间 平均时间 最坏时间 辅助存储 稳定性 备注
冒泡排序 O(n) O(n^2) O(n^2) O(1) 稳定 n小时比较好
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定 n小时比较好
插入排序 O(n) O(n^2) O(n^2) O(1) 稳定 大部分已经有序时比较好
希尔排序 O(n) O(nlogn) 略优于O(n^2) O(1) 不稳定  
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn) 不稳定 n大时比较好
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定 n大时比较好
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定 n大时比较好

4. 位运算

4.1 如何用移位操作实现乘法运算?

【注】把一个数左移n位相当于把该数乘以2的n次方,代码略。

4.2 如何判断一个数是否为2的n次方?

【注】如果一个数是2的n次方,那么这个数的二进制表示中只有一位是1,其余位都是0,那么n&(n-1)必定为0,代码略。

4.3 如何求二进制数中1的个数?

【注】一个数n,每进行一次n&(n-1),其结果中都会少一位1,而且是最后一位,所以可以每进行一次该操作,就计数+1,直到该操作结果为0,代码略。

5. 数组

5.1 如何寻找数组中的最小值与最大值?

  • 第一次遍历找出最小值、第二次遍历找出最大值,比较次数为2n;
  • 设置两个变量,一次遍历就找出最小值和最大值(最常用);
  • 在上一个方法的基础上,每一次比较都比较相邻两个元素,将小的那个与最小值比较、大的那个与最大值比较,比较次数为1.5n;
  • 将每相邻两个元素分为一组,将小的放在左边、大的放在右边,在所有左边中找最小值、所有右边中找最大值,比较次数为1.5n~2n;
  • 将数组分为左右两边,分别找出左边和右边的最小值、最大值,最后再来比较左边的最小值与右边的最小值,比较左边的最大值和右边的最大值,比较次数为1.5n
  • 递归。

方法1-5代码略,方法6寻找最大值代码,最小值同理:

public class ArrayTest {public static int findMax(int[] array, int index) {if (index == 0) {return array[0];}return Math.max(array[index], findMax(array, index - 1));}public static void main(String[] args) {int[] array = {7, 2, 4, 5, 8};System.out.println(findMax(array, array.length - 1));}
}

5.2 如何找出数组中第2大的数?

【注】一看代码就懂。

public class ArrayTest {public static int findSecMax(int[] array) {int max = array[0];int secMax = array[0];for (int i = 1; i < array.length; i++) {if (max < array[i]) {secMax = max;max = array[i];} else {if (secMax < array[i]) {secMax = array[i];}}}return secMax;}public static void main(String[] args) {int[] array = {3, 2, 4, 5, 7};System.out.println(findSecMax(array));}
}

5.3 如何求最大子数组之和?(动态规划)

【问题】一个有n个元素的数组,数组中连续的一个或多个元素可以组成一个连续的子数组,求子数组和的最大值。如数组{1, -4, 7, -5},子数组有{1}, {1, -4}, {1, -4, 7}, {1, -4, 7, -5}, {-4}, {-4, 7}, {-4, 7, -5}, {7}, {7, -5}, {-5},其中子数组和的最大值是7。

  • 常规遍历求解
public class ArrayTest {public static int maxSubArray(int[] array) {int max = 0;for (int i = 0; i < array.length; i++) {int sum = array[i];if (sum > max) {max = sum;}for (int j = i + 1; j < array.length; j++) {sum += array[j];if (sum > max) {max = sum;}}}return max;}public static void main(String[] args) {int[] array = {1, -2, 4, 8, -4, 7, -1, -5};System.out.println(maxSubArray(array));}
}
  • 动态规划思想

动态规划的思想参考我的另一篇文章:

https://blog.csdn.net/qq_28958301/article/details/93192577#10.%C2%A0%E5%A6%82%E4%BD%95%E6%B1%82%E8%A7%A3%E9%87%91%E7%9F%BF%E9%97%AE%E9%A2%98%EF%BC%9F%EF%BC%88%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E9%97%AE%E9%A2%98%EF%BC%89

public class ArrayTest {public static int maxSubArray1(int[] array, int index, int sum) {if (index < 0) {return sum;}return Math.max(sum,        //如果索引为index的数不要maxSubArray1(array, index - 1, sum + array[index])      //如果索引为index的数要);}public static void main(String[] args) {int[] array = {1, -2, 4, 8, -4, 7, -1, -5};int max = maxSubArray1(array, array.length - 1, 0);for (int i = array.length - 2; i > -1; i--) {int temp = maxSubArray1(array, i, 0);if (max < temp) {max = temp;}}System.out.println(max);}
}

假设当前的数是array[i]。第一步要判断的是加上array[i]后的子序列的和大还是从array[i]重新开始的子序列的和大;第二步要判断的是之前求出来的最大的和大还是当前求出来的和大。

public class ArrayTest {public static int maxSubArray2(int[] array) {int[] now = new int[array.length];int[] max = new int[array.length];now[0] = array[0];max[0] = array[0];for (int i = 1; i < array.length; i++) {now[i] = Math.max(now[i - 1] + array[i], array[i]);max[i] = Math.max(max[i - 1], now[i]);}return max[array.length - 1];}public static int maxSubArray3(int[] array) {//只保存最后的结果,省去数组空间int now = array[0];int max = array[0];for (int i = 1; i < array.length; i++) {now = Math.max(now + array[i], array[i]);max = Math.max(max, now);}return max;}public static void main(String[] args) {int[] array = {1, -2, 4, 8, -4, 7, -1, -5};System.out.println(maxSubArray2(array));System.out.println(maxSubArray3(array));}
}

5.4 如何找出数组中重复元素最多的数?

  • 利用计数排序的思想,参考我的另一篇文章:

https://blog.csdn.net/qq_28958301/article/details/91876603#5.%C2%A0%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F

public class ArrayTest {public static int findMostFrequentInArray(int[] array) {//找最大值和最小值int min = array[0];int max = array[0];for (int i = 1; i < array.length; i++) {if (min > array[i]) {min = array[i];}if (max < array[i]) {max = array[i];}}//创建统计数组int[] count = new int[max - min + 1];for (int i = 0; i < array.length; i++) {count[array[i] - min]++;}//找出出现最多的数int maxIndex = 0;for (int i = 1; i < count.length; i++) {if (count[maxIndex] < count[i]) {maxIndex = i;}}return min + maxIndex;}public static void main(String[] args) {int[] array = {1, 1, 2, 2, 4, 4, 4, 4, 5, 5, 6, 6, 6};System.out.println(findMostFrequentInArray(array));}
}
  • 利用哈希表
public class ArrayTest {public static int findMostFrequentInArray1(int[] array) {//每个数作为key存入哈希表,value则是出现的次数Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < array.length; i++) {if (map.containsKey(array[i])) {map.put(array[i], map.get(array[i]) + 1);} else {map.put(array[i], 1);}}//找出value最大的数int maxValue = Integer.MIN_VALUE;int maxKey = Integer.MIN_VALUE;for (Map.Entry<Integer, Integer> entry : map.entrySet()) {Integer value = entry.getValue();if (maxValue < value) {maxValue = value;maxKey = entry.getKey();}}return maxKey;}public static void main(String[] args) {int[] array = {1, 1, 2, 2, 4, 4, 4, 4, 5, 5, 6, 6, 6};System.out.println(findMostFrequentInArray1(array));}
}

5.5 如何求数组中两两相加等于k的组合种数?

【题目】给定一个数组{1, 7, 17, 2, 6, 3, 14},k = 20,这个数组种满足条件的有两对组合:{17, 3}, {6, 14}。

  • 双重遍历,代码略,时间复杂度O(n^2);
  • 先排序,然后同时从左往右、从右往左遍历排序后的数组,时间复杂度O(n)。
public class ArrayTest {public static void findSumK(int[] array, int k) {//排序Arrays.sort(array);int left = 0;int right = array.length - 1;while (left < right) {if (array[left] + array[right] < k) {left++;} else if (array[left] + array[right] > k) {right--;} else {System.out.println(array[left] + " + " + array[right] + " = 20");left++;right--;}}}public static void main(String[] args) {int[] array = {1, 7, 17, 2, 6, 3, 3, 14};findSumK(array, 20);}
}

5.6 如何把一个数组循环右移k位?

public class ArrayTest {public static int[] shiftK(int[] array, int k) {int[] newArray = new int[array.length];int j = 0;for (int i = array.length - k; i < array.length; i++, j++) {newArray[j] = array[i];}for (int i = 0; i < array.length - k; i++, j++) {newArray[j] = array[i];}return newArray;}public static void shiftK1(int[] array, int k) {while (k-- > 0) {int temp = array[array.length - 1];for (int i = array.length - 1; i > 0; i--) {array[i] = array[i - 1];}array[0] = temp;}}public static void shiftK2(int[] array, int k) {//逆序[0, array.length - 1 - k]reverse(array, 0, array.length - 1 - k);//逆序[array.length - k, array.length - 1]reverse(array, array.length - k, array.length - 1);//逆序[0, array.length -1]reverse(array, 0, array.length - 1);}public static void reverse(int[] array, int i, int j) {while (i < j) {int temp = array[i];array[i] = array[j];array[j] = temp;i++;j--;}}public static void main(String[] args) {int[] array = {1, 2, 3, 4, 5, 6, 7, 8};shiftK2(array, 2);System.out.println(Arrays.toString(array));}
}

【注】前两种方法就不多说了,第三种方法的意思是,把一个数组循环右移k位等价于先把左边数组长度-k个数逆序,然后把右边k个数逆序,最后再把所有数逆序。如{1, 2, 3, 4, 5, 6, 7, 8},左边逆序{6, 5, 4, 3, 2, 1, 7, 8},右边逆序{6, 5, 4, 3, 2, 1, 8, 7},全部逆序{7, 8, 1, 2, 3, 4, 5, 6}。

5.7 如何找出数组中第k个最小的数?

  • 从小大到排序后取索引为k-1的数,代码略;
  • 利用快速排序的思想,因为枢纽元的左边一定是比它小的数、右边一定是比它大的数,所以可以通过判断枢纽元的索引与k-1的大小来判断。
public class ArrayTest {public static int getKMin(int[] array, int k, int left, int right) {int pivotIndex = quickSort(array, left, right);if (pivotIndex < k - 1) {//在右边继续寻找return getKMin(array, k, pivotIndex + 1, right);} else if (pivotIndex > k - 1) {//在左边继续寻找return getKMin(array, k, 0, pivotIndex - 1);} else {//找到,返回return array[pivotIndex];}}public static int quickSort(int[] array, int left, int right) {int pivotIndex = left;int i = left;int j = right;while (i < j) {//从右往左while (j > i) {if (array[j] < array[pivotIndex]) {//交换int temp = array[j];array[j] = array[pivotIndex];array[pivotIndex] = temp;//设置枢纽元索引pivotIndex = j;i++;break;} else {j--;}}//从左往右while (i < j) {if (array[i] > array[pivotIndex]) {//交换int temp = array[i];array[i] = array[pivotIndex];array[pivotIndex] = temp;//设置枢纽元索引pivotIndex = i;j--;break;} else {i++;}}}return pivotIndex;}public static void main(String[] args) {int[] array = {7, 8, 2, 1, 6};System.out.println(getKMin(array, 2, 0, array.length - 1));}
}

【注】快速排序参考我的另一篇文章:

https://blog.csdn.net/qq_28958301/article/details/91876603#3.%C2%A0%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F

5.8 如何找到数组中只出现一次的数字?

做这道题之前先看一下一些相关的题目:

https://blog.csdn.net/qq_28958301/article/details/93192577#11.%C2%A0%E5%AF%BB%E6%89%BE%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%95%B4%E6%95%B0%EF%BC%9F

【题目】在一个数组中,只有1个数出现了1次,其他数都出现了n次,怎么找到这个只出现了1次的数?

【思路】如果数组中的所有数都出现了n次,那么这个数组中所有数对应的二进制数中,各个位上1出现的次数之和可以被n整除。如{1, 1, 1, 2, 2, 2},二进制表示{01, 01, 01, 10, 10, 10},第0位1出现的次数之和是1 + 1 + 1 + 0 + 0 + 0 = 3 % 3 = 0,第1位同理。于是如果还有一个数只出现了一次,如{1, 1, 1, 2, 2, 2, 3},二进制表示{01, 01, 01, 10, 10, 10, 11},那么第0位1出现的次数之和是1 + 1 + 1 + 0 + 0 + 0 + 1 = 4 % 3 = 1,第1位同理,取余结果组合起来就是那个数。

public class ArrayTest {public static int findOnce(int[] array, int n) {//一共array.length个数,用k表示有几个数不能再右移了int k = 0;//要查找的数int result = 0;int j = 0;while (k < array.length) {int count = 0;for (int i = 0; i < array.length; i++) {if (array[i] == 0) {continue;}if ((array[i] & 1) == 1) {count++;}array[i] >>= 1;if (array[i] == 0) {k++;}}//result += ((count % n) * Math.pow(2, j++));result += ((count % n) << j++);}return result;}public static void main(String[] args) {int[] array = {1, 1, 1, 2, 2, 2, 3};System.out.println(findOnce(array, 3));}
}

5.9 如何找出数组中唯一的重复元素?

【题目】对于数组a[n],里面存放的是无序的1~n-1,其中有1个数重复1次,如何找出这个重复的数?

  • 数组所有元素之和 - 1~n-1之和;
  • 数组所有元素异或的结果 异或 1~n-1异或的结果;
  • 用空间换时间。
public class ArrayTest {public static int findOnlyDup(int[] array) {int sum1 = 0, sum2 = 0;for (int i = 0; i < array.length; i++) {sum1 += array[i];sum2 += i;}return sum1 - sum2;}public static int findOnlyDup1(int[] array) {int xor1 = 0, xor2 = 0;for (int i = 0; i < array.length; i++) {xor1 ^= array[i];xor2 ^= i;}return xor1 ^ xor2;}public static int findOnlyDup2(int[] array) {Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < array.length; i++) {if (map.containsKey(array[i])) {return array[i];} else {map.put(array[i], array[i]);}}return Integer.MAX_VALUE;}public static void main(String[] args) {int[] array = {1, 2, 1, 3, 4};System.out.println(findOnlyDup(array));System.out.println(findOnlyDup1(array));System.out.println(findOnlyDup2(array));}
}

5.10 如何求数对之差的最大值?(动态规划)

【题目】数组中的一个数字减去它右边子数组中的一个数字可以得到一个差值,求所有可能的差值中的最大值。如{1, 4, 17, 3, 2, 9},最大的差值为17-2=15。

  • 双重遍历找最大差值;
public class ArrayTest {public static int findMaxSubtract(int[] array) {int max = Integer.MIN_VALUE;for (int i = 0; i < array.length; i++) {for (int j = i + 1; j < array.length; j++) {if (array[i] - array[j] > max) {max = array[i] - array[j];}}}return max;}public static void main(String[] args) {int[] array = {1, 4, 17, 3, 2, 9};System.out.println(findMaxSubtract(array));}
}
  • 动态规划的思想;

从左往右看数组array中的每一个数,假设当前这个数的索引为i,那么array[i]左边的某个数array[x] - array[i]的差值要最大,只可能的是array[x]是左边的数中的最大值。

于是建立两个数组sub[array.length]和max[array.length],sub用于存放到i为止,最大差值的变化,max用于存放到i为止,左边的数中的最大值。

public class ArrayTest {public static int findMaxSubtract1(int[] array) {int[] sub = new int[array.length];int[] max = new int[array.length];sub[0] = 0;max[0] = array[0];for (int i = 1; i < array.length; i++) {sub[i] = Math.max(max[i - 1] - array[i], sub[i - 1]);max[i] = Math.max(array[i], max[i - 1]);}return sub[array.length - 1];}public static void main(String[] args) {int[] array = {1, 4, 17, 3, 2, 9};System.out.println1(findMaxSubtract(array));}
}

上面用数组表示只是为了更加直观,可以明确的看到只需要保存最后的值就行,所以可以用2个变量替代:

public class ArrayTest {public static int findMaxSubtract2(int[] array) {int sub = 0;int max = array[0];for (int i = 1; i < array.length; i++) {sub = Math.max(max - array[i], sub);max = Math.max(array[i], max);}return sub;}public static void main(String[] args) {int[] array = {1, 4, 17, 3, 2, 9};System.out.println2(findMaxSubtract(array));}
}

甚至还可以改成递归的方式:

public class ArrayTest {public static int findMaxSubtract3(int[] array, int index, int sub, int max) {if (index == array.length) {return sub;}sub = Math.max(max - array[index], sub);max = Math.max(array[index], max);return findMaxSubtract3(array, index + 1, sub, max);}public static void main(String[] args) {int[] array = {1, 4, 17, 3, 2, 9};System.out.println(findMaxSubtract3(array, 1, 0, array[0]));}
}

5.11 如何求绝对值最小的数?

【问题】在一个升序排列的数组中,可能有正数、0和负数,求数组中绝对值最小的数。

public class ArrayTest {public static int findMinAbsoluteValue(int[] array) {//如果最小值大于等于0if (array[0] >= 0) {return array[0];}//如果最大值小于等于0if (array[array.length - 1] <= 0) {return array[array.length - 1];}//如果正数负数都有,找分界点int i = 1;for (; i < array.length - 1; i++) {if (array[i] >= 0) {break;}}return array[i] < Math.abs(array[i - 1]) ? array[i] : array[i - 1];}public static void main(String[] args) {int[] array = {-10, -5, -2, 7, 15, 50};System.out.println(findMinAbsoluteValue(array));}
}

【注】该方法的复杂度主要就在找分界点上,可以通过二分法找分界点提高效率,但是代码复杂很多。

5.12 如何求数组中两个元素的最小距离?

【题目】一个数组中可能含有重复元素,给出该数组中的两个数,求出这两个数的最小距离。

public class ArrayTest {public static int findMinDistance(int[] array, int m, int n) {return Math.min(findMinDistanceMethod(array, m, n),findMinDistanceMethod(array, n, m));}public static int findMinDistanceMethod(int[] array, int a, int b) {int min = Integer.MAX_VALUE;boolean start = false;for (int i = 0; i < array.length; i++) {if (array[i] == a) {if (!start) {start = true;}min = 0;}if (array[i] != a && start) {min++;}if (array[i] == b && start) {break;}}return min;}public static void main(String[] args) {int[] array = {4, 5, 6, 4, 7, 4, 6, 4, 7, 8, 5, 6, 4, 3, 10, 8};System.out.println(findMinDistance(array, 4, 8));}
}

5.13 如何求指定数字在数组中第一次出现的位置?

【题目】给定一个数组,这个数组相邻元素之差为1,给定一个数,查找这个数在这个数组中第一次出现的位置。

  • 直接遍历,代码略;
  • 根据相邻元素之差为1,跳跃查找。
public class ArrayTest {public static int findFirstIndex(int[] array, int m) {int i = 0;while (i < array.length) {if (array[i] != m) {i += m - array[i];} else {return i;}}return Integer.MIN_VALUE;}public static void main(String[] args) {int[] array = {3, 4, 5, 6, 5, 6, 7, 8, 9, 8};System.out.println(findFirstIndex(array, 9));}
}

5.14 如何对数组的两个子有序段进行合并?

【题目】一个数组中,包含两个子有序段,如{1, 5, 6, 7, 9, 2, 4, 8, 10, 13, 14},怎样将两个子有序段合并,让整个数组有序。

  • 如果不限制空间复杂度的话,非常容易,再创建一个同样长度的数组就行,代码略;
  • 这里给出如果限制空间复杂度为O(1)的方法。
public class ArrayTest {public static void merge(int[] array, int mid) {//遍历前一个子有序段for (int i = 0; i < mid; i++) {//如果array[mid]比array[i]小,那么交换if (array[mid] < array[i]) {int temp = array[mid];array[mid] = array[i];array[i] = temp;//交换后的array[mid]不一定是后一个子有序段中最小的,所以需要排序,采用插入排序的思想temp = array[mid];int j = mid + 1;while (j < array.length && array[j] < temp) {array[j - 1] = array[j];j++;}array[j - 1] = temp;}}}public static void main(String[] args) {int[] array = {1, 5, 6, 7, 9, 2, 4, 8, 10, 13, 14};merge(array, 5);System.out.println(Arrays.toString(array));}
}

【注】其实这个过程就是归并排序中合并的过程。

5.15 如何计算两个有序整型数组的交集?

  • 二路归并法,同时遍历两个数组(有序才行,不是有序的话只能双重遍历);
  • 利用HashMap或者HashSet存储一个数组中的元素,然后遍历另一个数组,如果HashMap或HashSet中已经存在,那么就是交集。
public class ArrayTest {public static Object[] getIntersection(int[] array1, int[] array2) {List<Integer> list = new ArrayList<>();int i = 0, j = 0;while (i < array1.length && j < array2.length) {if (array1[i] < array2[j]) {i++;} else if (array1[i] > array2[j]) {j++;} else {list.add(array1[i]);i++;j++;}}return list.toArray();}public static Object[] getIntersection1(int[] array1, int[] array2) {List<Integer> list = new ArrayList<>();Set<Integer> set = new HashSet<>();for (int i = 0; i < array1.length; i++) {set.add(i);}for (int i = 0; i < array2.length; i++) {if (set.contains(array2[i])) {list.add(array2[i]);}}return list.toArray();}public static void main(String[] args) {int[] array1 = {0, 1, 2, 3, 4};int[] array2 = {1, 3, 5, 7, 9};System.out.println(Arrays.toString(getIntersection(array1, array2)));System.out.println(Arrays.toString(getIntersection1(array1, array2)));}
}

【注】上述方法适用于两个数组长度相差不大的情况,如果相差很大,那么可以遍历长度短的数组,对每个元素在长度长的数组中进行二分查找。

5.16 如何求解数组中反序对的个数?

【题目】对于一个数组,如果a[i] > a[j],其中i < j,那么a[i]和a[j]被称为一个反序对。

  • 双重遍历,时间复杂度O(n^2);
  • 利用归并排序,归并排序的时间复杂度是O(nlogn),只需在合并过程中对反序对进行计数就行,其他代码和归并排序一摸一样。
public class ArrayTest {public static int[] reverseCount(int[] array) {//如果少于2个元素,不需要再分if (array.length < 2) {return array;}//分成两个子序列int center = array.length / 2;int[] leftArray = Arrays.copyOfRange(array, 0, center);int[] rightArray = Arrays.copyOfRange(array, center, array.length);//递归、合并return reverseCountMerge(reverseCount(leftArray), reverseCount(rightArray));}private static int count = 0;public static int[] reverseCountMerge(int[] leftArray, int[] rightArray) {//对反序对计数for (int i = 0; i < leftArray.length; i++) {for (int j = 0; j < rightArray.length; j++) {if (leftArray[i] > rightArray[j]) {count++;} else {break;}}}//将两个数组按顺序合并成一个数组int[] resultArray = new int[leftArray.length + rightArray.length];int i = 0, j = 0, k = 0;while (i < leftArray.length && j < rightArray.length) {if (leftArray[i] < rightArray[j]) {resultArray[k++] = leftArray[i++];} else {resultArray[k++] = rightArray[j++];}}while (i < leftArray.length) {resultArray[k++] = leftArray[i++];}while (j < rightArray.length) {resultArray[k++] = rightArray[j++];}return resultArray;}public static void main(String[] args) {int[] array = {1, 5, 3, 2, 6};reverseCount(array);System.out.println(count);}
}

5.17 如何求解最小三元组距离?

【题目】假设a[i]和b[j]和c[k]是一个三元组,那么distance = max(|a[i] - b[j]|, |a[i] - b[k]|, |a[j] - b[k]|),求最小的距离的三元组。

  • 三重遍历,时间复杂度等于三个数组的长度的乘积;
  • 如果三个数组分别都是升序的,那么可以用以下这个方法:
    • 如果当前a[i] <= b[j] <= c[k],那么距离为c[k] - a[i];
    • 要距离比这更小,只有一种可能是a[i+1] <= b[j] <= c[k],距离为c[k] - a[i+1] < c[k] - a[i],因为a[i+1] >a [i]。
public class ArrayTest {public static int getMin3Distance(int[] a, int[] b, int[] c) {int i = 0, j = 0, k = 0;int minDistance = Integer.MAX_VALUE;int distance;while (true) {int min = Math.min(Math.min(a[i], b[j]), c[k]);distance = Math.max(Math.max(a[i], b[j]), c[k]) - min;if (minDistance > distance) {minDistance = distance;}if (min == a[i]) {if (++i >= a.length) {break;}} else if (min == b[j]) {if (++j >= b.length) {break;}} else {if (++k >= c.length) {break;}}}return minDistance;}public static void main(String[] args) {int[] array1 = {3, 4, 5, 7};int[] array2 = {10, 12, 14, 16, 17};int[] array3 = {20, 21, 23, 24, 27, 30};System.out.println(getMin3Distance(array1, array2, array3));}
}

6. 字符串

6.1 如何实现字符串单词的反转?

【问题】把一个句子中的单词进行反转,例如"how are you"反转后变为"you are how"。

public class StringTest {public static String swapWords(String s) {char[] chars = s.toCharArray();//整个字符串反序swapMethod(chars, 0, chars.length - 1);//每个单词反序int begin = 0;for (int i = 0; i < chars.length; i++) {if (chars[i] == ' ') {swapMethod(chars, begin, i - 1);begin = i + 1;}}swapMethod(chars, begin, chars.length - 1);return new String(chars);}public static void swapMethod(char[] chars, int begin, int end) {int i = begin;int j = end;while (i < j) {char temp = chars[i];chars[i] = chars[j];chars[j] = temp;i++;j--;}}public static String swapWords1(String s) {String[] array = s.split(" ");int i = 0, j = array.length - 1;while (i < j) {String temp = array[i];array[i] = array[j];array[j] = temp;i++;j--;}StringBuilder sb = new StringBuilder();for (String ss : array) {sb.append(ss).append(" ");}return sb.toString();}public static void main(String[] args) {System.out.println(swapWords("how are you"));System.out.println(swapWords1("how are you"));}
}

6.2 如何判断两个字符串是否由相同的字符组成?

【题目】由相同的字符组成是指组成两个字符串的字母以及各个字母的个数是一样的,只是排列顺序不同而已,例如"aaaabbc"与"abcbaaa"就由相同的字符组成。

  • 排序后比较,时间复杂度取决于排序算法;
  • 空间换时间,时间复杂度O(n)。
public class StringTest {public static boolean compare(String s1, String s2) {//如果两个字符串长度不同,返回falseif (s1.length() != s2.length()) {return false;}//将第一个字符串的每个字符放进哈希表中计数Map<Character, Integer> map1 = new HashMap<>();for (char c : s1.toCharArray()) {if (map1.containsKey(c)) {map1.put(c, map1.get(c) + 1);} else {map1.put(c, 1);}}//将第二个字符串的每个字符放进哈希表中计数Map<Character, Integer> map2 = new HashMap<>();for (char c : s2.toCharArray()) {if (map2.containsKey(c)) {map2.put(c, map2.get(c) + 1);} else {map2.put(c, 1);}}//如果字符数量不同,返回falsefor (Map.Entry entry : map1.entrySet()) {Object key = entry.getKey();if (!map2.containsKey(key) || map2.get(key) != entry.getValue()) {return false;}}return true;}public static boolean compare1(String s1, String s2) {//如果两个字符串长度不同,返回falseif (s1.length() != s2.length()) {return false;}//对两个字符串分别排序,然后直接比较是否相等char[] chars1 = s1.toCharArray();char[] chars2 = s2.toCharArray();Arrays.sort(chars1);Arrays.sort(chars2);return new String(chars1).equals(new String(chars2));}public static void main(String[] args) {System.out.println(compare("aaaabbc", "abcbaaa"));System.out.println(compare1("aaaabbc", "abcbaaa"));}
}

6.3 如何删除字符串中重复的字符?

  • 双重遍历,时间复杂度O(n^2),代码略;
  • 正则表达式, 代码略;
  • 空间换时间,时间复杂度O(n)。
public class StringTest {public static String removeDuplicate(String s){Set<Character> set = new LinkedHashSet<>();for (char c : s.toCharArray()) {set.add(c);}StringBuilder sb = new StringBuilder();for (Character c : set) {sb.append(c);}return sb.toString();}public static void main(String[] args) {System.out.println(removeDuplicate("good"));}
}

6.4 如何输出所有不重复字符的全排列?

  • 字典序算法,思想参考我的另一篇文章:

https://blog.csdn.net/qq_28958301/article/details/93192577#7.%C2%A0%E5%AF%BB%E6%89%BE%E5%85%A8%E6%8E%92%E5%88%97%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%95%B0%EF%BC%9F%EF%BC%88%E5%AD%97%E5%85%B8%E5%BA%8F%E7%AE%97%E6%B3%95%EF%BC%89

public class StringTest {public static Object[] getAllCombinations(String s) {//把这个字符串按字典序排列char[] chars = s.toCharArray();Arrays.sort(chars);//按字典序算法每次找比当前大的下一个全排列List<String> combinations = new ArrayList<>();combinations.add(new String(chars));while (getNextCombinations(chars)) {combinations.add(new String(chars));}return combinations.toArray();}public static boolean getNextCombinations(char[] chars) {//从右往左找到逆序位int index = chars.length - 1;while (index > 0 && chars[index - 1] > chars[index]) {index--;}if (index == 0) {//如果完全逆序return false;} else if (index == chars.length - 1) {//如果完全有序,交换最后两位char temp  = chars[chars.length - 1];chars[chars.length - 1] = chars[chars.length - 2];chars[chars.length - 2] = temp;return true;} else {//此时[index, chars.length - 1]是完全逆序的,将该范围内刚好比chars[index - 1]大的数与之交换for (int i = chars.length - 1; i >= index; i--) {if (chars[i] > chars[index - 1]) {char temp = chars[i];chars[i] = chars[index - 1];chars[index - 1] = temp;break;}}//此时[index, chars.length - 1]还是完全逆序的,将它顺序for (int i = index, j = chars.length - 1; i < j; i++, j--) {char temp = chars[i];chars[i] = chars[j];chars[j] = temp;}return true;}}public static void main(String[] args) {System.out.println(Arrays.toString(getAllCombinations("abc")));}
}
  • 递归。
public class StringTest {public static Object[] getAllCombinations1(String s) {char[] chars = s.toCharArray();List<String> combinations = new ArrayList<>();getCombinationsMethod(chars, 0, chars.length - 1, combinations);return combinations.toArray();}public static void getCombinationsMethod(char[] chars, int start, int end, List<String> list) {if (start >= end) {list.add(new String(chars));return;}for (int i = start; i <= end; i++) {//交换char temp = chars[start];chars[start] = chars[i];chars[i] = temp;//递归getCombinationsMethod(chars, start + 1, end, list);//交换回来temp = chars[start];chars[start] = chars[i];chars[i] = temp;}}public static void main(String[] args) {System.out.println(Arrays.toString(getAllCombinations1("abc")));}
}

6.5 如何输出字符串的所有组合?

【题目】假设字符串中所有字符都不重复,例如"abc",则由"a", "b", "c", "ab", "bc", "ac", "abc"这几种组合。

public class StringTest {public static Object[] getAllCombine(String s) {List<String> combinations = new ArrayList<>();getAllCombineMethod(s, 0, new StringBuilder(), combinations);return combinations.toArray();}public static void getAllCombineMethod(String s, int index, StringBuilder sb, List<String> list) {if (index >= s.length()) {if (sb.length() > 0) {list.add(sb.toString());}return;}//如果索引为index的字符要sb.append(s.charAt(index));getAllCombineMethod(s, index + 1, sb, list);//如果索引为index的字符不要sb.deleteCharAt(sb.length() - 1);getAllCombineMethod(s, index + 1, sb, list);}public static void main(String[] args) {System.out.println(Arrays.toString(getAllCombine("abc")));}
}

7. 二叉树

7.1 二叉树的基本概念、实现、遍历

参考我的另外2篇文章:

https://blog.csdn.net/qq_28958301/article/details/90036572

https://blog.csdn.net/qq_28958301/article/details/91590545

7.2 已知先序遍历和中序遍历,如何求后序遍历?

【题目】已知先序遍历为ABDECF,中序遍历为DBEAFC,求后序遍历。

【思路】由于先序遍历是“根->左->右”,那么由先序遍历结果可知第一层A是根。由于中序遍历是“左->根->右”,那么由中序遍历结果可知A的左孩子包括DBE,右孩子包括FC。然后继续这个思路下去,就能把树构建出来。

public class TreeTest {private static class Node {Object data;Node left;Node right;public Node(Object data) {this.data = data;}}public static Node buildTreeByPreAndIn(String[] pre, String[] in) {if (pre.length == 0 && in.length == 0) {return null;}//根节点就是先序遍历第一个元素Node node = new Node(pre[0]);//在中序遍历中找到根节点的位置int index = 0;for (; index < in.length; index++) {if (in[index].equals(node.data)) {break;}}//把先序遍历和中序遍历都分成两半String[] inLeft = Arrays.copyOfRange(in, 0, index);String[] inRight = Arrays.copyOfRange(in, index + 1, in.length);String[] preLeft = Arrays.copyOfRange(pre, 1, inLeft.length + 1);String[] preRight = Arrays.copyOfRange(pre, inLeft.length + 1, pre.length);node.left = buildTreeByPreAndIn(preLeft, inLeft);node.right = buildTreeByPreAndIn(preRight, inRight);return node;}public static void postOrder(Node node) {if (node == null) {return;}postOrder(node.left);postOrder(node.right);System.out.print(node.data + " ");}public static void main(String[] args) {String[] pre = {"A", "B", "D", "E", "C", "F"};String[] in = {"D", "B", "E", "A", "F", "C"};Node node = buildTreeByPreAndIn(pre, in);postOrder(node);}
}

8. 其他

8.1 如何不使用比较运算就求出两个数哪个大哪个小?

【思路】max(a, b) = (a + b + |a - b|) / 2,min(a, b) = (a + b - |a - b|) / 2。

代码略。

Java程序员面试笔试宝典-数据结构与算法(四)相关推荐

  1. 程序员面试必备——《Java程序员面试笔试宝典》pdf

    <Java程序员面试笔试宝典>pdf 链接: https://pan.baidu.com/s/1uqxvY55jFOEAAzcn-pNVSQ 提取码: fety 1 内容简介 · · · ...

  2. Java程序员面试笔试宝典-Java基础知识(一)

    本文内容基于<Java程序员面试笔试宝典>,何昊.薛鹏.叶向阳著. 1. 基本概念 1.1 Java语言有哪些优点? 1.2 Java与C++有什么异同? 1.3 为什么需要public ...

  3. Java程序员面试笔试宝典刷题总结~11

    虽然申请博客已经有一段时间了,却是第一次写博客,有点激动,不知道该写些什么,刚好大三老学姐正值找实习工作之际,每天都会刷一点题,现在在看Java程序员面试笔试宝典一书,刚好把里面的题每天总结5道,写在 ...

  4. Java程序员面试笔试宝典-Java Web(二)

    本文内容基于<Java程序员面试笔试宝典>,何昊.薛鹏.叶向阳著. 1. Servlet与JSP 1.1 页面请求的工作流程是怎样的? 1.2 HTTP中GET与POST方法有什么区别? ...

  5. 读书笔记-Java程序员面试笔试宝典--持续更新中

    文章目录 第四章 Java基础知识 4.1 基础概念 4.2 面向对象技术 4.3 关键字 4.4 基本类型与运算 4.5 字符串与数组 4.6 异常处理 4.7 输入输出流 4.8 Java平台与内 ...

  6. Java程序员面试笔试宝典

    1. 基本概念 1.1 Java语言有哪些优点? Java为纯面向对象的语言: 平台无关性: Java提供了很多内置的类库: 提供了对Web应用开发的支持: 具有较好的安全性(数组边界检测和Bytec ...

  7. Java接口有时有结果 有时没有_《Java程序员面试笔试宝典》之为什么Java中有些接口没有任何方法...

    由于Java不支持多重继承,即一个类只能有一个父类,为了克服单继承的缺点,Java语言引入了接口这一概念.接口是抽象方法定义的集合(接口中也可以定义一些常量值),是一种特殊的抽象类.接口中只包含方法的 ...

  8. Java程序员面试笔试宝典答案,基于HTML

    <p> 截至2012年,石埇镇有耕地面积12021亩,人均0.61亩:林地面积共计26344亩. </p><p><b> 矿藏资源</b>&l ...

  9. java程序员面试笔试宝典8.3排序

    1如何进行选择排序 1)对于给定的一组纪录,经过第一轮比较得到最小的纪录将该纪录与第一个纪录的位置进行交换. 2)接着对不包括第一个纪录以外的其他纪录进行第二轮比较,得到最小的纪录并与第二个纪录进行位 ...

最新文章

  1. Asic设计参考工具与参考文档
  2. Difference Between HashMap and IdentityHashMap--转
  3. 为服务器端控件添加js.net
  4. Oracle全库导入表报错 ORA-39126
  5. 使用IntelliJ书签
  6. 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)...
  7. 40幅五彩缤纷的秋天风景摄影作品欣赏(上篇)
  8. 将您的SQL Server工作负载迁移到PostgreSQL –第4部分
  9. Python稳基修炼的经典案例3(计算机二级、初学者必须掌握的例题)
  10. 多标签分类与BCELoss
  11. C语言 链表 3个结点,一个关于C语言链表头结点的问题
  12. LINUX Telepresence编译详细过程记录
  13. java future 设计模式_转多线程设计模式 - Future模式之JAVA原生实现
  14. xampp修改mysql默认端口需要修改的地方
  15. wi7计算机桌面删除,如何删除win7系统桌面IE图标|win7删除桌面IE图标的方法
  16. 倡议书格式范文_倡议书的格式范文
  17. 【GitHub上传文件夹:bug】 ! [rejected] master - master (non-fast-forward)
  18. 网络攻防——黛蛇蠕虫病毒
  19. 【独家】防不胜防!默认勾选复燃,看刚上市的「同程艺龙」作恶勇气和相悖价值观 || 新芒X...
  20. ROS教程:视觉传感器使用漫谈

热门文章

  1. 什么是JDK、JRE?
  2. opencv2_蓝屏抠图(图像相减并去噪)
  3. 转:MFC之COleVariant
  4. React学习:状态(State) 和 属性(Props)
  5. 计算机组成原理(第三版)唐朔飞-第七章指令系统-课后习题
  6. jsp String 转 int 方法
  7. 无外网主机漏洞怎么破
  8. 【蓝桥杯】真题训练 2013年C++B组 题4 黄金连分数
  9. 夏普IGZO-LCD科普:从底层谈IGZO
  10. RStudio安装包时报错:Package LibPath Version Priority Depends Imports LinkingTo Suggests Enhances OS_