本文内容基于《Java程序员面试笔试宝典》,何昊、薛鹏、叶向阳著。
1. 链表
1.1 如何实现单链表的增删操作?
public class MyLinkedList {
private class Node {
public Object data;
public Node next;
public Node(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
private Node root;
@Override
public 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;
}
//两个引用同时前移,直到先前移的节点为null
while (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 如何检测一个链表是否有环?
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.print