LeetCode——链表

160. 相交链表

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

算法1:

  1. 先计算出两个链表的长度lenA和lenB,以及长度差diff
  2. 让指针p和q分别指向链表头部,让长链表先走过diff,使两链表的剩余长度相同
  3. 然后让p、q同时移动,相等时即为交点,或是同时到达结尾两值均为NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *p=headA;
    struct ListNode *q=headB;
    int lenA=0,lenB=0;
    while(p){
        lenA++;
        p=p->next;
    }
    while(q){
        lenB++;
        q=q->next;
    }
    p=headA;
    q=headB;
    int diff=lenA-lenB;
    if(diff>0){
        while(diff--){
            p=p->next;
        }
    }else{
        diff=-diff;
        while(diff--){
            q=q->next;
        }
    }
    while(p!=q){
        p=p->next;
        q=q->next;
    }
    return q;
}

算法2:

  1. 将两个链表相接,即A->B,B->A
  2. 此时让p从A头部出发,q从B头部出发,最终p将指向B最后一个节点,q指向A最后一个节点,因为两指针都走过A+B的总长
  3. 如果A、B有相交点,则必然会有一段路是p、q共同走过
  4. 因此,如果有交点,则在终点前就有p==q,反之,则p、q只有在终点时才同为NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *p=headA;
    struct ListNode *q=headB;
    if(p==NULL||q==NULL) return NULL;
    while(p!=q){
        p=p==NULL?headB:p->next;
        q=q==NULL?headA:q->next;
    }
    return p;
}

206. 反转链表

题目描述:反转一个单链表

算法1:迭代

使用头插法的思想,分别用prev和curr两指针前两个元素

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* curr=head;
    struct ListNode* prev=NULL;
    struct ListNode* tmp;
    while(curr){
        tmp=curr->next;
        curr->next=prev;
        prev=curr;
        curr=tmp;
    }
    return prev;
}

算法2:递归

image-20200412135812459

struct ListNode* reverseList(struct ListNode* head){
    if(head==NULL||head->next==NULL) return head;
    struct ListNode *p=reverseList(head->next);
    head->next->next=head;
    head->next=NULL;//避免产生循环
    return p;
}

21. 合并两个有序链表

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

算法1:迭代

  1. 设置一个头结点head和和一个尾指针res,初始时尾指针指向head
  2. 分别比较链表头结点处的值大小,较小的结点则接在头结点head后,并让res始终指向最后一个节点
  3. 最后将两链表剩余的部分接在最后
  4. 返回头结点的下一个结点
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode *res=(struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode *head=res;
    while(l1&&l2){
        if(l1->val>l2->val){
            res->next=l2;
            l2=l2->next;
        }else{
            res->next=l1;
            l1=l1->next;
        }
        res=res->next;
    }
    res->next=l1==NULL?l2:l1;//最终两链表至少有一个为空,如果都为空,则res->next=NULL
    return head->next;
}

算法2:递归

struct ListNode* mergeTwoLists(struct ListNode* l1, struct 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(l1,l2->next);
        return l2;
    }
}

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

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

算法1:迭代

  1. 删除一个结点必须要知道该结点的前一个结点
  2. 从头开始用指针p遍历链表,在指针p与p->next都不为NULL的情况下比较所指结点的值
  3. 注意释放空结点
struct ListNode* deleteDuplicates(struct ListNode* head){
    struct ListNode *p=head;
    while(p&&p->next){
        if(p->val==p->next->val){
            struct ListNode *tmp=p->next;
            p->next=p->next->next;
            free(tmp);
        }else{
            p=p->next;
        }
    }
    return head;
}

算法2:递归

  1. 递归终止条件:当前链表为空,或是只有一个元素
  2. 找返回值:返回给上层处理好的链表的头指针
  3. 本层中要完成的任务:从宏观上来看,如果当前结点的值与上层返回的头结点的值相等,则要删除当前的结点,即返回上一层的头结点,如果不等,则返回当前结点
struct ListNode* deleteDuplicates(struct ListNode* head){
    if(head==NULL||head->next==NULL) return head;
    head->next=deleteDuplicates(head->next);
    if(head->val==head->next->val) head=head->next;
    return head;
}

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

题目描述:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。给定的 n 保证是有效的。

算法1:快慢指针

先构造一个头结点dummy,方便对特殊情况的处理,让快指针从head处先走n个结点,然后让慢指针从dummy处和快指针一起走,直到快指针到达链表结尾,此时慢指针所在的位置即为要删除结点的前一个结点

总结:添加一个头结点,在第一个结点需要修改的情况下很好用

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    struct ListNode *fast = head;//快指针
    struct ListNode *dummy = malloc(sizeof(struct ListNode));//头结点
    dummy->next=head;
    struct ListNode *slow = dummy;//慢指针
    while(n--){
        fast=fast->next;
    }
    while(fast){
        slow=slow->next;
        fast=fast->next;
    }
    struct ListNode *tmp = slow->next;
    slow->next=slow->next->next;
    free(tmp);
    return dummy->next;
}

算法2:递归

思路:先递归到最后一个结点,在返回上一层的过程中,记录层数,如果为当前层数为n,则说明当前结点即为要删除的结点。

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    static int curr=0;
    if(head==NULL) return NULL;
    head->next=removeNthFromEnd(head->next,n);
    curr++;
    if(curr==n) return head->next;
    return head;
}

24. 两两交换链表中的节点

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

算法1:递归

  1. 递归终止条件:链表中只剩一个结点或是没有结点,此时无法交换,则返回head
  2. 找返回值:返回给上一层交换好的链表头结点
  3. 本层要完成的任务:从宏观上考虑,当前的链表由头结点,头结点的下一个结点,还有处理好的链表头结点,此时对这三个结点交换即可
struct ListNode* swapPairs(struct ListNode* head){
    if(head==NULL||head->next==NULL) return head;
    struct ListNode *p=head->next;
    head->next=swapPairs(head->next->next);
    p->next=head;
    return p;
}

算法2:迭代

利用两结点之前结点的指针prev即可完成反转

struct ListNode* swapPairs(struct ListNode* head){
    struct ListNode *dummy=malloc(sizeof(struct ListNode));
    dummy->next=head;
    struct ListNode *prev=dummy;

    while(prev->next&&prev->next->next){
        struct ListNode *l1=prev->next,*l2=prev->next->next;
        l1->next=l2->next;
        l2->next=l1;
        prev->next=l2;
        prev=l2->next;
    }
    return dummy->next;
}

445. 两数相加 II

题目描述:给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。

算法1:双栈

注意最高位的进位

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
         stack<int> st1,st2;
         while(l1){
             st1.push(l1->val);
             l1=l1->next;
         }
         while(l2){
             st2.push(l2->val);
             l2=l2->next;
         }
         int carry=0;
         ListNode *dummy=new ListNode(0);
         while(!st1.empty()||!st2.empty()||carry==1){
             int x1,x2;
             if(!st1.empty()){
                 x1=st1.top();
                 st1.pop();
             }else{
                 x1=0;
             }
             if(!st2.empty()){
                 x2=st2.top();
                 st2.pop();
             }else{
                 x2=0;
             }
             int sum=x1+x2+carry;
             ListNode *p=new ListNode(sum%10);
             carry=sum/10;
             p->next=dummy->next;
             dummy->next=p;
         }
         return dummy->next;
    }
};

算法2:递归

先将两个链表的长度补齐,然后递归求解

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
    int len1=0,len2=0;
    struct ListNode *h1=l1,*h2=l2;
    while(h1){
        len1++;
        h1=h1->next;
    }
    while(h2){
        len2++;
        h2=h2->next;
    }
    int diff=0;
    if(len1<len2){
        struct ListNode *tmp=l1;
        l1=l2;
        l2=tmp;
        diff=len2-len1;
    }else{
        diff=len1-len2;
    }
    while(diff--){
        struct ListNode *p=malloc(sizeof(struct ListNode));
        p->val=0;
        p->next=l2;
        l2=p;
    }
    int cout=0;
    addList(l1,l2,&cout);
    if(cout==1){
        struct ListNode *p=malloc(sizeof(struct ListNode));
        p->val=1;
        p->next=l1;
        return p;
    }
    return l1;
}
void addList(struct ListNode* l1,struct ListNode* l2,int *cout){
    if(l1==NULL||l2==NULL) return;
    addList(l1->next,l2->next,cout);
    int sum=l1->val+l2->val+*cout;
    l1->val=sum%10;
    *cout=sum/10;
    return;
}

234. 回文链表

题目描述:请判断一个链表是否为回文链表。你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

算法:

  1. 先求出中间结点

  2. 从中间结点处反转链表,反转算法如下图所示

    image-20200409164304115

  3. 比较前后两部分对应结点的值

bool isPalindrome(struct ListNode* head){
    int len=0;
    struct ListNode *p=head;
    while(p){
        len++;
        p=p->next;
    }
    if(len==1||len==0) return true;
    len=len%2?len/2+1:len/2;//奇数个结点时,中间结点设置为中心位置右边的结点
    struct ListNode *mid=head;
    while(len--){
        mid=mid->next;
    } 
    struct ListNode *prev=NULL;
    while(mid){
        struct ListNode *ovn=mid->next;
        mid->next=prev;
        prev=mid;
        mid=ovn;
    }
    while(prev&&head){
        if(prev->val!=head->val){
            return false;
        }
        prev=prev->next;
        head=head->next;
    }
    return true;
}

725. 分隔链表

思路:

  1. 先求出链表总长度len,len/k即为最短链长,len%k为最长链长的个数,最长的链长比最短链长多1
  2. 根据链长分配链表,这段代码尤其要注意考虑临界情况
struct ListNode** splitListToParts(struct ListNode* root, int k, int* returnSize){
    *returnSize=k;
    struct ListNode *p=root;
    int len=0;
    while(p){
        len++;
        p=p->next;
    }
    struct ListNode** res=(struct ListNode*)calloc(k,sizeof(struct ListNode));
    struct ListNode *prev=NULL,*head=root;//prev一定要初始化为NULL,没有初始化的指针很危险
    for(int i=0;i<k;i++){
        res[i]=head;
        int width=len/k+(i<len%k?1:0);
        for(int j=0;j<width;j++){
            prev=head;
            if(head) head=head->next;//这里的if可省略,不过有head->next还是加上比较好
        }
        if(prev) prev->next=NULL;
    }
    return res;
}
posted @ 2020-04-04 17:00  咸鱼不闲咋整啊  阅读(161)  评论(0编辑  收藏  举报