首先,我们来定义一个链表的数据结构,如下
1 public class Link { 2 private int value; 3 private Link next; 4 public void set_Value(int m_Value) { 5 this.value = m_Value; 6 } 7 public int get_Value() { 8 return value; 9 }10 public void set_Next(Link m_Next) {11 this.next = m_Next;12 }13 public Link get_Next() {14 return next;15 }16 }
有了这个数据结构后,我们需要一个方法来生成和输出链表,其中链表中每个元素的值采用的是随机数。
生成链表的代码如下:
1 public static Link init(int count, int maxValue) 2 { 3 Link list = new Link(); 4 Link temp = list; 5 Random r = new Random(); 6 temp.set_Value(Integer.MIN_VALUE); 7 for (int i = 0; i < count; i++) 8 { 9 Link node = new Link();10 node.set_Value(r.nextInt(maxValue));11 temp.set_Next(node);12 temp=node;13 }14 temp.set_Next(null);15 return list;16 }17 18 public static Link init(int count)19 {20 return init(count, Integer.MAX_VALUE);21 }
对于链表的头结点,我们是不存储任何信息的,因此将其值设置为Integer.MIN_VALUE。我们重载了生成链表的方法。
下面是打印链表信息的方法:
1 public static void printList(Link list) 2 { 3 if (list == null || list.get_Next() == null) 4 { 5 System.out.println("The list is null or empty."); 6 return; 7 } 8 Link temp = list.get_Next(); 9 StringBuffer sb = new StringBuffer();10 while(temp != null)11 {12 sb.append(temp.get_Value() + "->");13 temp=temp.get_Next();14 }15 System.out.println(sb.substring(0, sb.length() - 2));16 }
好了,有了以上这些基础的方法, 我们就可以深入探讨链表相关的面试题了。
-
- 链表反转思路:有两种方法可以实现链表反转,第一种是直接循环每个元素,修改它的Next属性;另一种是采取递归的方式。首先来看直接循环的方式:
1 public static Link Reverve(Link list) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) 4 { 5 System.out.println("list is null or just contains 1 element, so do not need to reverve."); 6 return list; 7 } 8 Link current = list.get_Next(); 9 Link next = current.get_Next();10 current.set_Next(null);11 while(next != null)12 {13 Link temp = next.get_Next();14 next.set_Next(current);15 current = next;16 next = temp;17 }18 list.set_Next(current);19 20 return list;21 }
然后是递归方式:
1 public static Link RecursiveReverse(Link list) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) 4 { 5 System.out.println("list is null or just contains 1 element, so do not need to reverve."); 6 return list; 7 } 8 9 list.set_Next(Recursive(list.get_Next()));10 11 return list;12 }13 14 15 private static Link Recursive(Link list)16 {17 if (list.get_Next() == null)18 {19 return list;20 }21 Link temp = Recursive(list.get_Next());22 list.get_Next().set_Next(list);23 list.set_Next(null);24 25 return temp;
输出指定位置的元素(倒数第N个元素)
思路:采用两个游标来遍历链表,第1个游标先走N步,然后两个游标同时前进,当第一个游标到最后时,第二个游标就是想要的元素。1 public static Link find(Link list, int rPos) 2 { 3 if (list == null || list.get_Next() == null) 4 { 5 return null; 6 } 7 int i = 1; 8 Link first = list.get_Next(); 9 Link second = list.get_Next();10 while(true)11 {12 if (i==rPos || first == null) break;13 first = first.get_Next();14 i++;15 }16 if (first == null)17 {18 System.out.println("The length of list is less than " + rPos + ".");19 return null;20 }21 while(first.get_Next() != null)22 {23 first = first.get_Next();24 second = second.get_Next();25 }26 27 return second;28 }
删除指定节点
思路:可以分情况讨论,如果指定节点不是尾节点,那么可以采用取巧的方式,将指定节点的值修改为下一个节点的值,将指定节点的Next属性设置为Next.Next;但如果指定节点为尾节点,那么只能是从头开始遍历。1 public static void delete(Link list, Link element) 2 { 3 if (element.get_Next() != null) 4 { 5 element.set_Value(element.get_Next().get_Value()); 6 element.set_Next(element.get_Next().get_Next()); 7 } 8 else 9 {10 Link current = list.get_Next();11 while(current.get_Next() != element)12 {13 current = current.get_Next();14 }15 current.set_Next(null);16 }17 }
删除重复节点
思路:采用hashtable来存取链表中的元素,遍历链表,当指定节点的元素在hashtable中已经存在,那么删除该节点。1 public static void removeDuplicate(Link list) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) return; 4 Hashtable table = new Hashtable(); 5 Link cur = list.get_Next(); 6 Link next = cur.get_Next(); 7 table.put(cur.get_Value(), 1); 8 while(next != null) 9 {10 if (table.containsKey(next.get_Value()))11 {12 cur.set_Next(next.get_Next());13 next = next.get_Next(); 14 }15 else16 {17 table.put(next.get_Value(), 1);18 cur= next;19 next = next.get_Next();20 }21 22 } 23 }
寻找链表中间节点
思路:采用两个游标的方式,第一个游标每次前进两步,第二个游标每次前进一步,当第一个游标到最后时,第二个游标就是中间位置。需要注意的是,如果链表元素的个数是偶数,那么中间元素应该是两个。1 public static void findMiddleElement(Link list) 2 { 3 if (list == null || list.get_Next() == null) return; 4 System.out.println("The Middle element is:"); 5 if (list.get_Next().get_Next() == null) 6 { 7 System.out.println(list.get_Next().get_Value()); 8 } 9 Link fast = list.get_Next();10 Link slow = list.get_Next();11 while(fast.get_Next() != null && fast.get_Next().get_Next() != null)12 {13 fast = fast.get_Next().get_Next();14 slow = slow.get_Next();15 }16 17 if (fast != null && fast.get_Next() == null)18 {19 System.out.println(slow.get_Value());20 }21 else22 {23 System.out.println(slow.get_Value());24 System.out.println(slow.get_Next().get_Value());25 }26 }
链表元素排序
思路:链表元素排序,有两种方式,一种是链表元素本身的排序,一种是链表元素值得排序。第二种方式更简单、灵活一些。1 public static void Sort(Link list) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) 4 { 5 return; 6 } 7 Link current = list.get_Next(); 8 Link next = current.get_Next(); 9 while(current.get_Next() != null)10 {11 while(next != null)12 {13 if (current.get_Value() > next.get_Value())14 {15 int temp = current.get_Value();16 current.set_Value(next.get_Value());17 next.set_Value(temp);18 }19 next = next.get_Next();20 }21 current = current.get_Next();22 next = current.get_Next();23 }24 }
判断链表是否有环,如果有,找出环上的第一个节点
思路:可以采用两个游标的方式判断链表是否有环,一个游标跑得快,一个游标跑得慢。当跑得快的游标追上跑得慢的游标时,说明有环;当跑得快的游标跑到尾节点时,说明无环。至于如何找出换上第一个节点,可以分两步,首先确定环上的某个节点,计算头结点到该节点的距离以及该节点在环上循环一次的距离,然后建立两个游标,分别指向头结点和环上的节点,并将距离平摊(哪个距离大,先移动哪个游标,直至两个距离相等),最后同时移动两个游标,碰到的第一个相同元素,就是环中的第一个节点。1 public static Link getLoopStartNode(Link list) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) 4 { 5 return null; 6 } 7 int m = 1, n = 1; 8 Link fast = list.get_Next(); 9 Link slow = list.get_Next();10 while(fast != null && fast.get_Next() != null)11 {12 fast = fast.get_Next().get_Next();13 slow = slow.get_Next();14 if (fast == slow) break;15 m++;16 }17 if (fast != slow)18 {19 return null;20 }21 Link temp = fast;22 while(temp.get_Next() != fast)23 {24 temp = temp.get_Next();25 n++;26 }27 Link node1 = list.get_Next();28 Link node2 = fast;29 if (m < n)30 {31 for (int i = 0; i < n - m; i++)32 {33 node2 = node2.get_Next();34 }35 }36 if (m > n)37 {38 for (int i = 0; i < m - n; i++)39 {40 node1 = node1.get_Next();41 }42 }43 while(true)44 {45 if (node1 == node2)46 {47 break;48 }49 node1 = node1.get_Next();50 node2 = node2.get_Next();51 }52 53 return node1;54 55 }
判断两个链表是否相交
思路:判断两个链表的尾节点是否相同,如果相同,一定相交1 public static boolean isJoint(Link list1, Link list2) 2 { 3 if (list1 == null || list2 == null || list1.get_Next() == null || list2.get_Next() == null) 4 { 5 return false; 6 } 7 Link node1 = list1; 8 Link node2 = list2; 9 while(node1.get_Next() != null)10 {11 node1 = node1.get_Next();12 }13 while(node2.get_Next() != null)14 {15 node2 = node2.get_Next();16 }17 18 return node1 == node2;19 }
合并两个有序链表
思路:新建一个链表,然后同时遍历两个有序链表,比较其大小,将元素较小的链表向前移动,直至某一个链表元素为空。然后将非空链表上的所有元素追加到新建链表中。1 public static Link merge(Link list1, Link list2) 2 { 3 Link list = new Link(); 4 list.set_Value(Integer.MIN_VALUE); 5 Link current1 = list1.get_Next(); 6 Link current2 = list2.get_Next(); 7 Link current = list; 8 while(current1 != null && current2 != null) 9 {10 Link temp = new Link();11 if (current1.get_Value() > current2.get_Value())12 {13 temp.set_Value(current2.get_Value());14 current2 = current2.get_Next();15 }16 else17 {18 temp.set_Value(current1.get_Value());19 current1 = current1.get_Next();20 }21 current.set_Next(temp);22 current = temp;23 }24 if (current1 != null)25 {26 while(current1 != null)27 {28 Link temp = new Link();29 temp.set_Value(current1.get_Value());30 current.set_Next(temp);31 current = temp;32 current1 = current1.get_Next();33 }34 }35 36 if (current2 != null)37 {38 while(current2 != null)39 {40 Link temp = new Link();41 temp.set_Value(current2.get_Value());42 current.set_Next(temp);43 current = temp;44 current2 = current2.get_Next();45 }46 }47 48 current.set_Next(null);49 50 return list;51 }
交换链表中任意两个元素(非头结点)
思路:首先需要保存两个元素的pre节点和next节点,然后分别对pre节点和next节点的Next属性重新赋值。需要注意的是,当两个元素师相邻元素时,需要特殊处理,否则会将链表陷入死循环。1 public static void swap(Link list, Link element1, Link element2) 2 { 3 if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null || 4 element1 == null || element2 == null || element1 == element2) 5 return; 6 7 Link pre1 = null, pre2 = null, next1 = null, next2 = null; 8 Link cur1=element1, cur2=element2; 9 Link temp = list.get_Next();10 boolean bFound1 = false;11 boolean bFound2 = false;12 while(temp != null)13 {14 if(temp.get_Next() == cur1)15 {16 pre1=temp;17 next1 = temp.get_Next().get_Next();18 bFound1 = true;19 }20 if (temp.get_Next() == cur2)21 {22 pre2 = temp;23 next2 = temp.get_Next().get_Next();24 bFound2=true;25 }26 if (bFound1 && bFound2) break;27 temp = temp.get_Next();28 }29 30 if (cur1.get_Next() == cur2)31 {32 temp = cur2.get_Next();33 pre1.set_Next(cur2);34 cur2.set_Next(cur1);35 cur1.set_Next(temp);36 }37 else if (cur2.get_Next() == cur1)38 {39 temp = cur1.get_Next();40 pre2.set_Next(cur1);41 cur1.set_Next(cur2);42 cur2.set_Next(temp);43 }44 else45 {46 pre1.set_Next(cur2);47 cur1.set_Next(next2);48 pre2.set_Next(cur1);49 cur2.set_Next(next1);50 }51 }
这里,还有另外一种取巧的方法,就是直接交换两个元素的值,而不需要修改引用。
1 public static void swapValue(Link list, Link element1, Link element2) 2 { 3 if (element1 == null || element2 == null) 4 { 5 return; 6 } 7 int temp = element1.get_Value(); 8 element1.set_Value(element2.get_Value()); 9 element2.set_Value(temp);10 }