《剑指offer升级打怪》--链表(上)
写在前面:
本人入门,以下纯属学习记录,若有错误请指正,还有代码有些是参考的。今天凌晨了(估计是疯了,MD明天怎么上班),来刷一篇,攒了好久了,刷剑指offer,刷的蛋疼。下面总结下其中关于链表的题目。
题目有:
画个图显得题目好多
这里说明一下,我个人是个彩笔,有些代码是从网上的参考(你要说抄袭,这个我承认。)
先来看个简单的:
输入一个链表,输出该链表中倒数第k个结点。
代码如下:
public class FindKthToTailSol {
public ListNode FindKthToTail(ListNode head, int k) {
Map<Integer, ListNode> map = new HashMap<>();
int index = 0;
while (head != null) {
map.put(index, head);
index++ ;
head = head.next;
}
if (k > index || k <= 0) {
return null;
}
return map.get(index - k);
}
@Test
public void test() {
ListNode node6 = new ListNode(5, null);
ListNode node5 = new ListNode(4, node6);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(3, node3);
ListNode node1 = new ListNode(2, node2);
ListNode head = new ListNode(1, node1);
System.out.println("看下结果:" + FindKthToTail(head, 8).val);
}
}
再来看个类似的:
输入一个链表,从尾到头打印链表每个节点的值。
代码如下:
public class PrintListFromEndToFront {
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.push(listNode.val);
listNode = listNode.next;
}
while (!stack.isEmpty()) {
list.add(stack.pop());
}
for (Integer integer : list) {
System.out.println(integer);
}
return list;
}
@Test
public void test() {
// {1,2,3,3,4,4,5}
// ListNode node7 = new ListNode(7, null);
ListNode node6 = new ListNode(5, null);
ListNode node5 = new ListNode(4, node6);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(3, node3);
ListNode node1 = new ListNode(2, node2);
ListNode head = new ListNode(1, node1);
System.out.println("sss " + printListFromTailToHead(head));
}
}
为什么类似,因为在我的解法里,我都借助了其他的容器来完成的,前者用的是map(用list是一样的,我第一次写就是用list做的),后者用的是个stack。
有了前面这两道题做铺垫,来看个装逼的题目,我第一次看到就想,这TM什么题目。不过我自己做出来了,我都做出来了,看官就不必谦虚了。
题目:
一个链表中包含环,请找出该链表的环的入口结点。
代码:
public class EntryNodeOfLoop {
public ListNode entryNodeOfLoop(ListNode pHead) {
// 错误原因是:当出现重复元素时,不能作为环
// List<Integer> list = new ArrayList<>();
List<ListNode> list = new ArrayList<>();
//
while (pHead != null) {
if (list.contains(pHead)) {
return pHead;
}
else {
list.add(pHead);
pHead = pHead.next;
}
}
return null;
}
@Test
public void test() {
ListNode node6 = new ListNode(5, null);
ListNode node5 = new ListNode(4, node6);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(3, node3);
ListNode node1 = new ListNode(2, node2);
ListNode head = new ListNode(1, node1);
entryNodeOfLoop(head);
}
}
有了前面两到题,这道题代码看起来确实很简单,关键点在于java的List.contain()方法应用。
看到我的注释,看官就应该知道下个题目了:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
代码:
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
// 创建一个头结点
ListNode firstNode = new ListNode(-1);
// 将pHead结点赋值给头结点的下一个结点first.next
firstNode.next = pHead;
// 创建一个last结点,此处考察的关键点是对last进行修改,first结点是否也跟着修改
ListNode lastNode = firstNode;
// 外层循环控制条件,当pHead!=null&&pHead.next!=null
while (pHead != null && pHead.next != null) {
// 判断当前结点Val与其下个结点的Val是否相等
if (pHead.val == pHead.next.val) {
int dupNodeVal = pHead.val;
while (pHead != null && pHead.val == dupNodeVal) {
pHead = pHead.next;
}
lastNode.next = pHead;
}
else {
lastNode = pHead;
pHead = pHead.next;
}
}
return firstNode.next;
}
}
这道题我没做出来,我没做出来,我没做出来。关键点:在于内层的while循环的使用,经典。为什么后面会总结。
再来看个装逼的题目:
输入两个链表,找出它们的第一个公共结点。
代码:
public class FindFirstCommonNode {
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
List<ListNode> list = new ArrayList<>();
while (pHead1 != null) {
list.add(pHead1);
pHead1 = pHead1.next;
}
while (pHead2 != null) {
if (list.contains(pHead2)) {
System.out.println(pHead2.val);
return pHead2;
}
pHead2 = pHead2.next;
}
return null;
}
@Test
public void test() {
ListNode node7 = new ListNode(7, null);
ListNode node6 = new ListNode(3, node7);
ListNode node5 = new ListNode(2, node6);
ListNode node4 = new ListNode(-2, node5);
ListNode node1 = new ListNode(7, node5);
ListNode head = new ListNode(1, node1);
findFirstCommonNode(head, node4);
}
}
惊不惊喜,意不意外。
再看个题目:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
代码:
public class MergeList {
public ListNode Merge(ListNode list1, ListNode list2) {
ListNode firstNode = null;
ListNode tmp = null;
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
if (firstNode == null) {
firstNode = tmp = list1;
}
else {
tmp.next = list1;
tmp = tmp.next;
}
list1 = list1.next;
}
else {
if (firstNode == null) {
firstNode = tmp = list2;
}
else {
tmp.next = list2;
tmp = tmp.next;
}
list2 = list2.next;
}
}
if (list1 == null) {
tmp.next = list2;
}
else {
tmp.next = list1;
}
return firstNode;
}
@Test
public void test() {
ListNode node6 = new ListNode(56, null);
ListNode node5 = new ListNode(23, node6);
ListNode node4 = new ListNode(18, node5);
ListNode node3 = new ListNode(9, node4);
ListNode node2 = new ListNode(8, node3);
ListNode node1 = new ListNode(5, node2);
ListNode head = new ListNode(1, node1);
ListNode node7 = new ListNode(19, null);
ListNode node8 = new ListNode(17, node7);
ListNode node9 = new ListNode(16, node8);
ListNode node10 = new ListNode(14, node9);
ListNode node11 = new ListNode(12, node10);
ListNode node12 = new ListNode(10, node11);
ListNode head1 = new ListNode(0, node12);
ListNode firstNode = Merge(head, head1);
while (firstNode != null) {
System.out.println(firstNode.val);
firstNode = firstNode.next;
}
}
}
这个是数据结构书的原题,放到这儿主要是和重复结点的那道题一起看下。感觉还有有些联系的。
今天最后一道,链表反转。
题目:
输入一个链表,反转链表后,输出链表的所有元素。
先给出优秀的代码:
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return null;
ListNode newHead = null;
ListNode pNode = head;
ListNode pPrev = null;
while(pNode!=null){
ListNode pNext = pNode.next;
if(pNext==null)
newHead = pNode;
pNode.next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return newHead;
}
}
反正我没想出来这么写。我写的比较烂,此处略。这道题目不知道该如何总结。在下篇文章会结合剩下的几道题目总结一下。
_______________________________________________________________________________________________________________
分割线表示更新:
先更新一个题目答案,后来总结又写的一种答案(这是之前的思路,代码保留了,总结的时候又反思当时为什么没写出来,发现自己对题目的理解有误,包括对java对象的理解也有欠缺的地方,后面会指出之前的错误认识):
两个链表的第一个公共结点
先更新下解法:
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
while (pHead1 != null) {
ListNode tmp = pHead2;
while (tmp != null) {
if (pHead1.val == tmp.val && pHead1.next == tmp.next) {
System.out.println(pHead1.val);
return pHead1;
}
else {
tmp = tmp.next;
} ;
}
pHead1 = pHead1.next;
}
return null;
}
@Test
public void test() {
ListNode node7 = new ListNode(7, null);
ListNode node6 = new ListNode(3, node7);
ListNode node5 = new ListNode(2, node6);
ListNode node4 = new ListNode(-2, node5);
ListNode node1 = new ListNode(7, node5);
ListNode head = new ListNode(1, node1);
findFirstCommonNode(head, node4);
}
下面记录自己的纠正错误的过程及新的发现:
修改前的代码:
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
List<ListNode> list = new ArrayList<>();
while (pHead1 != null) {
list.add(pHead1);
pHead1 = pHead1.next;
}
while (pHead2 != null) {
if (list.contains(pHead2)) {
System.out.println(pHead2.val);
return pHead2;
}
pHead2 = pHead2.next;
}
return null;
}
@Test
public void test() {
ListNode node7 = new ListNode(7, null);
ListNode node6 = new ListNode(3, node7);
ListNode node5 = new ListNode(2, node6);
ListNode node4 = new ListNode(-2, node5);
ListNode node3 = new ListNode(4, null);
ListNode node2 = new ListNode(3, node3);
ListNode node1 = new ListNode(2, node2);
ListNode head = new ListNode(1, node1);
findFirstCommonNode(node1, node4);
}
这里为什么要修改,是因为一旦两个链表出现公共结点后,则其next结点也是相同,其next之后的结点也都是相同。这里的相同是指,两个引用指向了同一块儿内存区域。
我个人给出公共结点的公式:
node1.val=node2.val
node1.next=node1.next
图示如下:
我最初的理解如下:
还有中错误理解这里也贴出来:
这种理解是错误的,因为在实际的JVM存储空间上,node33,node43是两块内存区,而不是仅仅指这两个结点的val是相等,下面展示下更有说服力的代码:
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
while (pHead1 != null) {
ListNode tmp = pHead2;
while (tmp != null) {
if (pHead1.val == tmp.val && pHead1.next == tmp.next) {
System.out.println(pHead1.val);
return pHead1;
}
else {
tmp = tmp.next;
} ;
}
pHead1 = pHead1.next;
}
return null;
}
@Test
public void test() {
ListNode node7 = new ListNode(7, null);
ListNode node6 = new ListNode(3, node7);
ListNode node5 = new ListNode(2, node6);
ListNode node4 = new ListNode(-2, node5);
ListNode node1 = new ListNode(7, node5);
ListNode head = new ListNode(1, node1);
findFirstCommonNode(head, node4);
}
@Test
public void test1() {
ListNode node24 = new ListNode(4, null);
ListNode node23 = new ListNode(3, node24);
ListNode node22 = new ListNode(9, node23);
ListNode node21 = new ListNode(8, node22);
ListNode node14 = new ListNode(4, null);
ListNode node13 = new ListNode(3, node14);
ListNode node12 = new ListNode(2, node13);
ListNode node11 = new ListNode(1, node12);
System.out.println(node23 == node13);
findFirstCommonNode(node11, node21);
}
@Test
public void test3() {
ListNode node24 = new ListNode(4, null);
ListNode node23 = new ListNode(3, node24);
ListNode node22 = new ListNode(9, node23);
ListNode node21 = new ListNode(8, node22);
ListNode node14 = new ListNode(4, null);
ListNode node13 = node23;
ListNode node12 = new ListNode(2, node13);
ListNode node11 = new ListNode(1, node12);
System.out.println(node23 == node13);
findFirstCommonNode(node11, node21);
}
上面的输出:
false
4
true
3
很直观的看到,不同之处吧。test1中的代码,就是new 了node13和node23,他两不相等,test3中的测试代码node13和node23就是相等的。
图示:
这个问题,需要客官自己再查询关于java对象引用及new的相关知识结合理解。
如果看官理解上面的题目,接下来再来更新一道题目,会用到我们上面所说的内容。
题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),
返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
客官可以自己差试一下,单链表怎么写?
代码如下:
public RandomListNode clone(RandomListNode pHead) {
if (pHead == null) {
return null;
}
RandomListNode node = new RandomListNode(pHead.label);
node.next = pHead.next;
node.random = pHead.random;
pHead = pHead.next;
RandomListNode tmp3 = node;
while (pHead != null) {
RandomListNode node2 = new RandomListNode(pHead.label);
node2.next = pHead.next;
node2.random = pHead.random;
tmp3.next = node2;
tmp3 = node2;
pHead = pHead.next;
}
return node;
}
@Test
public void test() {
RandomListNode node6 = new RandomListNode(5, null, null);
RandomListNode node5 = new RandomListNode(4, node6, null);
RandomListNode node4 = new RandomListNode(4, node5, null);
RandomListNode node3 = new RandomListNode(3, node4, null);
RandomListNode node2 = new RandomListNode(3, node3, node6);
RandomListNode node1 = new RandomListNode(2, node2, null);
RandomListNode head = new RandomListNode(1, node1, node5);
System.out.println("看下结果:" + clone(head).label);
}
我个人就是先写单链表的,用自己能写出来的解法写的,后来一步步优化到这版的。这个题目有点儿,装逼的地方就是,
复杂链表。其实就是多了个属性,我才不管你是个啥。(可能是大多数人会使用C来操作,所以可能心里就会抵触,放弃了,我之前难道这个玩意儿,也是这TM是什么玩意儿)
public RandomListNode clone(RandomListNode pHead) {
if (pHead == null) {
return null;
}
RandomListNode node = new RandomListNode(pHead.label);
node.next = pHead.next;
node.random = pHead.random;
pHead = pHead.next;
Map<Integer, Map<String, Object>> map = new HashMap<>();
String next = "next";
String label = "label";
String random = "random";
int index = 0;
while (pHead != null) {
HashMap<String, Object> map2 = new HashMap<>();
map2.put(next, pHead.next);
map2.put(label, pHead.label);
map2.put(random, pHead.random);
map.put(index, map2);
index++ ;
pHead = pHead.next;
}
RandomListNode tmp3 = node;
for (int i = 0; i < index; i++ ) {
RandomListNode node2 = new RandomListNode((Integer)map.get(i).get(label));
node2.next = (RandomListNode)map.get(i).get(next);
node2.random = (RandomListNode)map.get(i).get(random);
tmp3.next = node2;
tmp3 = node2;
}
return node;
}
个人在使用第三方结合,屡试屡爽,所以先用这个写一个版本出来,发现两个优化点:
第一次尝试优化了for循环:
第一个for循环将链表的结点取出来,连接方式没有改变,放到map里。
第二个for循环干的事:利用for循环将其中的取出来,
我靠,先放进去再取出来,所以将其归并到一个for循环中。
第二次将map优化掉:
map.get(key)在某种意义上讲等价于Object.attribute。而且比map厉害的是,放入map中的key不能重复啊,但是对象用来取值,是没有这个问题的。
最大的心得,学会使用debug,一步步跟踪代码,最重要的是:
(ps:更于2017.12.22 凌晨1.42。困死我了)