常用算法的总结——链表

链表常用算法:快慢指针、利用HashMap、栈

如:

1 快慢指针

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

第一题代码如下,其他同理:

// head头
public static Node mi d0rUpMi dNode(Node head) {
    if (head == nu11|I head.next == nu11Il| head.next.next == nu1l) {
    return head;
  }   Node slow
= head . next;   Node fast = head. next . next;   while (fast.next != nu11 && fast. next.next != nu11) {     slow = slow. next;     fast = fast. next. next;
  }   
return slow;
}

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

1) 哈希表方法特别简单——利用栈

public static boolean isPalindrome1 (Node head) {
  Stack<Node> stack = new Stack<Node>( );
  Node cur = head;
  while (cur != nu1l) {
    stack. push(cur);
    cur = cur. next;
  }
  while (head != nu1l) {
    if (head.value != stack. pop().value) {
      return false;
    }
    head = head. next;
  }
  return true;
}

2) 改原链表的方法就需要注意边界了

3) 利用快慢指针的算法:先让快指针到终点,慢指针到中点,然后让指向中点的慢指针指向null,后半部分全部倒转,后面的指向前面的,最后由终点和起点开始往中间遍历,只要有一步不相等就返回false,最后由如果需要恢复则将其恢复成原样,代码如下(包含了最后恢复成原样的步骤):

public static boolean isPalindrome3(Node head) {
        if (head == null | head.next == null) {
            return true;
        }
        Node n1 = head;
        Node n2 = head;
        while (n2.next != null && n2.next.next != null) { // find mid node
            n1 = n1.next; // n1 -> mid
            n2 = n2.next.next; // n2 -> end
        }
        n2 = n1.next; // n2 -> right part first node
        n1.next = null; // mid.next -> null
        Node n3 = null;
        while (n2 != null) { // right part convert
            n3 = n2.next; // n3 -> save next node
            n2.next = n1; // next of right node convert
            n1 = n2;//n1move
            n2 = n3;//n2move
        }
        n3 = n1; // n3 -> save last node
        n2 = head;// n2 -> left first node .
        boolean res = true;
        while (n1 != null && n2 != null) { // check palindrome
            if (n1.value != n2. value) {
                res = false;
                break ;
            }
            n1 = n1.next; // left to mid
            n2 = n2.next; // right to mid
        }
        n1 = n3.next;
        n3.next = null;
        while (n1 != null) { // recover list
            n2 = n1. next;
            n1.next = n3;
            n3 = n1;
            n1 = n2;
        }
        return res;

    }

 3 

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

  方法:把链表放入数组里,在数组上做partition ,代码如下:

public static Node listPartition1(Node head, int pivot) {
        if (head == null) {
            return head;
        }
        Node cur = head;
        int i = 0;
        while (cur != null) {
            i++;
            cur = cur.next;
        }
        Node[] nodeArr = new Node[i];
        i = 0;
        cur = head;
        for (i = 0; i != nodeArr.length; i++) {
            nodeArr[i] = cur;
            cur = cur.next;
        }
    //将数组进行交换,大于pivot的放pivot左边,反之放右边 arrPartition(nodeArr, pivot);
for (i = 1; i != nodeArr.length; i++) { nodeArr[i - 1].next = nodeArr[i]; } nodeArr[i - 1].next = null; return nodeArr[0]; } public static int[] arrPartition(int[] arr, int L, int R, int p) { int less = L - 1; int more = R + 1; while(L < more) { if(arr[L] < p) { swap(arr, ++less, L++); } else if (arr[L] > p) { swap(arr, --more, L); } else { L++; } } return new int[] { less + 1, more - 1 }; } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }

 

4 利用HashMap

一种特殊的单链表节点类描述如下
class Node {
  int value;
  Node next;
  Node rand;
  Node(int val) { value = val;

}
rand指针是单链表节点结构中新增的指针,rand可 能指向链表中的任意一个节 点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。

代码如下:

    public static Node copyListWithRand1 (Node head) {
        HashMap<Node, Node> map = new HashMap<Node, Node>();
        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) 新
            map.get(cur).next = map. get(cur.next);
            map. get(cur).rand = map. get(cur.rand);
            cur = cur.next ;
        }
        return map . get(head);
    }


[进阶] 时间复杂度O(N),额外空间复杂度0(1)

方式2:保证时间复杂度的前提下,不使用大的空间

public static Node copyListWithRand2(Node head) {
        if (head == null) {
            return null;
        }
        Node cur = head;
        Node next = null;
        // copy node and link to every node
        // 1-> 2
        //1->1'->2
        while (cur != null) {
            // cur老
            next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next;
        }
        cur = head;
        Node curCopy = null;
        // set copy node rand 搞定rand结点
        //1->1'->2->2'
        while (cur != null) {
            // cur老
            // cur.next  新copy
            next = cur.next.next;
            curCopy = cur.next;
            curCopy.rand = cur.rand != null ? cur.rand.next : null;
            cur = next;
        }
        //返回结果的头结点要为head的下一个
        Node res = head.next;
        cur = head;

        // split,搞定next结点
        while (cur != null) {
            next = cur.next.next;
            curCopy = cur.next;
            cur.next = next;
            curCopy.next = next != null ? next.next : null;
            cur = next;

        }
        return res;

    }

5 相交链表

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null

[要求]如果两个链表长度之和为N,时间复杂度请达到O(N),[进阶]额外空间复杂度请达到O(1)。

5.1 无环情况下解法head1、end1为第一个链表的头结点、尾结点,head2、end2为第二个链表的头结点、尾结点,判断end1是否为end2,不是则直接返回null(两者必不相交);是的话,长链表先走长链表比短链表长的部分(如长链表为100,短链表为80,则长链表先走20步),然后同时走,他们一定会在第一个结点相遇,代码如下:

    //如果两个链表都无环,返回第-一个相交节点,如果不想交,返回null
    public static Node noLoop(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        //n为差值
        int n = 0;
        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }
        //此时cur1、cur2分别到达链表1、2的末尾,
        if (cur1 != cur2) {
            return null;
        }

        // n :链表1长度减去链表2长度的值
        cur1 = n > 0 ? head1 : head2; //谁长,谁的头变成cur1
        cur2 = cur1 == head1 ? head2 : head1; //谁短,谁的头变成cur2
        n = Math.abs(n);
        //先走差值的步骤
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        //再同时一块走
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;

    }

5.2 有环的情况下解法

分析:①两条不想交的有环链表;②两条相交点为同一点;③相交点为不同点;三种情况图示如下:②其实和无环情况一样,只需要把cur1、cur2到达末尾改成到达公共结点即可

 

 

代码如下:

/** 两个有环链表, 返回第一个相交节点,如果不想交返回null
     * @param head1 1的头
     * @param loop1 1的入环
     * @param head2 2的头
     * @param loop2 2的入环
     * @return 返回相交结点,情况③的话有两个,返回任意一个即可
     */
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        //这部分的代码其实和无环的类似,只不过把while循环的终止条件改成到达入环的结点
        if (loop1 == loop2) {//说明相交点在环外,为情况②
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
                return cur1;
            }
        }
        else {//情况①和情况③
            cur1 = loop1.next;
            //开始从入环结点处开始遍历这个环,
            // 如果找到另外一个入环结点,则为情况③,返回任意一个入环结点即可
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            //如果没找到,则说明为情况①,返回null
            return null;

        }
    }

5.3 汇总有环和无环情况

总的方法如下:

public static Node getIntersectNode(Node head1, Node head2) {
        if (head1 == null|| head2 == null) {
            return null;
        }
        //getLoopNode()为获取入环结点的方法
        Node loop1 = getLoopNode (head1);
        Node loop2 = getLoopNode (head2);
        //如果两个链表的入环结点都为空,则说明二者都为无环链表
        if (loop1 == null && loop2 == null) {
            return noLoop( head1, head2);
        }
        //如果两个链表的入环结点都不为空,则说明二者都为有环链表
        if (loop1 != null && loop2 != null) {
            return bothLoop(head1, loop1, head2, loop2);
        }
        //其他情况都不可能相交
        return null;
    }

5.4 里面的获取入环结点的方法如下(其实这也是一道简单的面试题,代码省略了)

链表是否有环,有则找到入环结点,没有则返回null

解法一:HashSet

利用HashSet的唯一性,遍历链表,不存在则放入,存在则说明有环,没有环一定会返回null,图示如下:

 

 

 

 

 解法二:快慢指针

慢指针走一步,快指针走两步,那么一定会在某个点相遇,此时让快指针回到开头,变成一次走一步,两者同时出发(此时慢指针在相遇的点),两个慢指针就必然会在入环的点相遇

 

6 删除链表结点

能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉?

抖机灵的做法:给出要删除的结点,把该节点的next赋值给该节点,然后让该节点跨过下一个结点,让next指针指向下下一个节点

其实这样本质上并没有把自己删除掉,除此之外,尾巴结点删除不掉

其实这样是不行的,只能给出头结点才能解决。

posted @ 2021-01-01 12:21  Mistolte  阅读(285)  评论(0)    收藏  举报