文章目录
#看前须知:
链表相对数组而言,他的访问没有那么灵活,只能通过指针一个一个的访问,说所以他的算法题都不是很抽象。
以下是后面链表的结点类型:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
铁子,如果你还不会用Java实现链表,先看看这个吧,有基础的话,十分钟搞定!
如果你没有用C实现过链表,或者你对链表的基本概念和理论不清楚,那就从这里看起吧!
1.从尾到头打印链表
题目
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
答案
(1)采用额外空间栈:
栈的功能就是先进后出,很适合这道题,但是要使用额外空间,一般情况下,使用额外空间的方法。
/**
* 用额外空间栈
* @param listNode
* @return
*/
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<Integer>();
Stack<Integer> stack = new Stack<Integer>();
ListNode next = listNode;
while(next != null) {
stack.add(next.val);
next = next.next;
}
while(!stack.isEmpty()) {
/*
* pop()方法:移除栈顶的元素,并将该元素作为方法的返回值
* peek()方法:查看栈顶的元素,但是不会将其删除
*/
res.add(stack.pop());
}
return res;
}
(2)将链表反转,然后再打印:
画个链表,跟着算法走一遍,就很容易理解,很简单的。
/**
* 反转一个链表
* @param listNode
* @return
*/
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<Integer>();
ListNode head = listNode;
ListNode pre = null;
ListNode next = null;
while(head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
while(pre != null) {
res.add(pre.val);
pre = pre.next;
}
return res;
}
(3)采用递归的方式:
递归的工作原理就是,先从最外面一层开始,一层一层的扒拉进去,找到最里层递归结束的判断条件,再从最里层一层一层的退出来,所以用在这里再合适不过了。
/**
* 采用递归的方式
*/
ArrayList<Integer> result = new ArrayList<Integer>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode != null) {
if(listNode.next != null)
printListFromTailToHead(listNode.next);
result.add(listNode.val);
}
return result;
}
2.链表中倒数第k个结点
题目:
输入一个链表,输出该链表中倒数第k个结点。
答案:
思路:设置两个指针,让一个指针比另一个指针快k
个结点,当那个快的指针为空时,慢的那个指针所指的位置,就是倒数第k个结点。
但是这个题要注意一个点,链表的长度是事先无法知道的,要设置一个标志位,对传入的值k
作分析,如果k的值大于链表本身的长度,要返回null
。
/**
* 输出链表的倒数第k个结点
* @param head
* @param k
* @return
*/
public ListNode FindKthToTail(ListNode head,int k) {
ListNode next = head;
ListNode pre =head;
int sign = 0;//这个变量就是为了判断 k值是否大于链表长度
while(next != null) {
next = next.next;
if(sign < k)
sign++;
else
pre = pre.next;
}
if(sign == k)
return pre;
return null;
}
3.反转链表
题目:
输入一个链表,反转链表后,输出新链表的表头
答案:
很简单的,跟着下面这个图捋一遍,想想为什么要用temp != null
作为循环条件
/**
* 反转链表
* @param head
* @return
*/
public ListNode ReverseList(ListNode head) {
ListNode temp = head;
ListNode pre = null;
ListNode next = null;
while(temp != null) {
next = temp.next;
temp.next = pre;
pre = temp;
temp = next;
}
return pre;
}
4.合并两个排序链表
题目
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
答案
1)非递归实现:
如果这里有值得一说的地方,那就是:可能返回的头结点不好处理,可以新建一个节点,用它的next指向合并好的链表的头结点,就很奈斯,跟着代码走一边,很好理解,很简单。
/**
* 合并两个有序链表
* @param list1
* @param list2
* @return
*/
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
return list1;
ListNode head = new ListNode(0);
ListNode pre = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
pre.next = list1;
list1 = list1.next;
pre = pre.next;
} else {
pre.next = list2;
list2 = list2.next;
pre = pre.next;
}
}
if (list1 != null)
pre.next = list1;
if (list2 != null)
pre.next = list2;
return head.next;
}
2)递归实现
图片来源于网络,侵删!!!
按照图图上面理解,思路很清晰了,我根本没有想到还能这么写,简单明了。
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
5.删除链表中重复的结点
题目
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
答案
思路:在注释里!!!看图跟着注释和代码走一遍,很简单的。
public ListNode deleteDuplication(ListNode pHead) {
if(pHead == null)
return null;
//新建一个结点,让他作为原链表的头结点
ListNode newHead = new ListNode(0);
newHead.next = pHead; //让新链表和原链表相连
//新建一个变量pre,让他记录不被删除的结点,最开始让他和newHead指向同一空间
ListNode pre = newHead;
//suf的作用,就是一一访问链表中的每个结点
ListNode suf = pHead;
while(suf != null && suf.next != null) { //因为suf每次都要和他的下一个结点比较,
// 所以这里的条件设置两个,而且要注意两者的顺序,不能颠倒
if(suf.val == suf.next.val) { //当suf和他的下一个结点相等的时候
int val = suf.val; //记录下val的值
while(suf != null && suf.val == val) {
suf = suf.next; //suf向后滑,知道suf指向值不等于val的结点
}
/*如果链表的第一个结点和后面的结点值相等
* 这里pre.next 就相当于 newhead.next
*/
pre.next = suf;
}else {//当suf和他的下一个结点不相等的时候
//suf向后滑,pre紧跟其后
pre = suf;
suf = suf.next;
}
}
//这里返回newHead.next也就相当于返回新链表的头结点
return newHead.next;
}
6.复杂链表的复制
题目
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
结点类型:
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
答案
思路:看图说话。
最初我没有想到这种方法,看到大佬的这张图就恍然大悟,然后看着图片自己写代码,感觉一切都OK,但是ac的时候,报错空指针,然后看了大佬的代码,发现人家有两处判断空指针,我没有。
图片来源于网络,侵必删!!!
public RandomListNode Clone(RandomListNode pHead) {
if(pHead == null)
return null;
RandomListNode p = pHead;
//1.复制每个结点,让新结点跟在被复制结点的后面
while(p != null) {
RandomListNode newNode = new RandomListNode(p.label);
newNode.next = p.next;
p.next = newNode;
p = p.next.next;
}
//2.重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
p = pHead;
while(p != null) {
//老表,关键点在这一步的空指针的判断
p.next.random = p.random == null ? null : p.random.next;
p = p.next.next;
}
//3.拆分链表,将链表拆分为原链表和复制后的链表
p = pHead;
RandomListNode copyHead = p.next;
RandomListNode np = p.next;
while(p != null) {
p.next = np.next;
p = p.next;
//这一步和上一个空指针的判断一样重要,少一个都不行
np.next = p == null ? null : p.next;
np = np.next;
}
return copyHead;
}
7.两个链表的第一个公共结点
题目
输入两个链表,找出它们的第一个公共结点。
答案
思路:
两个链表公共结点之后,就是共用一个链表,所以长度是相等的。只要找出两个链表的长度差,让长的链表先向右滑,等滑到后面的长度和短链表的长度相等时,再将两个链表的同时向右滑,并且一一比较每个结点是否相同,如果第一个相同的结点出现,那这个结点就是,连个链表的公共结点。
/**
* 找到两个链表的公共结点
* @param pHead1
* @param pHead2
* @return
*/
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null)
return null;
// 想求出第一个链表的长度
int len1 = 0;
ListNode p1 = pHead1;
while (p1 != null) {
len1++;
p1 = p1.next;
}
// 在求出第二个链表的长度
int len2 = 0;
ListNode p2 = pHead2;
while (p2 != null) {
len2++;
p2 = p2.next;
}
int dif = len1 - len2;
p1 = pHead1;
p2 = pHead2;
if (dif > 0) {
while (dif > 0) {
p1 = p1.next;
dif--;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
} else {
while (dif < 0) {
p2 = p2.next;
dif++;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
}
8.链表中环的入口结点
题目
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
答案
图片来源于网络,侵必删!!!
思路:主要还是看人家大神的解法。
设置两个指针slow、fast,slow一次移动一步,fast一次移动两步,如果有环,这两个指针就一定会相遇。
在上图中,假设:slow和fast相遇点是Z,
则:
slow走过的路程为:a+b
fast走过的路程为:a+b+c+b
又因为路程在走的路程上:
2 slow = fast
所以:2*(a+b) = a+b+c+b => a = c
那么此时让slow回到起点,fast依然停在z,两个同时开始走,一次走一步
那么它们最终会相遇在y点,正是环的起始点。
/**
* 环的入口结点
*
* @param pHead
* @return
*/
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode slow = pHead;
ListNode fast = pHead;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
fast = pHead;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}