链表

Leetcode #160 相交链表

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

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

方法1:

1.利用指针及链表特性数出两个链表各自长度
2.使较长的链表的头指针先向前移动多出来的长度,使得两链表从当前位置处开始长度相等
3.移动两个指针,直到两个指针值相等时退出循环
4.返回该指针

这个方法很显而易见,就不具体阐述了

方法2:

我们可以得出链表长度由两部分组成,一部分是公共部分,一部分是非公共部分。

要点:如果我知道了A链表和B链表各自非公共部分的长度,那么只要走完这个非公共部分的长度,指针所指即是答案。

假设有链表A、B。 A链表非公共部分长度为a,B链表非公共部分长度为b,公共部分长度为c,那么我们可以可以很容易得出的一个等式: a + c + b = b + c + a其中左边的 a + c 和右边的 b + c 其实就是两个链表各自的长度了。那么这句等式具体是什么意思呢?
我们可以这样理解:PA 和 PB刚开始是分别指向链表A、B的指针,不断的令PA、PB往下一个结点移动,当PA走完A链表的长度后(也就是走完a + c的距离后),令PA指向B链表的头部,再走一个b的距离(也就是B链表非公共部分的长度),这不就实现了我们的那个要点嘛!!!!

上代码:


    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *la = headA, *lb = headB;
        while(la != lb)
        {
            la = (la == NULL)? headB : la->next;
            lb = (lb == NULL)? headA : lb->next;
        }
        return la;
    }

Leetcode #206 翻转链表

题名:翻转链表
描述:反转一个单链表。分别写出迭代和递归算法。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/reverse-linked-list/

方法1:迭代

迭代无非就是按顺序从头遍历,在遍历的同时就行翻转操作!
我们可以设置连个三个指针, pre, cur, next。初始时,pre指向NULL,cur指向头结点(注意这里的头结点也是元素节点,并不是空节点),next指向头结点的下一个结点。
用这三个指针遍历整个数组,每一次将cur的指针指向pre所指的结点,next用于记录下一个结点的位置。结束条件是当cur为空。代码如下:


    ListNode* reverseList(ListNode* head) {
        ListNode *pre = NULL, *cur = head;
        while(cur)
        {
            ListNode *next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

还有一种方法,速度要快得多,当然也消耗了多余的空间,是空间换时间的思想。它就是多申请了一个头结点,然后用头插法进行翻转。代码如下:


    ListNode* reverseList(ListNode* head) {
        ListNode *nh = new ListNode(-1);
        while(head)
        {
            ListNode *next = head->next;
            head->next = nh->next;
            nh->next = head;
            head = next;
        }
        return nh->next;
    }

方法2:递归

递归算法思想还是比较好像的,关键在于细节。
此处再次回顾一下递归的思想:把大问题化成具有相同性质的小问题,在解决小问题后再自然而然地解决大问题。
关键是:1.递归出口 2.递归式 3.返回值 4.具体实现

这个翻转链表的问题就是一个典型的递归问题。将一个较长的链表进行翻转,那我就先去翻转较短的子链表。例如要对1->2->3->4->5进行翻转,我就先去翻转2->3->4->5,要对2->3->4->5进行翻转我就先去翻转3->4->5,知道最后仅仅翻转5,这么个最小的问题,所以递归出口也就在这里了。这样大概的就可以写出一个雏形:


    ListNode* reverseList(ListNode* head) {
        //先写递归出口
        if(head == NULL || head->next == NULL)
            return head;
        //再写递归式
        ListNode *next = head->next;//指向下一个较短链表
        ListNode *newhead = reverseList(next);//翻转这个较短的链表
        
        ......
        return newhead;//返回值可以马上就确定下来,就是指向翻转后新链表的头指针
    }

这样雏形就出来了,这时候就应该想如何去翻转某个具体的链表。例如翻转4->5,head是指向4的,next是指向5的,那么要变成5->4,就是next->next = head; head->next = NULL;所以具体代码如下:


    ListNode* reverseList(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;
        ListNode *next = head->next;
        ListNode *reversed = reverseList(next);
        next->next = head;
        head->next = NULL;
        return reversed;
    }

Leetcode #21 合并链表

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

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/merge-two-sorted-lists/

方法1:常规遍历

将两个链表合并成一个链表,是链表基本操作!
为了思考起来比较方便,我们可以引入一个头结点,然后设置一个用于移动遍历的指针p,指向头结点。对l1,l2进行遍历每次穿起较小的那个节点,代码如下:


    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *newlist = new ListNode(-1);
        ListNode *p = newlist;
        while(l1 && l2)
        {
            if(l1->val <= l2->val)
            {
                p->next = l1;
                p = l1;
                l1 = l1->next;   
            }
            else
            {
                p->next = l2;
                p = l2;
                l2 = l2->next; 
            }
        }
        while(l1)
        {
            p->next = l1;
            p = l1;
            l1 = l1->next;
        }
        while(l2)
        {
            p->next = l2;
            p = l2;
            l2 = l2->next;
        }
        return newlist->next;
    }

方法2:递归

递归思想回顾:
1.递归出口; 显而易见,当链表为空时,就到了递归的边界了。
2.递归式;
3.返回值; 因为是单链表,只有知道链表头指针才能遍历整个链表,所以返回值是指向新链表的指针。
4.具体实现; 取整个递归过程中,某一层(只需要一层,而不需要完全搞清楚整个递归栈是如何运作的)递归的实现即是具体实现。

其中递归式,就是用来把大问题化解为小问题的。
具体实现要取整个递归过程中的某一层递归,取设计其实现。例如这一题:(取第一层递归)假设我们有两链表L1: 1->3->5->7 L2: 2->4->6->8
因为要求升序,所以合并1->3->5->7和2->4->6->8,相当先合并3->5->7和2->4->6->8并返回新合并链表的头指针newlist(由递归的正确性,我们可以得知newlist链表就是2->3->4->5->6->7->8,而newlist是指向2的),再使1->(指向)返回的头指针newlist,结果就是1->2->3->4->5->6->7->8

具体代码如下:


    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == NULL)
            return l2;//返回值永远是指向新链表的指针!
        if(l2 == NULL)
            return l1;
        if(l1->val < l2->val)
        {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        else
        {
            l2->next = mergeTwoLists(l2->next, l1);
            return l2;
        }
    }

Leetcode #83 删除排序链表中重复的元素

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

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/

方法一:

因为链表是sorted已排序的,所以可以用双指针法,然后遍历整个链表,较为简单不再叙述,上代码:


    ListNode* deleteDuplicates(ListNode* head) {
        ListNode *p = head, *q;
        if(p)   q = p->next;
        bool ndd = false;
        while(q != NULL)
        {
            if(p->val == q->val)
                ndd = true;
            else
            {
                if(ndd)
                {
                    p->next = q;
                    ndd = false;
                }
                p = q;
            }
            q = q->next;
        }
        if(ndd)
            p->next = q;
        return head;
    }

方法二:

可以用递归的方法,能减少代码量:
这里递归式就是要加一个判断。假设有p,q指针,当p->val != q->val时,再用递归,代码如下:


    ListNode* deleteDuplicates(ListNode* head) {
        if(!head)
            return head;
        ListNode *p = head, *q = head->next;
        while(q && p->val == q->val) //加一个判断
            q = q->next;
        ListNode *res = deleteDuplicates(q);
        head->next = res;
        return head;
    }

Leetcode #19 删除链表倒数第n个结点

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

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

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

方法一:

需要扫描2遍链表,第一遍是计算出链表总长度m,令ltf = m - n 再从头扫描到ltf处,删除该位置的结点即可。链表操作一般引入一个空头结点会比较方便。

代码如下:


    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode hh(-1);//引入一个头结点;在栈空间中,退出该函数后会自动销毁;如果要new一个则在堆空间中,记得最后要自己释放该空间!
        hh.next = head;
        ListNode *p = head;
        int count = 0;
        while(p){
            count++;
            p = p->next;
        }
        p = head;
        ListNode *pre = &hh;
        int lft = count - n;
        while(lft--){
            pre = p;
            p = p->next;
        }
        pre->next = p->next;
        return hh.next;
    }

方法二:一次扫描

如何做到一次扫描就能删除倒数第n个结点呢?可以将扫描时得到的各节点的地址记录到数组中,因为数字索引是O(1)的,故而可以做到一次扫描就完成。
代码如下:


    ListNode* removeNthFromEnd(ListNode* head, int n) {
        vector<ListNode *> vi;
        ListNode hh(-1);
        hh.next = head;
        vi.push_back(&hh);
        ListNode *p = head;
        while(p){
            vi.push_back(p);
            p = p->next;
        }
        vi[vi.size() - n - 1]->next = vi[vi.size() - n]->next;
        return hh.next;
    }

Leetcode #24 两两交换链表中的结点

题名:两两交换链表中的结点
描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/swap-nodes-in-pairs/

方法:

同样的,引入一个空头结点将简化很多思想和操作。要考虑链表中元素个数为奇数个的情况,此时最后一个结点无需交换。
代码如下:


    ListNode* swapPairs(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;
        ListNode hh(-1);
        ListNode *p = head, *q = head->next, *f = &hh;
        while(p && q){
            //需要四个指针: f,p,q,next;用于完成交换
            ListNode *next = q->next;
            f->next = q;
            q->next = p;
            p->next = next;
            //更新f,p,q
            f = p;
            p = next;
            if(p)    q = p->next;//考虑p是空指针的情况
        }
        return hh.next;
    }

Leetcode #328 奇偶链表

题名:奇偶链表
描述:给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/odd-even-linked-list/

方法:

又是一题List + Simulation的题,按照题意进行模拟即可。我们分成奇偶两个链表同时进行。


    ListNode* oddEvenList(ListNode* head) {
        if(!head || !head->next)    return head;//如果是空链表或链表结点数小于2,不需要操作直接返回;
        ListNode *oddp = head, *evenp = head->next, *p = head, *op, *ep;//oddp和evenp分别指向奇偶链表的表头;op和ep用于移动;
        while(p && p->next){//因为是对奇偶链表同时进行,所以要往后多看一个结点是否为空
            op = p;
            ep = p->next;
            p = p->next->next;//p指向下一次while开始处
            if(ep->next){//如果ep的下一个结点也非空
                op->next = ep->next;//将结点插入奇数链表
                if(ep->next->next){//如果ep的下下个结点非空
                    ep->next = ep->next->next;//插入偶数链表
                }
            }      
        }
        if(!p)  op->next = evenp;//这里要分情况;如果输入链表结点个数为偶数个,那么从while出来p一定是NULL
        else//如果输入链表结点个数为奇数个,那么从while出来p->next一定是NULL,而p是非空
            op->next->next = evenp;//将奇数链表的最后一个结点的next指针指向偶数链表表头,以串联起奇偶链表;
        ep->next = NULL;//令偶链表,表尾指向NULL;
        return head;
    }
原文地址:https://www.cnblogs.com/Codroc/p/12340821.html