【常用算法思路分析系列】链表相关高频题集

原创

【常用算法思路分析系列】链表相关高频题集

本文是【常用算法思路分析系列】的第四篇,总结链表相关的高频题目和解题思路。本文分析如下几个问题:1、环形链表的差值问题;2、只能访问单个结点的删除问题;3、链表的分化;4、打印两个链表的公共部分;5、把链表的每k个结点逆序;6、删除链表中指定结点;7、判断链表是否为回文结构;8、复杂链表的复制;9、判断链表是否有环;10、判断两个无环链表是否相交;11、判断两个有环链表是否相交;12、判断两个链表(状态未定)是否相交。

本系列前三篇导航:
【常用算法思路分析系列】排序高频题集
【常用算法思路分析系列】字符串高频题集

【常用算法思路分析系列】栈和队列高频题集(修改版)


1、环形链表插值问题

有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。

给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。

测试样例:
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}

   
   
  1. public class InsertValue {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6. int[] a = { 1, 3, 4, 5, 7};
  7. int[] b = { 1, 2, 3, 4, 0};
  8. // insert(a,b,2);
  9. System.out.println( "dd");
  10. }
  11. public static class ListNode {
  12. int val;
  13. ListNode next = null;
  14. ListNode( int val) {
  15. this.val = val;
  16. }
  17. }
  18. public static ListNode insert(int[] A, int[] nxt, int val) {
  19. ListNode head = null;
  20. if(A == null || A.length == 0){
  21. head = new ListNode(val);
  22. head.next = null;
  23. } else{
  24. //构造环形链表
  25. ListNode curNode = new ListNode(A[ 0]);
  26. head = curNode;
  27. for( int i = 0; i < nxt.length - 1; i++){
  28. ListNode node = new ListNode(A[nxt[i]]);
  29. curNode.next = node;
  30. curNode = node;
  31. }
  32. curNode.next = head;
  33. ListNode pre = head;
  34. curNode = head.next;
  35. boolean flag = false;
  36. while(curNode != head){
  37. if(val <= curNode.val){
  38. ListNode node = new ListNode(val);
  39. node.next = curNode;
  40. pre.next = node;
  41. flag = true;
  42. break;
  43. } else{
  44. pre = curNode;
  45. curNode = curNode.next;
  46. }
  47. }
  48. if(!flag){ //表示没有找到合适的位置,需要插入到头结点的前面(可能是val大于所有值或小于所有节点值)
  49. ListNode node = new ListNode(val);
  50. node.next = head;
  51. pre.next = node;
  52. if(val <= head.val){
  53. head = node;
  54. }
  55. }
  56. }
  57. return head;
  58. }
  59. }
我这个没有通过牛客的测试,说是超时,但我自己测试并没有发现有死循环的可能啊。。有知道的可以提示一下~

2、只能访问单个节点的删除

实现一个算法,删除单向链表中间的某个结点,没有给定头结点,你只能访问该结点。

给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true

思路:
将删除节点的下一个结点值赋给要删除的当前结点,然后当前结点指向它下一个结点的下一个结点。即实质是把 删除节点的下一个结点值赋给当前节点,然后删除当前节点的下一个结点。这种情况下,最后一个节点是不能删除的。

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public boolean removeNode(ListNode pNode) {
  9. if(pNode == null || pNode.next == null)
  10. return false;
  11. ListNode nextNode = pNode.next;
  12. pNode.val = nextNode.val;
  13. pNode.next = nextNode.next;
  14. return true;
  15. }

3、链表的分化

对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。

给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。

测试样例:
{1,4,2,5},3
{1,2,4,5}
思路:
定义一个指向结点值<=val的结点的链表,一个结点值>val的结点的链表,开始从头结点开始遍历,遍历到当前结点的时候,先用next指向当前结点的后面链表,取出当前结点,对当前结点的值进行比较,决定放到哪个链表中。这里要特别注意, 对于这种链表分化的操作,我们在遍历链表的时候,一定要把当前结点取出来,将其next置为null,即脱离原来的链表,否则,如果不把当前结点取出next置null,在组拼两个链表后,组拼后的链表中可能存在环!

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public ListNode listDivide(ListNode head, int val) {
  9. if(head == null)
  10. return null;
  11. ListNode minList = null,maxList = null;
  12. ListNode minListHead = null,maxListHead = null;
  13. ListNode next = null;
  14. while(head != null){
  15. next = head.next;
  16. head.next = null;
  17. if(head.val <= val){ //如果当前结点值<=val,将当前结点放入到小于阀值的链表
  18. if(minList == null){
  19. minList = head;
  20. minListHead = head;
  21. } else{
  22. minList.next = head;
  23. minList = head;
  24. }
  25. } else{ //如果当前结点值>val,将当前结点放入到大于阀值的链表
  26. if(maxList == null){
  27. maxList = head;
  28. maxListHead = head;
  29. } else{
  30. maxList.next = head;
  31. maxList = head;
  32. }
  33. }
  34. head = next;
  35. }
  36. if(minList != null){
  37. minList.next = maxListHead;
  38. }
  39. return minListHead != null ? minListHead : maxListHead;
  40. }

4、打印两个链表的公共值部分

现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。

给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值

测试样例:
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]
思路:
由于链表都是有序的,因此从头结点开始遍历两个链表,如果headA指向的结点值小于headB指向的结点值,headA往后移动一次,如果 headA指向的结点值大于headB指向的结点值,headB往后移动一次,如果相等,则可以打印该值,两个链表同时移动。如下:

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public int[] findCommonParts(ListNode headA, ListNode headB) {
  9. if(headA == null || headB == null){
  10. return null;
  11. }
  12. List<Integer> list = new ArrayList<Integer>();
  13. while(headA != null && headB != null){
  14. if(headA.val < headB.val){
  15. headA = headA.next;
  16. } else if(headA.val > headB.val){
  17. headB = headB.next;
  18. } else{ //相等,打印值
  19. list.add(headA.val);
  20. headA = headA.next;
  21. headB = headB.next;
  22. }
  23. }
  24. int[] res = new int[list.size()];
  25. for( int i = 0; i < list.size(); i++){
  26. res[i] = list.get(i);
  27. }
  28. return res;
  29. }

5、把链表的每k个元素逆序

有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。

给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。


思路一:
利用一个栈进行反转排序,从头开始遍历元素,每遍历一个元素,将其取下(next置为null),放入到栈中,当栈中元素达到k个时,对栈中的元素进行出栈,记录好头部和尾部结点,并链接到上一个反转后的尾部。由于利用了一个栈,因此这种算法时间复杂度为O(N),空间复杂度为 O(K)。代码如下:     

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public ListNode inverse(ListNode head, int k) {
  9. if(head == null || head.next == null || k < 2){
  10. return head;
  11. }
  12. Stack<ListNode> stack = new Stack<ListNode>();
  13. ListNode next = null;
  14. ListNode resHead = null; //反转后的链表的头结点
  15. ListNode header = null, tail = null; //记录每次反转过程中的头结点和尾结点
  16. ListNode lastTail = null; //上一次反转过程中的尾结点
  17. while(head != null){
  18. next = head.next;
  19. head.next = null;
  20. stack.push(head);
  21. if(stack.size() == k){
  22. //先退栈反转这k个元素,找到这k个元素的头和尾部
  23. header = tail = stack.pop();
  24. while(!stack.isEmpty()){
  25. tail.next = stack.pop();
  26. tail = tail.next;
  27. }
  28. if(resHead == null){ //是否是第一次反转前k个元素
  29. resHead = header;
  30. lastTail = tail;
  31. } else{
  32. lastTail.next = header;
  33. lastTail = tail;
  34. }
  35. }
  36. head = next;
  37. }
  38. if(!stack.isEmpty()){ //栈中还有元素,表示最后几个元素没有满足k个数量,保持原有顺序
  39. header = stack.pop();
  40. header.next = null;
  41. ListNode temp ;
  42. while(!stack.isEmpty()){ //头插法,维持原来顺序
  43. temp = stack.pop();
  44. temp.next = header;
  45. header = temp;
  46. }
  47. lastTail.next = header;
  48. }
  49. return resHead;
  50. }

思路二:
不借助栈,每次从链表中截取处k个元素,对这k个元素利用头插法进行反转。时间复杂度为O(N),空间复杂度为O(1),如下:

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public ListNode inverse(ListNode head, int k) {
  9. if(head == null || head.next == null || k < 2){
  10. return head;
  11. }
  12. ListNode next = null;
  13. ListNode resHead = null; //反转后的链表的头结点
  14. ListNode header = null, tail = null; //记录每次截取k个元素过程中的头结点和尾结点
  15. ListNode lastTail = null; //上一次反转过程中的尾结点
  16. boolean flag = true;
  17. while(flag){
  18. header = null;
  19. tail = null;
  20. //每次拿出k个元素
  21. for( int i = 0; i < k; i++){
  22. if(head == null){ //表示最后一组不满足k个元素
  23. flag = false;
  24. break;
  25. } else{
  26. next = head.next;
  27. head.next = null;
  28. if(i == 0){
  29. header = head;
  30. tail = head;
  31. } else{
  32. tail.next = head;
  33. tail = head;
  34. }
  35. head = next;
  36. }
  37. }
  38. if(flag){ //反转这k个元素
  39. ListNode tempListHeader = header;
  40. ListNode temp = tempListHeader;
  41. tempListHeader = tempListHeader.next;
  42. temp.next = null; //摘取出来
  43. while(tempListHeader != null){
  44. next = tempListHeader.next;
  45. tempListHeader.next = temp;
  46. temp = tempListHeader;
  47. tempListHeader = next;
  48. }
  49. if(resHead == null){
  50. resHead = tail;
  51. lastTail = header;
  52. } else{
  53. lastTail.next = tail;
  54. lastTail = header;
  55. }
  56. } else{ //最后一组不满足k个元素,直接放到反转后的链表后面
  57. lastTail.next = header;
  58. }
  59. }
  60. return resHead;
  61. }

6、删除链表中指定的元素

现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。

给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。

测试样例:
{1,2,3,4,3,2,1},2
{1,3,4,3,1}
思路一:
定义first、tail链表结点,用于指向新链表的头结点和尾结点,遍历链表,当前结点值!=val时,将当前结点加入到tail的后面,如下:

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public ListNode clear(ListNode head, int val) {
  9. if(head == null)
  10. return null;
  11. ListNode first = null,tail = null,next = null;
  12. while(head != null){
  13. next = head.next;
  14. head.next = null;
  15. if(head.val != val){
  16. if(first == null){
  17. first = head;
  18. tail = head;
  19. } else{
  20. tail.next = head;
  21. tail = head;
  22. }
  23. }
  24. head = next;
  25. }
  26. return first;
  27. }

思路二:
思路一相当于用了一个新链表,虽然空间复杂度还是为O(1),我们用一种在原链表上进行操作的办法。
对于单链表,要进行删除操作,重要的是记录当前删除结点的前一个结点,因此,我们定义pre、cur结点指针。如下:

  
  
  1. public ListNode clear(ListNode head, intnum) {
  2. while(head != null) {
  3. if(head.val != num) {
  4. break;
  5. }
  6. head = head.next;
  7. }
  8. ListNode pre = head;
  9. ListNode cur = head;
  10. while(cur != null) {
  11. if(cur.val == num) {
  12. pre.next = cur.next;
  13. } else{
  14. pre = cur;
  15. }
  16. cur = cur.next;
  17. }
  18. return head;
  19. }
  

7、判断链表是否为回文结构

编写一个函数,检查链表是否为回文。回文链表结构如下:1->2->3->2->1为回文结构,1->2->3->1不是回文结构。即回文结构是左右对称的。
思路一:
利用栈结构,第一遍遍历链表,将所有结点入栈;第二遍遍历链表,将当前遍历结点的值依次与栈中弹出的栈顶结点比较,只有全部相同才为回文结构。如下:

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public boolean isPalindrome(ListNode pHead) {
  9. if(pHead == null){
  10. return false;
  11. }
  12. Stack<ListNode> stack = new Stack<ListNode>();
  13. ListNode first = pHead;
  14. while(first != null){ //链表中全部结点先入栈
  15. stack.push(first);
  16. first = first.next;
  17. }
  18. ListNode temp;
  19. while(pHead != null && !stack.isEmpty()){ //再遍历一遍链表
  20. temp = stack.pop();
  21. if(temp.val != pHead.val){
  22. return false;
  23. } else{
  24. pHead = pHead.next;
  25. }
  26. }
  27. return true;
  28. }
时间复杂度为O(N),空间复杂度为O(N)。

思路二:
同样是利用一个栈结构,利用回文的特性,即左右结构对称,定义快、慢两个指针,初始都指向头结点,快指针每次走2步,慢指针每次走1步,把慢指针指向的结点入栈。当快指针到达尾部时,慢指针到达链表中部,如果链表结点数量为奇数,则慢指针指向的中间结点不入栈。此后,慢指针继续遍历后面的元素,每遍历一个结点,和栈中弹出的栈顶结点值进行比较,只有完全相同的时候,才为回文结构。、

思路二比思路一主要的优化在于栈中元素少了一半,为N/2,代码如下:     

   
   
  1. public boolean isPalindrome(ListNode pHead) {
  2. if(pHead == null){
  3. return false;
  4. }
  5. Stack<ListNode> stack = new Stack<ListNode>();
  6. ListNode fast = pHead;
  7. ListNode slow = pHead;
  8. while(fast != null){
  9. if(fast.next == null){ //表示到快指针达尾部,说明链表结点个数为奇数,此时慢指针到达中部,且此时慢指针指向的结点不需要入栈
  10. slow = slow.next; //链表结点数为奇数时,跳过中间结点
  11. break;
  12. }
  13. stack.push(slow);
  14. slow = slow.next;
  15. fast = fast.next.next;
  16. }
  17. ListNode temp;
  18. while(slow != null && !stack.isEmpty()){ //后面慢指针和栈顶结点值依次比较
  19. temp = stack.pop();
  20. if(temp.val != slow.val){
  21. return false;
  22. } else{
  23. slow = slow.next;
  24. }
  25. }
  26. return true;
  27. }

思路三(最优解):
不借助栈结构,同样是使用快慢指针,利用快慢指针,先找到链表中点位置,将中点位置的后半段链表进行反转,得到反转后的链表,对反转后的链表和原来链表的前半段进行依次遍历操作,比较两者的结点值是否相同,只有都相同才是回文结构。记得,不论是否是回文结构,都要把反转后的链表再次反转回来。示意图如下:


代码如下:     

   
   
  1. public boolean isPalindrome(ListNode pHead) {
  2. if(pHead == null){
  3. return false;
  4. }
  5. ListNode fast = pHead;
  6. ListNode slow = pHead;
  7. ListNode lastHead = null;
  8. while(fast.next != null && fast.next.next != null){ //寻找到慢指针指向的中点
  9. slow = slow.next;
  10. fast = fast.next.next;
  11. }
  12. if(fast.next != null){ //表示链表节点数量为偶数
  13. lastHead = slow.next;
  14. } else{
  15. lastHead = slow;
  16. }
  17. ListNode head = lastHead; //指向反转后的头结点
  18. lastHead = lastHead.next;
  19. ListNode next = null;
  20. head.next = null;
  21. while(lastHead != null){ //反转链表
  22. next = lastHead.next;
  23. lastHead.next = head;
  24. head = lastHead;
  25. lastHead = next;
  26. }
  27. boolean res = true;
  28. ListNode tempList = head;
  29. while(tempList != null && pHead != null){
  30. if(tempList.val != pHead.val){
  31. res = false;
  32. break;
  33. }
  34. tempList = tempList.next;
  35. pHead = pHead.next;
  36. }
  37. //再把后面反转的链表反转回来
  38. ListNode head2 = head;
  39. head = head.next;
  40. head2.next = null;
  41. while(head != null){
  42. next = head.next;
  43. head.next = head2;
  44. head2 = head;
  45. head = next;
  46. }
  47. slow.next = head2;
  48. return res;
  49. }

8、复杂链表的复制

一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),要求复制一份这种链表。
思路:
首先,第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
第二遍遍历结点,设置新结点的random指针。可以结合图,看一下,每个新结点的random指针,指向的是,它的前面一个结点(父本)的random指向的结点的下一个结点。
第三遍遍历结点,还原原来链表,取出所有的新结点;
示意图如下:

代码如下:     

   
   
  1. public class RandomListNode {
  2. int label;
  3. RandomListNode next = null;
  4. RandomListNode random = null;
  5. RandomListNode( int label) {
  6. this.label = label;
  7. }
  8. }
  9. public RandomListNode Clone(RandomListNode pHead){
  10. if(pHead == null){
  11. return null;
  12. }
  13. RandomListNode list = pHead;
  14. //第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
  15. while(list != null){
  16. RandomListNode node = new RandomListNode(list.label);
  17. node.next = list.next;
  18. list.next = node;
  19. list = list.next.next;
  20. }
  21. //第二遍遍历结点,设置新结点的random指针
  22. list = pHead;
  23. while(list != null){
  24. if(list.random != null){
  25. list.next.random = list.random.next;
  26. } else{
  27. list.next.random = null;
  28. }
  29. list = list.next.next;
  30. }
  31. list = pHead;
  32. RandomListNode newList = null;
  33. RandomListNode temp = null;
  34. RandomListNode res = null;
  35. //第三遍遍历结点,还原原来链表,取出所有的新结点;
  36. while(list != null){
  37. temp = list.next;
  38. list.next = list.next.next;
  39. temp.next = null;
  40. list = list.next;
  41. if(newList == null){
  42. newList = temp;
  43. res = temp;
  44. } else{
  45. newList.next = temp;
  46. newList = temp;
  47. }
  48. }
  49. return res;
  50. }

9、判断链表是否有环

如何判断一个单链表是否有环?有环的话返回进入环的第一个节点的值,无环的话返回-1。如果链表的长度为N,请做到时间复杂度O(N),额外空间复杂度O(1)。

思路一:
有点暴力破解的意思,利用哈希表,遍历链表过程中,在将结点放入到哈希表中前,先去判断哈希表中是否有该结点,如果有表示有环,没有就加入到哈希表中,如果无环,会因为null退出循环。注意,哈希表中方的是结点对象,而不是存放的结点值,因此,只有在放入前发现前面已经放入过相同的对象,则表示这是第一个进入环的地方。代码如下:    

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public int chkLoop(ListNode head) {
  9. if(head == null){
  10. return - 1;
  11. }
  12. int res = - 1;
  13. Map<ListNode,Integer> map = new HashMap<ListNode,Integer>();
  14. while(head != null){
  15. if(map.containsKey(head)){
  16. return head.val;
  17. }
  18. map.put(head, 1);
  19. head = head.next;
  20. }
  21. return res;
  22. }
思路一时间复杂度为O(N),空间复杂度为O(N)。

思路二(推荐):
利用 快慢指针遍历链表,确定是否有环。快指针走两步,慢指针走一步,如果快指针指向了null,表示无环;如果快指针和慢指针最后指向的同一个结点,表示链表中有环。再来确定第一个进入环的位置,当前面快指针和慢指针指向同一个结点时,快指针重新指向链表头结点,此后,慢指针和快指针同步以一个步伐的速度前进,当再一次相遇时,这个结点就是第一次进入环的结点。     

   
   
  1. public int chkLoop(ListNode head) {
  2. if(head == null || head.next == null){ //链表为空或者只有一个结点
  3. return - 1;
  4. }
  5. ListNode fast = head.next.next; //我的判断条件中是fast!=slow,因此这里不能设置fast=slow=head;
  6. ListNode slow = head.next;
  7. while(fast != null && fast.next != null && fast != slow){ //如果无环,则fast或fast.next会指向null,如果有环,某一时刻fast==slow
  8. fast = fast.next.next;
  9. slow = slow.next;
  10. }
  11. if(fast == slow){ //表示有环
  12. fast = head;
  13. while(fast != slow){
  14. fast = fast.next;
  15. slow = slow.next;
  16. }
  17. return fast.val;
  18. }
  19. return - 1;
  20. }
思路二时间复杂度为O(N),空间复杂度为O(1)。

10、判断两个无环链表是否相交

现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。

给定两个链表的头结点headA和headB,请返回一个bool值,代表这两个链表是否相交。

思路一:
利用哈希表,首先遍历一遍链表A,将A的所有结点放入到哈希表中,再遍历一遍B,遍历时,判断哈希表中是否有相同的结点,有表示相交;
此时空间复杂度为O(N),不满足要求;
思路二:
如果两个链表相交,后端对齐后,那说明两个链表的最后一个结点相同。因此,这种思路是,分别遍历链表A和B,指针指向两个链表的最后一个结点,如果租后一个结点相同,则表示相交。这种方式,虽然空间复杂度为O(1),但是 这种方式不能够知道最初相交的结点在哪里。如下:

  
  
  1. public boolean chkIntersect(ListNode headA, ListNode headB) {
  2. // write code here
  3. ListNode alast=headA,blast=headB;
  4. while(alast.next!= null){
  5. alast=alast.next;
  6. }
  7. while(blast.next!= null){
  8. blast=blast.next;
  9. }
  10. if(alast==blast)
  11. returntrue;
  12. else
  13. returnfalse;
  14. }

思路三:
如果要知道最初相交的结点在哪里,可用如下方法。首先分别遍历一遍链表A和B,得到A和B的长度m和n,如果m>n,A从头开始先遍历m-n步,如果m<n,B先遍历n-m步,此后A、B同时移动,如果遍历过程中A和B的当前结点相同,表示两个链表相交,且当前结点为相交的第一个结点。如下:     

   
   
  1. public boolean chkIntersect(ListNode headA, ListNode headB) {
  2. if(headA == null || headB == null){
  3. return false;
  4. }
  5. ListNode listA = headA;
  6. ListNode listB = headB;
  7. int m = 0; //链表A的长度
  8. int n = 0; //链表B的长度
  9. int dst = 0; //两个链表的长度差值
  10. while(listA != null){
  11. m++;
  12. listA = listA.next;
  13. }
  14. while(listB != null){
  15. n++;
  16. listB = listB.next;
  17. }
  18. listA = headA;
  19. listB = headB;
  20. if(m >= n){
  21. dst = m - n;
  22. for( int i = 0; i < dst; i++){
  23. listA = listA.next;
  24. }
  25. } else{
  26. dst = n - m;
  27. for( int i = 0; i < dst; i++){
  28. listB = listB.next;
  29. }
  30. }
  31. while(listA != null && listB != null && listA != listB){
  32. listA = listA.next;
  33. listB = listB.next;
  34. }
  35. if(listA != null && listB != null && listA == listB){
  36. return true;
  37. }
  38. return false;
  39. }
额外空间复杂度为O(1)。

11、有环单链表相交判断

如何判断两个有环单链表是否相交?相交的话返回第一个相交的节点,不想交的话返回空。如果两个链表长度分别为N和M,请做到时间复杂度O(N+M),额外空间复杂度O(1)。
思路:
对于两个有环链表,通过上面介绍的寻找单链表的入环点的方法,拿到两个链表的入环点进行比较:
(1)如果 这两个链表的入环结点相同,那说明这两个链表一定相交。此时,是如下这种情况:

A、B第一次相交的结点在共同入环结点的上面,这时,和上面判断两个无环链表是否相交一样,分别计算链表A和B到共同入环结点出的长度,让长度更长的结点先走长度的差值,然后两个链表共同移动,第一次相等的结点即为A和B第一次相交的结点。

(2)如果两个链表的入环结点不相同,此时,又有两种情况,如下:

这种情况下,只需要对链表A从入环结点出开始遍历一下,在再次回到入环结点前,看遍历节点中是否有B的入环结点。此时,A和B第一次相交的结点可以是A的入环结点或者是B的入环结点。

代码实现如下:     

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public boolean chkInter(ListNode head1, ListNode head2) {
  9. if(head1 == null || head2 == null){
  10. return false;
  11. }
  12. ListNode loopNode1 = getLoopStartNode(head1); //拿到链表head1的入环结点
  13. ListNode loopNode2 = getLoopStartNode(head2); //拿到链表head2的入环结点
  14. if(loopNode1 == null || loopNode2 == null)
  15. return false;
  16. if(loopNode1 == loopNode2){ //表示两个链表的入环结点相同,则两个链表肯定相交
  17. return true;
  18. } else{
  19. ListNode temp = loopNode1.next;
  20. while(temp != loopNode1){
  21. if(temp == loopNode2){ //表示环中有相交结点
  22. return true;
  23. }
  24. temp = temp.next;
  25. }
  26. }
  27. return false;
  28. }
  29. public ListNode getLoopStartNode(ListNode head){
  30. ListNode fast = head.next.next;
  31. ListNode slow = head.next;
  32. while(fast != null && fast.next != null && fast != slow){
  33. fast = fast.next.next;
  34. slow = slow.next;
  35. }
  36. if(fast == slow){
  37. fast = head;
  38. while(fast != slow){
  39. fast = fast.next;
  40. slow = slow.next;
  41. }
  42. return fast;
  43. }
  44. return null;
  45. }

12、判断两个链表(状态未知)是否相交

给定两个单链表的头节点head1和head2,如何判断两个链表是否相交?相交的话返回true,不想交的话返回false。

思路:
这个问题是前面9、10、11问题的综合,因为这里的两个链表状态未知,即不清楚是否有环,或者哪个有环,哪个无环。因此,首先要通过第9节的算法,拿到两个单链表的入环结点loop1和loop2进行分析比较:
(1)一个入环结点不为null,另一个入环结点为null,即一个链表有环,一个无环。这种情况下,两个链表不可能相交;
(2)两个入环结点都为null,此时,问题回到第10节的算法,判断两个无环结点是否相交的问题;
(3)如果入环结点都不为null,问题回到第11节的算法,判断两个有环结点是否相交的问题。
代码如下:     

   
   
  1. public class ListNode {
  2. int val;
  3. ListNode next = null;
  4. ListNode( int val) {
  5. this.val = val;
  6. }
  7. }
  8. public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
  9. if(head1 == null || head2 == null){
  10. return false;
  11. }
  12. ListNode loopNode1 = getLoopStartNode(head1); //拿到链表head1的入环结点
  13. ListNode loopNode2 = getLoopStartNode(head2); //拿到链表head2的入环结点
  14. //如果一个链表有环,一个链表无环,那么这两个链表不可能相交
  15. if((loopNode1 != null && loopNode2 == null) || (loopNode2 != null && loopNode1 == null)){
  16. return false;
  17. }
  18. //如果两个链表都没有环,则问题回到两个无环链表是否相交的判断上
  19. if(loopNode1 == null && loopNode2 == null){
  20. return chkNoLoopList(head1,head2);
  21. }
  22. //如果两个入环结点都不为空,即问题回到两个有环链表是否相交的判断上
  23. if(loopNode1 != null && loopNode2 != null){
  24. return chkHasLoopList(loopNode1,loopNode2);
  25. }
  26. return false;
  27. }
  28. /**
  29. * 判断两个有环链表是否相交
  30. * @param loopNode1
  31. * @param loopNode2
  32. * @return
  33. */
  34. public boolean chkHasLoopList(ListNode loopNode1, ListNode loopNode2) {
  35. if(loopNode1 == loopNode2){ //表示两个链表的入环结点相同,则两个链表肯定相交
  36. return true;
  37. } else{
  38. ListNode temp = loopNode1.next;
  39. while(temp != loopNode1){
  40. if(temp == loopNode2){ //表示环中有相交结点
  41. return true;
  42. }
  43. temp = temp.next;
  44. }
  45. }
  46. return false;
  47. }
  48. /**
  49. * 判断两个无环链表是否相交
  50. * @param headA
  51. * @param headB
  52. * @return
  53. */
  54. public boolean chkNoLoopList(ListNode headA, ListNode headB) {
  55. if(headA == null || headB == null){
  56. return false;
  57. }
  58. ListNode listA = headA;
  59. ListNode listB = headB;
  60. int m = 0; //链表A的长度
  61. int n = 0; //链表B的长度
  62. int dst = 0; //两个链表的长度差值
  63. while(listA != null){
  64. m++;
  65. listA = listA.next;
  66. }
  67. while(listB != null){
  68. n++;
  69. listB = listB.next;
  70. }
  71. listA = headA;
  72. listB = headB;
  73. if(m >= n){
  74. dst = m - n;
  75. for( int i = 0; i < dst; i++){
  76. listA = listA.next;
  77. }
  78. } else{
  79. dst = n - m;
  80. for( int i = 0; i < dst; i++){
  81. listB = listB.next;
  82. }
  83. }
  84. while(listA != null && listB != null && listA != listB){
  85. listA = listA.next;
  86. listB = listB.next;
  87. }
  88. if(listA != null && listB != null && listA == listB){
  89. return true;
  90. }
  91. return false;
  92. }
  93. /**
  94. * 获取链表的入环结点
  95. * @param head
  96. * @return
  97. */
  98. public ListNode getLoopStartNode(ListNode head){
  99. if(head == null || head.next == null){ //链表为null或者只有一个结点,此时没有入环结点
  100. return null;
  101. }
  102. ListNode fast = head.next.next;
  103. ListNode slow = head.next;
  104. while(fast != null && fast.next != null && fast != slow){
  105. fast = fast.next.next;
  106. slow = slow.next;
  107. }
  108. if(fast == slow){
  109. fast = head;
  110. while(fast != slow){
  111. fast = fast.next;
  112. slow = slow.next;
  113. }
  114. return fast;
  115. }
  116. return null;
  117. }


下一篇将是与二分搜索相关。



文章最后发布于: 2016-05-24 10:45:28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值