关于链表的常见算法题(一)
链表一直是面试最喜欢考的数据结构,灵活而又多变,代码往往也不是特别长。
从尾到头打印链表
输入一个链表的头节点,从尾到头反过来打印出每个节点的值
例如链表为1->2->3,打印出3 2 1
有三种思路可以参考:
- 栈:栈天然是先进后出的,在遍历链表时,把值按顺序放入栈中,最后出栈就是逆序了。
- 既然想到了栈,那么递归天然是一个栈结构,一样的,访问到一个节点,先递归输出它的后面节点,再输出自己,这样链表就反过来了
- 头插法:使用头插法可以得到一个逆序的链表。
- 1->2->3,加入一个头节点head,只用于操作中转,不存储数据
- 先把head->1,然后继续从2->3中取
- 变为head->2->1,最后变为head->3->2->1,从head.next开始输出就可以了
public class _06 {
static class ListNode {
int value;
public ListNode(int value) {
this.value = value;
}
ListNode next;
}
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
listNode1.next = listNode2;
listNode2.next = listNode3;
_06 test = new _06();
// List<Integer> list = test.printListFromQueue(listNode1);
// List<Integer> list = test.printListFromDG(listNode1);
List<Integer> list = test.printListFromTC(listNode1);
for (Integer i : list) {
System.out.println(i);
}
}
/**
* 栈
*/
public List<Integer> printListFromQueue(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.add(listNode.value);
listNode = listNode.next;
}
List<Integer> list = new ArrayList<>();
while (!stack.isEmpty()) {
list.add(stack.pop());
}
return list;
}
/**
* 递归
*/
public List<Integer> printListFromDG(ListNode listNode) {
List<Integer> list = new ArrayList<>();
if (listNode != null) {
list.addAll(printListFromDG(listNode.next));
list.add(listNode.value);
}
return list;
}
/**
* 头插法
*/
public List<Integer> printListFromTC(ListNode listNode) {
//头插法构建逆序链表
//新建一个头部节点,只用于中转,不存储值
ListNode head = new ListNode(-1);
//链表反转
while (listNode != null) {
ListNode temp = listNode.next;
listNode.next = head.next;
head.next = listNode;
listNode = temp;
}
List<Integer> list = new ArrayList<>();
//真正的值是从第一个节点开始
head = head.next;
while (head != null) {
list.add(head.value);
head = head.next;
}
return list;
}
}
复制代码
删除链表的节点
在O(1)的时间内删除链表节点
给定单身链表的头指针和一个节点指针,定义一个函数在O(1)内删除这个节点。
解题思路
-
如果这个节点不是尾节点,那么可以直接把下一个节点的值赋值给这个节点,再把这个节点指向下下个节点,时间复杂度为O(1)
如,1->2->3->4,要删除的是2,那么先把2的下一个节点值3,赋值给2的节点,变为1-3-3-4,然后把第二个节点的位置,指向下下个节点4,这样,就变为了1-3-4,2这个节点这删除了。
-
如果是尾指针,那么找到前一个节点,把前一个节点指向null,时间复杂度为O(N)
如果进行N次操作,这个算法的平均时间复杂度为O(1)
/**
* 删除指定节点
*/
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
if (head == null || tobeDelete == null) {
return null;
}
if (tobeDelete.next != null) {
//要删除的不是尾节点
ListNode next = tobeDelete.next;
tobeDelete.value = next.value;
tobeDelete.next = next.next;
} else {
if (head == tobeDelete) {
head = null;
} else {
ListNode curNode = head;
while (curNode.next != tobeDelete) {
curNode = curNode.next;
}
curNode.next = null;
}
}
return head;
}
复制代码
删除链表中重复的节点
在一排序的链表中,如何删除重复的节点
1->2->2->3->3->3->4
1->4
解题思路
- 头节点一样有可能重复
- 遍历整个链表,如果当前节点与next的下一个节点相同,则都可以删除,然后把当前节点的前一个节点与下下个节点相连,我们必须保证前一个节点是无一个无重复的节点相连,这一想就是递归~~
/**
* 删除重复的节点
*/
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
ListNode next = pHead.next;
//当头节点有重复时,一直找到没有重复的为止
if (pHead.value == next.value) {
while (next != null && pHead.value == next.value) {
next = next.next;
}
return deleteDuplication(next);
} else {
pHead.next = deleteDuplication(pHead.next);
return pHead;
}
}
复制代码
链表中倒数第K个节点
输入一个单向链表,输出这个链表中倒数第k个节点
1->2->3->4->5->6
倒数第三个点为4
解题思路
这是一个单向链表,如果我们使用遍历,很明显不方便从尾开始遍历的,这时候我们可以想到的方法是双指针法。
- 定义两个指针p1和p2,p2先不动,p1走k-1步,这时p1指向3,p2指向1
- 现在p1和p2同时向后遍历,当p1遍历到6,它的next为null时,p2所指向的值就是我们所要的值
public ListNode FindKthToTail(ListNode head, int k) {
if (head == null) {
return null;
}
ListNode p1 = head;
while (p1 != null && k > 0) {
p1 = p1.next;
k--;
}
if (k > 0) {
return null;
}
ListNode p2 = head;
while (p1 != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
复制代码
反转链表
解题思路
-
头插法(迭代)
- 前面其实已经用到了,使用一个头head,只做中转,而不存储数据。
-
递归
分解成最简单的两个节点,就next.next = head,这样就能把1->2反转成2->1。1的next是2,它的next指向1,就已经完成了2->1
public ListNode reverseList(ListNode head){
if (head==null||head.next==null){
return head;
}
ListNode next = head.next;
head.next = null;
ListNode newHead = reverseList(next);
next.next = head;
return newHead;
}
public ListNode reverseList2(ListNode head){
if (head==null||head.next==null){
return head;
}
ListNode newList = new ListNode(-1);
while (head!=null){
ListNode next = head.next;
head.next = newList.next;
newList.next = head;
head = next;
}
return newList.next;
}
复制代码
合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新的链表中的节点仍然是递增的。
1-3-5-7
2-4-6-8
1-2-3-4-5-6-7-8
解题思路
首先我们要比较两个链表头,发现1比2小,那么新链表的头就是1,然后继续比较,这时还是比较链表头,我们想到了,这就是个递归啊,把头继续比较,依次加进新的链表中,所以本题可以用递归来解,当然也能使用迭代。
/**
* 递归
*/
public ListNode merge(ListNode node1, ListNode node2) {
if (node1 == null) {
return node2;
}
if (node2 == null) {
return node1;
}
if (node1.value < node2.value) {
node1.next = merge(node1.next, node2);
return node1;
} else {
node2.next = merge(node1, node2.next);
return node2;
}
}
/**
* 迭代
*/
public ListNode merge2(ListNode node1, ListNode node2) {
ListNode head = new ListNode(-1);
ListNode cur = head;
while (node1 != null && node2 != null) {
if (node1.value <= node2.value) {
cur.next = node1;
node1 = node1.next;
} else {
cur.next = node2;
node2 = node2.next;
}
cur = cur.next;
}
if (node1 != null) {
cur.next = node1;
}
if (node2 != null) {
cur.next = node2;
}
return head.next;
}
复制代码
两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点
1-2-3-6-7
4-5-6-7
解题思路
-
暴力破解法
在第一个链表上顺序遍历到一个节点,就到第二个链表上遍历每一个节点,如果有一个节点与这个值相等,就找到了。但是时间复杂度为O(mn)
-
栈
我们发现,公共部分一定是在尾部,如果我们从后面开始找会更快,最后一个相同点就是我们要找的点,不过这是单向链表,所以我们想到了栈的结构——后进先出
-
求差法
其实我们用栈,只是为了想同时能到达两个链表的尾节点。当两链表长度不同时,我们从头开始遍历,到后面的时间就不一致。我们可以先遍历两个链表,得到它们的长度,然后,链表长的先走先走多的几个节点,接着同时在两个链表遍历,找到的第一个相同点就是我们要找的第一个公共点
/**
* 用栈取
*/
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
//分别放入栈中
while (pHead1 != null) {
stack1.push(pHead1);
pHead1 = pHead1.next;
}
while (pHead2 != null) {
stack2.push(pHead2);
pHead2 = pHead2.next;
}
ListNode result = null;
//从栈顶取值,取到不相等时,上一个就是第一个公共节点
while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.peek()) {
stack1.pop();
result = stack2.pop();
}
return result;
}
/**
* 差值法
*/
public ListNode findFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
int count1 = 1;
int count2 = 1;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
//获取两个链表的长度
while (p1.next != null) {
p1 = p1.next;
count1++;
}
while (p2.next != null) {
p2 = p2.next;
count2++;
}
//哪个长就先走差值步
if (count1 > count2) {
int dif = count1 - count2;
while (dif != 0) {
pHead1 = pHead1.next;
dif--;
}
} else {
int dif = count2 - count1;
while (dif != 0) {
pHead2 = pHead2.next;
dif--;
}
}
//同时走,当相等时得到公共节点
while (pHead1 != null && pHead2 != null) {
if (pHead1 == pHead2) {
return pHead1;
}
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
return null;
}
复制代码
By《剑指offer》
下面是我的公众号,欢迎大家关注我
转载于:https://juejin.im/post/5d067f4ee51d4577596486fb