链表相关算法题总结 1

链表题目对算法的要求度不高,但实际写的过程中需要注意语言细节,考虑精细度的地方很多。

1.链表结构与基本操作

1.1 添加节点

一般情况:

cur ->next = prev ->next;
prev ->next = cur;

表头插入:

cur ->next = head;
head = cur;

1.2删除节点

一般情况:(已知待删除节点的前驱节点)

ListNode* temp = prev->next;
prev->next = prev->next->next;
delete temp;

表头元素删除:

ListNode* temp = head->next;
delete temp;
head = temp;

变形题目:(已知待删除节点,且不知道头指针位置,leetcode237 https://leetcode.com/problems/delete-node-in-a-linked-list/)

思路:将待删除节点后继节点内容拷贝至当前节点,然后删除后继节点,相当于以后继代替当前节点被删除

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val;
        ListNode* temp = node->next;
        node->next = node->next->next;
        delete temp;
    }
};

注意增删时边界条件的特别处理。

2.常见题型总结

2.1Remove Duplicates from Sorted List 1(leetcode83 https://leetcode.com/problems/remove-duplicates-from-sorted-list/)

Remove Duplicates from Sorted List 2(leetcode82  https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/)

思路:1 直接的思路,即遍历链表,发现相同元素则删除,否则继续下一步

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == NULL){
            return head;
        }
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        while(head->next != NULL){
            if(head->next->val == head->val){
                ListNode* temp = head->next;
                head->next = head->next->next;
                delete temp;
            }
            else{
                head = head->next;
            }
        }
        return dummy->next;
    }
};

2. 因为涉及删除所有重复元素,删除过程需要得到待删除元素的前驱节点,故采用head->next 与 head->next->next比较,保证可以实现删除工作

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
    if(head == NULL){
        return head;
    }
    ListNode* dummy = new ListNode(0);
    dummy->next = head;
    head = dummy;
    while(head->next != NULL&& head ->next->next != NULL ){
        if(head->next->val == head->next->next->val){
            int val = head->next->val;
            while(head->next != NULL && head->next->val == val){
                ListNode* curr = head->next;
                head->next = head->next->next;
                delete curr;
            }
        }
        else{
            head = head->next;
        }
    }
    return dummy->next;
    }
};

2.2 链表翻转相关题目

2.2.1 链表整体翻转(Leetcode 206 https://leetcode.com/problems/reverse-linked-list/)

思路:遍历链表,把当前节点作为已经翻转成功链表的新表头(头插法)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* result = NULL;
        while(head){
            ListNode* temp = head->next;
            head ->next = result;
            result = head;
            head = temp;
        }
        return result;
    }
};

2.2.2 链表部分翻转(Leetcode 92 https://leetcode.com/problems/reverse-linked-list-ii/)

思路:将链表看做三部分,即头到m,m到n,n到最后。

将m,n之间进行翻转后,将链表重新连接,注意处理m为1时的情况(利用dummy node,统一处理)

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        ListNode *dummy = new ListNode(0);
        dummy->next = head;
        head = dummy;
        for(int i = 0;i < m-1;i++){
            head = head->next;
        }
        ListNode* temp1 = head;
        head = head->next;
        ListNode* temp2 = head;
        ListNode* result = NULL;
        for(int i = m;i <= n; i++){
            ListNode* temp = head->next;
            head->next = result;
            result = head;
            head = temp;
        }
        temp1->next = result;
        temp2->next = head;
        return dummy->next;
    }
};

2.2.3 链表回文判断(Leetcode 234 https://leetcode.com/problems/palindrome-linked-list/)

思路:找到链表中点(方法见后续 two pointers),将后半部分翻转,与前半部分比较,得到是否回文。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if( head == NULL ||head ->next == NULL ){
            return true;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        while( fast->next != NULL &&  fast->next->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
        }
     //   ListNode* mid = slow;
        slow = slow->next;
        ListNode* result = NULL;
        while(slow != NULL){
            ListNode* temp = slow->next;
            slow->next = result;
            result = slow;
            slow = temp;
        }
        //mid->next = NULL;
        while( result != NULL){
            if(head->val != result->val){
                return false;
            }
            head = head->next;
            result = result->next;
        }
        return true;

    }
};

2.3 Two pointers 思路应用

2.3.1  寻找链表中点位置或某一特殊点位置  (Leetcode 19 https://leetcode.com/problems/remove-nth-node-from-end-of-list/)

思路: 快指针一次走两步,慢指针一次走一步,快指针到达链表末尾时,慢指针指向中点。

ListNode* findMiddle(ListNode* head){
        ListNode* chaser = head;
        ListNode* runner = head->next;
        while(runner != NULL && runner->next != NULL){
            chaser = chaser->next;
            runner = runner->next->next;
        }
        return chaser;
    }

一个指针先走n步,然后快慢指针一起走,快指针达到末尾时,慢指针达到待删除节点。

注意事项:删除首元素时往往出现问题,可使用dummy node使链表加哨兵,使链表可以统一处理。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head == NULL){
            return head;
        }
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        head = dummy;
        ListNode* slow = head;
        ListNode* fast = head;
        for(int i = 0; i <n; i++){
            fast = fast ->next;
        }
        while(fast->next != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* temp = slow->next;
        slow ->next = slow->next->next;
        delete temp;
        return dummy->next;
    }
};

2.3.2 给定单链表,找到二者交点(Leetcode 160 https://leetcode.com/problems/intersection-of-two-linked-lists/)

思路:计算链表长度,让较长链表先走差值的距离,后同时遍历,找到相同元素为止

class Solution {
public:
    int getLength(ListNode* head){
        int num = 0;
        while(head != NULL){
            head = head->next;
            num++;
        }
        return num;
    }
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lA = getLength(headA);
        int lB = getLength(headB);
        if(lA >= lB){
            for(int i = 0; i < lA - lB; i++){
                headA = headA->next;
            }
        }
        else{
            for(int i = 0;i < lB - lA; i++){
                headB = headB->next;
            }
        }
        while(headA != NULL && headB != NULL){
            if(headA == headB){
                return headA;
            }
            headA = headA->next;
            headB = headB->next;
        }
        return NULL;
    }
};

2.3.3 判断单链表是否有环

(Leetcode 141 142 https://leetcode.com/problems/linked-list-cycle/   https://leetcode.com/problems/linked-list-cycle-ii/)

思路: 1)Two pointers 一快一慢,有环的话,必然相遇。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL){
            return 0;
        }
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast!= NULL && fast->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast){
                return 1;
            }
        }
        return 0;
    }
};

2)

2(a + b) = a + b +n(b + c)

推出 a = (n-1)b + nc 即 a = (n-1)(b+c) + c

因为b+c是环的长度, 所以说讲两个指针分别指链表头和初始相遇位置,他们还会在环开始位置相遇,由此整理思路

① 同第一题,快慢指针判断是否存在环,并记录相遇的位置

② 将两指针分别放置在开头和相遇位置,同样速度推进,则根据推导应该在环开始位置相遇

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == NULL){
            return 0;
        }
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast != NULL && fast->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast){
                break;
            }
        }
        if(fast == NULL || fast->next == NULL){
            return NULL;
        }
        slow = head;
        while(slow != fast){
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

待续

时间: 2024-09-29 10:20:54

链表相关算法题总结 1的相关文章

二分查找的相关算法题

最近笔试经常遇到二分查找的相关算法题 1)旋转数组中的最小数字 2)在旋转数组中查找某个数 2)排序数组中某个数的出现次数 下面我来一一总结 1 旋转数组的最小数字 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. 实现数组的旋转见左旋转字符串. 和二分查找法一样,用两个指针分别指向数组的第一个元素和最后一个元素. 我们注意到旋转

【常用算法思路分析系列】链表相关高频题集

本文是[常用算法思路分析系列]的第四篇,总结链表相关的高频题目和解题思路.本文分析如下几个问题:1.环形链表的差值问题:2.只能访问单个结点的删除问题:3.链表的分化:4.打印两个链表的公共部分:5.把链表的每k个结点逆序:6.删除链表中指定结点:7.判断链表是否为回文结构:8.复杂链表的复制:9.判断链表是否有环:10.判断两个无环链表是否相交:11.判断两个有环链表是否相交:12.判断两个链表(状态未定)是否相交. 本系列前三篇导航: [常用算法思路分析系列]排序高频题集 [常用算法思路分析

单链表的算法题

单链表很简单,就是一些细节要注意. 多练练现场纸上手写. #include <iostream> using namespace std; struct node {     int key; node * next; node () : key(-1), next(NULL)  {} node (int n) : key(n), next(NULL)  {} }; void print (node * head) { while (head) { cout << head->

数据结构-单向链表相关算法

#include <stdio.h>#include <stdlib.h>#define OVERFLOW -2#define OK 1#define ERROR 0typedef int ElemType;//单向链表结构体typedef struct LNode {    ElemType data;    struct LNode *next;}LNode,*LinkList; LinkList CreateList_L(LinkList L,int n);void Trav

链表相关编程题总结

1.复制带随机指针的链表 class Solution(object): def copyRandomList(self, head): """ :type head: Node :rtype: Node """ if head is None: return None p = head while p: node = Node(x=p.val, next=p.next) p.next = node p = p.next.next p = hea

leetcode sum相关算法题

1. Two Sum(https://oj.leetcode.com/problems/two-sum/) 解题思路: 解法一: 暴力,O(n2)时间复杂度,TLE 解法二:利用hash, 记录下数组中每个值对应的下标,再遍历一遍数组,通过查看target-num[i]的值是否在map中来确定另一个数值.时间复杂度O(n) 解法三:对num数组排序,O(nlog(n)), 然后左右夹逼O(n). 但这道题要求记录下标,故这个方法行不通. python代码如下: 1 def twoSum(self

【常用算法思路分析系列】与二分搜索相关高频题

本文是[常用算法思路分析系列]的第五篇,总结二分搜索相关的高频题目和解题思路.本文分析如下几个问题:1.求数组局部最小值问题:2.元素最左出现的位置:3.循环有序数组求最小值:4.最左原位:5.完全二叉树计算结点数:6.快速N次方. 本系列前四篇导航: [常用算法思路分析系列]排序高频题集 [常用算法思路分析系列]字符串高频题集 [常用算法思路分析系列]栈和队列高频题集(修改版) [常用算法思路分析系列]链表相关高频题集 二分搜索的重要提醒: 一般我们选择中点进行搜索,会写成mid = (lef

算法题——翻转链表中的一段

题目:给出一个链表中的两个指针p1和p2,将其之间的结点翻转. 思路:可以通过交换结点内的值来实现结点的翻转,空间为O(N):如果要求不能交换值,那么仅凭p1和p2是无法翻转的,只能交换两个指针之间的链表. 代码: 交换值: 1 struct ListNode 2 { 3 int val; 4 ListNode *next; 5 }; 6 7 void reverseNodes(ListNode *p1, ListNode *p2) { 8 if ( p1 == NULL || p2 == NU

算法题:复制复杂链表之空间换时间法

说明:本文仅供学习交流,转载请标明出处,欢迎转载!  题目:复制一个复杂链表,所谓复杂链表指的是每个节点含有两个指针,一个指向单链表的下一个结点,一个指向单链表中的任意某个结点,或者该指针为空. 为了方便起见,我们将待复制的链表称为原型链表,将复制后的新链表称为复制链表,将指向下一个结点的指针定义为next指针,指向其他位置的指针定义为any指针.<剑指offer>上给出了三种解决方法:(1)常规法:(2)空间换时间法:(3)紧随复制法.书上并给出了第三种方法的实现代码.这里我根据书上的提示,