第9节.pptx

1:快慢指针

1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个

  1. static class Node {
  2. int value;
  3. Node next;
  4. public Node(int value) {
  5. this.value = value;
  6. }
  7. }
  8. //1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
  9. public static Node midOrUpMidNode(Node head) {
  10. if (head == null || head.next == null || head.next.next == null) {
  11. return head;
  12. }
  13. // 链表至少有3个节点
  14. Node sPoint = head.next;
  15. Node qPoint = head.next.next;
  16. while (qPoint.next != null && qPoint.next.next != null) {
  17. qPoint = qPoint.next.next;
  18. sPoint = sPoint.next;
  19. }
  20. return sPoint;
  21. }
  22. public static Node test1(Node head) {
  23. if (head == null) {
  24. return head;
  25. }
  26. Node cur = head;
  27. List<Node> list = new ArrayList<>();
  28. while (cur != null) {
  29. list.add(cur);
  30. cur = cur.next;
  31. }
  32. return list.get((list.size() - 1) >> 1);
  33. }
  34. //2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
  35. public static Node midOrDownMidNode(Node head) {
  36. if (head == null || head.next == null |) {
  37. return head;
  38. }
  39. //链表至少有2个节点
  40. Node sPoint = head.next;
  41. Node qPoint = head.next;
  42. while (qPoint.next != null && qPoint.next.next != null) {
  43. qPoint = qPoint.next.next;
  44. sPoint = sPoint.next;
  45. }
  46. return sPoint.next;
  47. }
  48. public static Node test2(Node head){
  49. if (head == null) {
  50. return head;
  51. }
  52. Node cur = head;
  53. List<Node> list = new ArrayList<>();
  54. while (cur != null) {
  55. list.add(cur);
  56. cur = cur.next;
  57. }
  58. return list.get(list.size() >> 1);
  59. }
  60. //3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
  61. public static Node midOrUpMidPreNode(Node head) {
  62. if (head == null || head.next == null || head.next.next == null) {
  63. return null;
  64. }
  65. // 至少3个节点
  66. //链表至少有2个节点
  67. Node sPoint = head;
  68. Node qPoint = head.next.next;
  69. while (qPoint.next != null && qPoint.next.next != null) {
  70. qPoint = qPoint.next.next;
  71. sPoint = sPoint.next;
  72. }
  73. return sPoint;
  74. }
  75. public static Node test3(Node head){
  76. if (head == null||head.next==null||head.next.next==null) {
  77. return null;
  78. }
  79. Node cur = head;
  80. List<Node> list = new ArrayList<>();
  81. while (cur != null) {
  82. list.add(cur);
  83. cur = cur.next;
  84. }
  85. return list.get((list.size()-3)>>1);
  86. }
  87. //4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
  88. public static Node midOrDownMidPreNode(Node head) {
  89. if (head == null || head.next == null) {
  90. return null;
  91. }
  92. if (head.next.next == null) {
  93. return head;
  94. }
  95. // 至少3个节点
  96. Node sPoint = head;
  97. Node qPoint = head.next;
  98. while (qPoint.next != null && qPoint.next.next != null) {
  99. qPoint = qPoint.next.next;
  100. sPoint = sPoint.next;
  101. }
  102. return sPoint;
  103. }
  104. public static Node test4(Node head){
  105. if (head == null||head.next==null||head.next.next==null) {
  106. return null;
  107. }
  108. Node cur = head;
  109. List<Node> list = new ArrayList<>();
  110. while (cur != null) {
  111. list.add(cur);
  112. cur = cur.next;
  113. }
  114. return list.get((list.size()-2)>>1);
  115. }

2 常见面试题

题目1 给定一个单链表的头节点head,请判断该链表是否为回文结构。

思路:
1)哈希表方法特别简单(笔试用)
2)改原链表的方法就需要注意边界了(面试用)

  1. public static class Node {
  2. public int value;
  3. public Node next;
  4. public Node(int data) {
  5. this.value = data;
  6. }
  7. }
  8. // need n extra space
  9. public static boolean isPalindrome1(Node head) {
  10. Stack<Node> stack = new Stack<Node>();
  11. Node cur = head;
  12. while (cur != null) {
  13. stack.push(cur);
  14. cur = cur.next;
  15. }
  16. while (head != null) {
  17. if (head.value != stack.pop().value) {
  18. return false;
  19. }
  20. head = head.next;
  21. }
  22. return true;
  23. }
  24. // need n extra space 使用List
  25. public static boolean isPalindromeList2(Node head) {
  26. if (head == null | head.next == null) {
  27. return true;
  28. }
  29. List<Node> list = new ArrayList<>();
  30. Node cur = head;
  31. while (cur != null) {
  32. list.add(cur);
  33. cur = cur.next;
  34. }
  35. cur = head;
  36. for (int i = list.size() - 1; i >= 0; i--) {
  37. if (list.get(i).value != cur.value) {
  38. return false;
  39. }
  40. cur = cur.next;
  41. }
  42. return true;
  43. }
  44. // need n/2 extra space
  45. public static boolean isPalindrome2(Node head) {
  46. if (head == null || head.next == null) {
  47. return true;
  48. }
  49. Node right = head.next;
  50. Node cur = head;
  51. while (cur.next != null && cur.next.next != null) {
  52. right = right.next;
  53. cur = cur.next.next;
  54. }
  55. Stack<Node> stack = new Stack<Node>();
  56. while (right != null) {
  57. stack.push(right);
  58. right = right.next;
  59. }
  60. while (!stack.isEmpty()) {
  61. if (head.value != stack.pop().value) {
  62. return false;
  63. }
  64. head = head.next;
  65. }
  66. return true;
  67. }
  68. // need O(1) extra space
  69. public static boolean isPalindromeList(Node head) {
  70. if (head == null || head.next == null) {
  71. return true;
  72. }
  73. // 至少2个节点
  74. Node n1 = head;
  75. Node n2 = head;
  76. // n1 ,链表长度为奇数时,为中点;链表长度为偶数时,为上中点
  77. while (n2.next != null && n2.next.next != null) {
  78. n2 = n2.next.next;
  79. n1 = n1.next;
  80. }
  81. n2 = n1.next;
  82. n1.next = null;
  83. Node n3 = null;
  84. while (n2 != null) {
  85. n3 = n2.next;
  86. n2.next = n1;
  87. n1 = n2;
  88. n2 = n3;
  89. }
  90. // n1 是最后一个节点,n3 是最后一个节点
  91. n3 = n1;
  92. n2 = head;
  93. boolean ans = true;
  94. while (n1 != null && n2 != null) {
  95. if (n1.value != n2.value) {
  96. ans = false;
  97. break;
  98. }
  99. n1 = n1.next;
  100. n2 = n2.next;
  101. }
  102. // n1 为倒数第2个节点
  103. n1 = n3.next;
  104. n3.next = null;
  105. while (n1 != null) {
  106. n2 = n1.next;
  107. n1.next = n3;
  108. n3 = n1;
  109. n1 = n2;
  110. }
  111. return ans;
  112. }

题目2:将单向链表按某值划分成左边小、中间相等、右边大的形式

思路:
1)把链表放入数组里,在数组上做partition(笔试用)
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)

  1. static class Node {
  2. int value;
  3. Node next;
  4. public Node(int value) {
  5. this.value = value;
  6. }
  7. }
  8. // 法1 分成小、中、大三部分,再把各个部分之间串起来(面试用)
  9. public static Node smallEqualBig(Node head, int k) {
  10. // 只有0、1个节点情况下直接返回
  11. if (head == null || head.next == null) {
  12. return head;
  13. }
  14. //至少2个节点的情况
  15. Node smallHead = null;
  16. Node smallTail = null;
  17. Node equalHead = null;
  18. Node equalTail = null;
  19. Node bigHead = null;
  20. Node bigTail = null;
  21. Node next = null;
  22. while (head != null) {
  23. next = head.next;
  24. head.next = null;
  25. if (head.value < k) {
  26. if (smallHead == null) {
  27. smallHead = head;
  28. } else {
  29. smallTail.next = head;
  30. }
  31. smallTail = head;
  32. } else if (head.value == k) {
  33. if (equalHead == null) {
  34. equalHead = head;
  35. } else {
  36. equalTail.next = head;
  37. }
  38. equalTail = head;
  39. } else {
  40. if (bigHead == null) {
  41. bigHead = head;
  42. } else {
  43. bigTail.next = head;
  44. }
  45. bigTail = head;
  46. }
  47. head = next;
  48. }
  49. if (smallTail != null) {
  50. smallTail.next = equalHead;
  51. equalTail = equalTail == null ? smallTail : equalTail;
  52. }
  53. if (equalTail != null) {
  54. equalTail.next = bigHead;
  55. }
  56. return smallHead != null ? smallHead : (equalHead != null ? equalHead : bigHead);
  57. }
  58. // 法2 把链表放入数组里,在数组上做partition(笔试用)
  59. public static Node smallEqualBig2(Node head, int k) {
  60. if (head == null || head.next == null) {
  61. return head;
  62. }
  63. ArrayList<Node> list = new ArrayList<>();
  64. while (head != null) {
  65. list.add(head);
  66. head = head.next;
  67. }
  68. partition(list, k);
  69. for (int i = 0; i < list.size(); i++) {
  70. head = list.get(i);
  71. if (i == list.size() - 1) {
  72. head.next = null;
  73. } else {
  74. head.next = list.get(i + 1);
  75. }
  76. }
  77. return list.get(0);
  78. }
  79. private static void partition(ArrayList<Node> list, int k) {
  80. int smallR = -1;
  81. int bigL = list.size() - 1;
  82. for (int i = 0; i <= bigL; ) {
  83. if (list.get(i).value < k) {
  84. swap(list, ++smallR, i++);
  85. } else if (list.get(i).value == k) {
  86. i++;
  87. } else {
  88. swap(list, bigL--, i);
  89. }
  90. }
  91. }
  92. private static void swap(ArrayList<Node> list, int i, int j) {
  93. Node tmp = list.set(i, list.get(j));
  94. list.set(j, tmp);
  95. }

题目3:链表的复制,并返回复制的新链表的头节点。

leetcode : https://leetcode.com/problems/copy-list-with-random-pointer/
一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(intval) { value = val; }
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)

static class Node {
    int value;
    Node next;
    Node random;

    public Node(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Node node = (Node) o;
        return value == node.value && Objects.equals(next, node.next) && Objects.equals(random, node.random);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value, next, random);
    }
}

public static Node copyListWithRandom(Node head) {
    // 0、1个节点 直接返回
    if (head == null) {
        return head;
    }
    Node cur = head;
    Node next = null;
    while (cur != null) {
        Node newNode = new Node(cur.value);
        next = cur.next;
        cur.next = newNode;
        newNode.next = next;
        cur = next;
    }

    cur = head;
    Node random = null;
    while (cur != null) {
        random = cur.random;
        cur.next.random = random != null ? random.next : null;
        cur = cur.next.next;
    }

    Node ans = head.next;
    Node ansCur = ans;
    cur = head;
    while (cur != null) {
        next = cur.next.next;
        cur.next = next;
        ansCur.next = next != null ? next.next : null;
        cur = next;
        ansCur = ansCur.next;
    }
    return ans;
}

// 法2 使用容器
public static Node copyListWithRandom2(Node head) {
    // key 老节点
    // value 新节点
    HashMap<Node, Node> map = new HashMap<>(16);
    Node cur = head;
    while (cur != null) {
        map.put(cur, new Node(cur.value));
        cur = cur.next;
    }
    cur = head;
    while (cur != null) {
        // cur 老
        // map.get(cur) 新
        // 新.next ->  cur.next克隆节点找到
        map.get(cur).next = map.get(cur.next);
        map.get(cur).random = map.get(cur.random);
        cur = cur.next;
    }
    return map.get(head);
}

题目4 返回2个可能有环的链表相交的第一个节点

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null
【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

public static Node findFirstIntersectNode(Node head1, Node head2) {
    // 两个链表都为空
    if (head1 == null || head2 == null) {
        return null;
    }
    // 两个链表都不为空

    Node loop1 = isLoop(head1);
    Node loop2 = isLoop(head2);
    if (loop1 == null && loop2 == null) {
        return noLoopIntersect(head1, head2);
    } else if (loop1 == null ^ loop2 == null) {
        return oneLoopIntersect(head1, head2);
    } else {
        return twoLoopIntersect(head1, head2);
    }
}

// 判断链表是否有环,有环则返还进入环的节点,无环则返回null

private static Node isLoop(Node head) {
    if (head == null || head.next == null || head.next.next == null) {
        return null;
    }
    Node slow = head.next;
    Node fast = head.next.next;

    // 从循环里出来时,slow==fast
    while (slow != fast) {
        if (fast.next == null || fast.next.next == null) {
            return null;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    fast = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
/*
* 两个可能有环的链表相交,有以下几种情况:
* 1 两个无环链表不相交
* 2 两个无环链表相交
* 3 一个有环、一个无环 不可能相交
* 4 两个有环链表不相交
* 5 两个有环链表相交
*   i 相交节点在第一个入环节点之前
*   ii 相交节点环上
* */

//  返回两个无环链表相交节点,不相交返回null(情况1、2)

private static Node noLoopIntersect(@NotNull Node head1, @NotNull Node head2) {
    Node end1 = head1;
    Node end2 = head2;
    //至少有一个节点
    int size1 = 1;
    int size2 = 1;
    while (end1.next != null) {
        size1++;
        end1 = end1.next;
    }
    while (end2.next != null) {
        size2++;
        end2 = end2.next;
    }
    // 两个无环链表相交,那么最后一个节点一定相同,不同则表示两个链表不相交
    if (end1 != end2) {
        return null;
    }
    // end1节点指向长的那个链表,end2指向短的那个链表
    end1 = size1 > size2 ? head1 : head2;
    end2 = end1 == head1 ? head2 : head1;

    for (int i = 0; i < Math.abs(size1 - size2); i++) {
        end1 = end1.next;
    }

    while (end1 != end2) {
        end1 = end1.next;
        end2 = end2.next;
    }
    return end1;
}

//  一个有环,一个无环,不可能相交(情况3)

private static Node oneLoopIntersect(Node head1, Node head2) {
    return null;
}

// 返回两个有环链表相交第一个节点,不相交返回null(情况4、5)

private static Node twoLoopIntersect(Node head1, Node head2) {
    Node loopNode1 = isLoop(head1);
    Node loopNode2 = isLoop(head2);
    // 相交节点在第一个入环节点之前
    if (loopNode1 == loopNode2) {
        // 将第一个入环节点当成链表最后一个节点,类似情况2
        Node end1 = head1;
        Node end2 = head2;
        int n = 0;
        while (end1 != loopNode1) {
            n++;
            end1 = end1.next;
        }
        while (end2 != loopNode2) {
            n--;
            end2 = end2.next;
        }
        // end1 指向长的链表
        end1 = n > 0 ? head1 : head2;
        end2 = end1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            end1 = end1.next;
            n--;
        }
        while (end1 != end2) {
            end1 = end1.next;
            end2 = end2.next;
        }
        return end1;
    } else {
        Node loopNodeCur = loopNode1.next;
        while (loopNodeCur != loopNode1) {
            // 相交节点在环上
            if (loopNodeCur == loopNode2) {
                return loopNode1;
            }
            loopNodeCur = loopNodeCur.next;
        }
        // 不相交
        return null;
    }
}