博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
链表Linked List
阅读量:6668 次
发布时间:2019-06-25

本文共 13761 字,大约阅读时间需要 45 分钟。

链表问题

在链表问题中,最常见的方法就是“双指针”,“快慢指针”。

最常用的技巧就是加“fakehead”

删除系列

删除链表中的节点 203题(easy):

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6输出: 1->2->3->4->5

分析:

最基础的删除操作,用到fakehead,定义一个指针一趟遍历,判断指针的下一位是否是目标val。

代码:

public ListNode removeElements(ListNode head, int val) {          ListNode i=new ListNode(0);           i.next=head;           ListNode s=i;           while (i.next!=null){               if (i.next.val==val){                   i.next=i.next.next;               }               else i=i.next;           }           return s.next;             }

删除链表中的节点 237题(easy):

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

示例:

输入: head = [4,5,1,9], node = 5输出: [4,1,9]解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

分析:

和203题的区别在于只给了我们这个要被删除的节点,不能像之前一样通过从头节点遍历定位到要被删除的前一节点。所以我们只能操作当前这个节点和他的下一节点,而操作不了他的前一个节点。

思路是把下一节点的值覆盖到当前要被删除的节点上,然后不删除当前节点而是删除下一节点。这样删除的效果的一样的。

代码:

public void deleteNode(ListNode node) {         node.val = node.next.val;    node.next = node.next.next;    }

删除链表的倒数第N个节点 19题:

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:给定的 n 保证是有效的。

进阶:你能尝试使用一趟扫描实现吗?

分析:

用双指针的方法做;两指针从头节点开始,一个快指针先走n步,然后两个指针同时走,等到快指针走到尾节点时,这时的慢支针就指在倒数第n个节点。注意这里的边界,多试试。

代码:

public ListNode removeNthFromEnd(ListNode head, int n) {         if (head==null) return null;        ListNode slow=new ListNode(-1);        ListNode fast=new ListNode(-1);        slow.next=head;        fast.next=head;        ListNode res=slow;        for (int i=0;i

删除排序链表中的重复元素 83题(easy):

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例:

输入: 1->1->2输出: 1->2
输入: 1->1->2->3->3输出: 1->2->3

分析:

一趟扫描就可以;i挪向下一节点前,看当前节点和它的下一个节点是不是相等;如果相等执行i.next=i.next.next

代码:

public ListNode deleteDuplicates(ListNode head) {          if (head==null) return head;            ListNode i=head;            while (i.next!=null){                if (i.next.val==i.val){                    i.next=i.next.next;                }                else i=i.next;            }            return head;    }

删除排序链表中的重复元素II 82题:

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例:

输入: 1->2->3->3->4->4->5输出: 1->2->5
输入: 1->1->1->2->3输出: 2->3

分析:

(1)这里用到了双指针的方法。一个pre,一个cur

(2)这里的头节点可能被删去,所以操作时在头节点前加一个“假头节点”是必要的。
(3)和I的区别在于只保留没有重复出现的数字,我们要把重复的数字都删掉,因为链表的单向性,要保存一个前驱节点pre。这样才能保证跳过所有重复的数字。

代码:

public ListNode deleteDuplicates(ListNode head) {        if(head==null) return null;        ListNode FakeHead=new ListNode(0);        FakeHead.next=head;        ListNode pre=FakeHead;        ListNode cur=head;        while(cur!=null){            while(cur.next!=null&&cur.val==cur.next.val){                cur=cur.next;   //有重复cur挪动            }            if(pre.next==cur){//成立代表cur没有多挪动跳过重复元素                pre=pre.next;  //pre挪动一个            }            else{                pre.next=cur.next;//否则连接节点,删掉重复元素            }            cur=cur.next;//迭代扫描中cur每次都挪一个        }        return FakeHead.next;    }

交换旋转反转系列

两两交换链表中的节点 24题:

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.*你的算法只能使用常数的额外空间。*你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

分析:

按要求操作就行 注意加fakehead

代码:

public ListNode swapPairs(ListNode head) {        ListNode header=new ListNode(-1);        header.next=head;        ListNode l=header;        while (l.next!=null&&l.next.next!=null){//判断条件注意            ListNode temp1=l.next.next.next;            ListNode temp2=l.next;            l.next=temp2.next;            l.next.next=temp2;            temp2.next=temp1;                        l=l.next.next;        }        return header.next;    }

旋转链表 61题:

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例:

输入: 1->2->3->4->5->NULL, k = 2输出: 4->5->1->2->3->NULL解释:向右旋转 1 步: 5->1->2->3->4->NULL向右旋转 2 步: 4->5->1->2->3->NULL
输入: 0->1->2->NULL, k = 4输出: 2->0->1->NULL解释:向右旋转 1 步: 2->0->1->NULL向右旋转 2 步: 1->2->0->NULL向右旋转 3 步: 0->1->2->NULL向右旋转 4 步: 2->0->1->NULL

分析:

1.首先k可能大于节点个数,即k>len,此时旋转的位置是k%len个

2.关键找到旋转后的尾节点tail是哪个节点。观察后发现应该是从头节点数len-k%len个节点
3.最后一步,将原来链表首尾相接成一个环,将旋转后的尾节点tail的后一节点置为NULL
用到双指针(一个确定len,一个确定tail)和fakehead

代码:

public ListNode rotateRight(ListNode head, int n) {    if (head==null||head.next==null) return head;    ListNode dummy=new ListNode(0);    dummy.next=head;    ListNode fast=dummy,slow=dummy;    int i;    for (i=0;fast.next!=null;i++)//得到总长度i        fast=fast.next;        for (int j=i-n%i;j>0;j--) //得到第i-k%i个节点        slow=slow.next;        fast.next=dummy.next; //做旋转    dummy.next=slow.next;    slow.next=null;        return dummy.next;}

反转链表 206题(easy):

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL

分析:

迭代法一趟遍历,保存当前节点curr的前驱prev和后继nextTemp。

递归法思路一致,传参为nextTemp和prev,递归基是curr==null时,返回它的prev

代码:

public ListNode reverseList(ListNode head) {    /* 迭代法 */    ListNode prev = null;    ListNode curr = head;    while (curr != null) {        ListNode nextTemp = curr.next;        curr.next = prev;        prev = curr;        curr = nextTemp;    }    return prev;}
public ListNode reverseList(ListNode head) {        /* 递归法*/        return reverseListInt(head, null);    }    private ListNode reverseListInt(ListNode curr, ListNode prev) {        if (curr == null)            return prev;        ListNode nextTemp = curr.next;        curr.next = prev;        return reverseListInt(nextTemp, prev);    }

反转链表 II 92题:

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4输出: 1->4->3->2->5->NULL

分析:

思路和I的类似,但是这题是反转m到n的一段,而且要一趟扫描,所以要分段(0~m-1, m, m+1~n)不同处理,额外的变量也是必须的。

下面解法中,m到n这段中定义的subhead实际相当于I中的prev的作用;而这里的pre_cur只是为了将它最后定位在m-1位置上。扫描结束 将pre_cur,subtail,subhead,cur按pre_cur->subhead,
subtail->cur接起来.

代码:

public ListNode reverseBetween(ListNode head, int m, int n) {    ListNode dummyhead = new ListNode(0);    dummyhead.next = head;    ListNode sublisthead = new ListNode(0);    ListNode sublisttail = new ListNode(0);    int count = 1;    ListNode pre_cur = dummyhead, cur = head;    while(count <=n){        ListNode temp = cur.next;         if (count < m)   //这段实际不进行操作,只是保存每次cur的前后两个节点            pre_cur = cur;          else if (count == m){   //在m这点上定义 subtail和subhead的指向            sublisttail = cur;                  sublisthead.next = cur;        }else if (count > m){。  //在m+1~n这段上,方法就同I了,subhead就是I中的prev            cur.next = sublisthead.next;            sublisthead.next = cur;        }        cur = temp;    //cur转向下一节点        ++count;     //计数器    }    //连接    pre_cur.next = sublisthead.next;      sublisttail.next = cur;    return dummyhead.next;}

重排链表 143题:

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,

将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定链表 1->2->3->4, 重新排列为 1->4->2->3.给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

分析:

这题用到了反转链表I,可以分为三个步骤来操作:

1.利用快慢指针定位到中间节点(长度为偶数时靠左的中间节点)
2.将后一半reverse
3.将后一半依次交替插入

代码:

public void reorderList(ListNode head) {        if(head==null||head.next==null) return;        //Find the middle of the list        ListNode p1=head;        ListNode p2=head;        while(p2.next!=null&&p2.next.next!=null){            p1=p1.next;            p2=p2.next.next;        }        //Reverse the half after middle  1->2->3->4->5->6 to 1->2->3->6->5->4      Reverse Linked List I        ListNode preMiddle=p1;        ListNode preCurrent=p1;        ListNode current=p1.next;        while (current!=null){            ListNode temp=current.next;            current.next=preCurrent;            preCurrent=current;            current=temp;        }        preMiddle.next.next=null;        preMiddle.next=preCurrent;        //Start reorder one by one  1->2->3->6->5->4 to 1->6->2->5->3->4        p1=head;        p2=preMiddle.next;        while(p1!=preMiddle){            preMiddle.next=p2.next;            p2.next=p1.next;            p1.next=p2;            p1=p2.next;            p2=preMiddle.next;        }    }

判断回文链表 234题:

请判断一个链表是否为回文链表。

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

示例:

输入: 1->2输出: false
输入: 1->2->2->1输出: true

分析:

可以用到206题的反转链表来把原链表的后一半反转,然后依次比对。

注意要考虑节点长度为奇数和偶数的两种情况。

代码:

public boolean isPalindrome(ListNode head){        ListNode fast=head,slow=head;        //快慢指针用法,将slow定位到中间的节点上        while (fast!=null&&fast.next!=null){            fast=fast.next.next;            slow=slow.next;           }        if (fast!=null){ //fast!=null说明节点总长为奇数            slow=slow.next;        }        slow=reverseList(slow);        fast=head;        while (slow!=null){            if (fast.val!=slow.val){                return false;            }            fast=fast.next;            slow=slow.next;        }        return true;    }public ListNode reverseList(ListNode head) { //206题的reverseList函数          ListNode pre=null;          while (head!=null){              ListNode next=head.next;              head.next=pre;              pre=head;              head=next;          }          return pre;    }

循环问题

环形链表 141题(easy):

给定一个链表,判断链表中是否有环。

你能否不使用额外空间解决此题?

分析:

这类判断循环的方法就是“快慢指针”,快指针每次两步,慢指针每次一步。如果有循环的话,快慢指针最后一定会汇合。

代码:

public boolean hasCycle(ListNode head) {        if (head == null || head.next == null) {        return false;    }    ListNode slow = head;    ListNode fast = head.next;    while (slow != fast) {        if (fast == null || fast.next == null) {            return false;        }        slow = slow.next;        fast = fast.next.next;    }    return true;    }

环形链表II 142题:

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

说明:不允许修改给定的链表。
进阶:你是否可以不用额外空间解决此题?

分析:

这题首先像判断循环一样用快慢指针定位到汇合点,然后就是数学问题了...

假设:
1.L1定义为头点和入口点之间的距离
2.L2定义为入口点和会合点之间的距离
3.C定义为循环的长度
4.n被定义为快速指针绕循环的圈数
则有:
2*(L1+L2)=L1+L2+n*C => L1+L2=n*C =>L1=(n-1)*C+(C-L2)
那么一个指针从头节点,一个指针从汇合点沿着正向每次都只走一步,两指针最后的汇合点就是循环入口点。

代码:

public ListNode detectCycle(ListNode head) {        if (head==null||head.next==null) return null;        ListNode entry=head;        ListNode slow=head;        ListNode fast=head;        while (fast.next!=null&&fast.next.next!=null){            slow=slow.next;            fast=fast.next.next;            if (slow==fast){                while (entry!=slow){                    entry=entry.next;                    slow=slow.next;                }                return slow;            }        }        return null;//没有循环    }

其他问题

分割链表 86题:

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入: head = 1->4->3->2->5->2, x = 3输出: 1->2->2->4->3->5

分析:

基本思想是维护两个队列,第一个队列存储val小于x的所有节点,第二个队列存储所有其余节点。 然后连接这两个队列。 请记住将第二个队列的尾部设置为null,否则你将获得Time Limit Exceeded,即有循环

For this list: 5->6->1->2, x=3, at last cur2 points to 6, cur1 points to 2, we must set 6->1 to 6->null, otherwise there will be a cycle.

代码:

public ListNode partition(ListNode head, int x) {       ListNode d1=new ListNode(-1);       ListNode d2=new ListNode(-1);       ListNode cur1=d1,cur2=d2;       while (head!=null){           if (head.val

相交链表 160题:

编写一个程序,找到两个单链表相交的起始节点。

例如,下面的两个链表:

A:          a1 → a2                   ↘                     c1 → c2 → c3                   ↗            B:     b1 → b2 → b3

在节点 c1 开始相交。

注意:
1.如果两个链表没有交点,返回 null.
2.在返回结果后,两个链表仍须保持原有的结构。
3.可假定整个链表结构中没有循环。
4.程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存

分析:

这题两个链表的长度可能不一样,所以要先让两个链表各自指针指在“对齐”的位置,然后两指针每次都走一步判断是否相等就能找到相交点。

discuss里有个巧妙的方法,代码如下,记住吧...

代码:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {    //boundary check    if(headA == null || headB == null) return null;        ListNode a = headA;    ListNode b = headB;        //if a & b have different len, then we will stop the loop after second iteration    while( a != b){        //for the end of first iteration, we just reset the pointer to the head of another linkedlist        a = a == null? headB : a.next;        b = b == null? headA : b.next;        }        return a;}

合并两个有序链表 21题:

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4输出:1->1->2->3->4->4

分析:

类似于二路归并的思路。

注意这题是通过拼接的方式,不能重新再建一条链表把对应的值存里。

代码:

//迭代版本public ListNode mergeTwoLists(ListNode l1, ListNode l2) {        ListNode fakehead=new ListNode(-1);        ListNode tail=fakehead;        while (l1!=null&&l2!=null){            if (l1.val
//递归版本 public ListNode mergeTwoLists(ListNode l1, ListNode l2) {        if(l1 == null){            return l2;        }        if(l2 == null){            return l1;        }                ListNode mergeHead;        if(l1.val < l2.val){            mergeHead = l1;            mergeHead.next = mergeTwoLists(l1.next, l2);        }        else{            mergeHead = l2;            mergeHead.next = mergeTwoLists(l1, l2.next);        }        return mergeHead;    }

复制随机指针的链表 138题:

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的深度拷贝。

//定义的随机指针节点class RandomListNode {      int label;     RandomListNode next, random;     RandomListNode(int x) { this.label = x; }}

分析:

这题和克隆图那道类似,算是那题的一个简化版。使用克隆图那题的BFS方法可以做,但是复杂没必要。这题的一个高票答案只用了三趟扫描,常数空间复杂度。三趟扫描思路如下:

1.在next指针相连的两个节点之间插入一个前一节点的拷贝
如1->2->3->null 插入后为 1->1->2->2->3->3->null
2.这次扫描确定拷贝的节点的random指针指向;因为拷贝的节点就在原节点的后面,所以能够直接定位操作到。
3.再把拷贝的节点从原来的链表中取出来,用next连接起来。

代码:

public RandomListNode copyRandomList(RandomListNode head) {    RandomListNode iter = head, next;    // 第一轮:做好每一个节点的拷贝    // 插入到原连表中    while (iter != null) {        next = iter.next;        RandomListNode copy = new RandomListNode(iter.label);        iter.next = copy;        copy.next = next;        iter = next;    }    // 第二轮:标记拷贝节点的random指针指向    iter = head;    while (iter != null) {        if (iter.random != null) {            iter.next.random = iter.random.next;        }        iter = iter.next.next;    }    // 第三轮:恢复原链表,把拷贝链表中原链表中取出来。    iter = head;    RandomListNode pseudoHead = new RandomListNode(0);    RandomListNode copy, copyIter = pseudoHead;    while (iter != null) {        next = iter.next.next;        // 取出拷贝        copy = iter.next;        copyIter.next = copy;        copyIter = copy;        // 恢复原链表        iter.next = next;        iter = next;    }    return pseudoHead.next;}

遗留的题目:

109 有序链表转换二叉搜索树(DFS)

147 对链表进行插入排序(sort)
148 排序链表(sort)

转载于:https://www.cnblogs.com/10zhang/p/9286433.html

你可能感兴趣的文章
40种网页小技巧
查看>>
PHP 乱码解决方面
查看>>
在Linux中一个网卡绑定多个IP设定
查看>>
Ural 1519 Formula 1 (插头DP)
查看>>
c++动态链接库函数转换为C#函数
查看>>
Mageia 3 Alpha 2 发布,Mandriva 分支
查看>>
poj3994
查看>>
vim中的复制与粘贴 | WangYan BLog
查看>>
android.database.sqlite.SQLiteException: table TB_READ_PERIOD already exists
查看>>
Nginx 1.2.5 稳定版发布
查看>>
linux 自学系列:linux 文本模式
查看>>
poj1003
查看>>
Spring 表单处理
查看>>
编写用逻辑扇区号读写软盘的中断例程
查看>>
Pentaho Big Data Community Home - Pentaho Big Data - Pentaho Wiki
查看>>
HTML基础(二)
查看>>
【转】NSMutableArray的正确使用
查看>>
vim配置
查看>>
逆序数
查看>>
mysql远程访问的时候遇到了各种问题
查看>>