链表问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiangxishidayuan/article/details/50810948

1. 在O(1)时间删除链表节点

主要思想: “狸猫换太子”, 即用下一个节点的数据覆盖删除节点的数据,然后删除下一个节点。

//第一种方法
//O(1)时间删除链表节点,从无头单链表中删除节点
void deleteRandomNode(Node *cur) {
    assert(cur != NULL);
    assert(cur->next != NULL);    //不能是尾节点
    Node* pNext = cur->next;
    cur->data = pNext->data;
    cur->next = pNext->next;
    delete pNext;
}

//平均复杂度为O(1)
void deleteNode(ListNode** head, ListNode* toBeDeleted) {
    //如果都为空 直接返回
    if(!head || !toBeDeleted)
        return;
    if(toBeDeleted->next != null) {
        ListNode* next = toBeDeleted->next;
        toBeDeleted->value = next->value;
        toBeDeleted->next = next->next;
        delete next;
        next = null;
    }else if(*head == toBeDeleted) {
        delete toBeDeleted;
        toBeDeleted = null;
        *head = null;
    }else {
        ListNode* node = *head;
        while(node->next != toBeDeleted) {
            node = node->next;
        }
        node->next = null;
        deleted toBeDeleted;
        toBeDeleted = null;
    }
}

2. 单链表的转置

添加一个dummy节点,统一化操作

//第一种
public class Solution {
    public ListNode reverseList(ListNode head) {
        // write your code here
        if(head == null)
            return head;
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        ListNode first = pre.next;
        while(first.next != null){
            ListNode second = first.next;
            first.next= second.next;
            second.next= pre.next;
            pre.next = second;
        }
        return dummy.next;
 }

//第二种方法
//单链表的转置,循环方法
Node* reverseByLoop(Node *head)
{
    if(head == NULL || head->next == NULL)
        return head;
    Node *pre = NULL;
    Node *next = NULL;
    while(head != NULL)
    {
        next = head->next;

        head->next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

//单链表的转置,递归方法
Node* reverseByRecursion(Node *head)
{
    //第一个条件是判断异常,第二个条件是结束判断
    if(head == NULL || head->next == NULL) 
        return head;

    Node *newHead = reverseByRecursion(head->next);

    head->next->next = head;
    head->next = NULL;

    return newHead;    //返回新链表的头指针
}

3. 求倒数第k个节点

设置两个指针 p1、p2,首先 p1 和 p2 都指向 head,然后 p2 向前走 k 步,这样 p1 和 p2 之间就间隔 k 个节点,最后 p1 和 p2 同时向前移动,直至 p2 走到链表末尾。

//倒数第k个节点
Node* theKthNode(Node *head,int k) {
    if(k < 0) return NULL;    //异常判断

    Node *slow,*fast;
    slow = fast = head;
    int i = k;
    for(;i>0 && fast!=NULL;i--) {
        fast = fast->next;
    }

    if(i > 0)    return NULL;    //考虑k大于链表长度的case

    while(fast != NULL) {
        slow = slow->next;
        fast = fast->next;
    }

    return slow;
}

3. 1 删除倒数第K个节点

public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(n < 0)
            return null;
        ListNode fast, slow;
        fast = slow = head;
        int i = n;
        for(; i > 0 && fast != null; i--)
            fast = fast.next;
        if(i > 0)
            return null;

        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        while(fast != null) {
            fast = fast.next;
            slow = slow.next;
            pre = pre.next;
        }
        pre.next = slow.next;
        slow = null;
        return dummy.next;
    }
}

3.2 旋转链表

Given 1->2->3->4->5->NULL and k = 2,
return 4->5->1->2->3->NULL.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(k < 0)
            return null;
        if(head == null || k == 0)
            return head;

        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode fast = dummy;

        int len = getListLength(head);
        //注意k可以大于链表的长度
        k = k % len;
        for(int i = 0;i < k; i++) {
            fast = fast.next;
        }
        ListNode slow = dummy;
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        fast.next = dummy.next;
        dummy.next = slow.next;
        slow.next = null;
        return dummy.next;
    }

    private int getListLength(ListNode head) {
        int length = 0;
        while(head != null) {
            head = head.next;
            length++;
        }
        return length;
    }
}

4. 求链表的中间节点

用两个指针从链表头节点开始,一个指针每次向后移动两步,一个每次移动一步,直到快指针移到到尾节点,那么慢指针即是所求。

//求链表的中间节点
Node* theMiddleNode(Node *head) {
    if(head == NULL)
        return NULL;
    Node *slow,*fast;
    slow = fast = head;
    //如果要求在链表长度为偶数的情况下,返回中间两个节点的第一个,可以用下面的循环条件
    //while(fast && fast->next != NULL && fast->next->next != NULL)  
    while(fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

5. 判断单链表是否存在环

通过两个指针,分别从链表的头节点出发,一个每次向后移动一步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。

//判断单链表是否存在环,参数circleNode是环内节点,后面的题目会用到
bool hasCircle(Node *head,Node *&circleNode) {
    Node *slow,*fast;
    slow = fast = head;
    while(fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow) {
            circleNode = fast;
            return true;
        }
    }

    return false;
}

6. 找到环的入口点

按照 p2 每次两步,p1 每次一步的方式走,发现 p2 和 p1 重合,确定了单向链表有环路了。接下来,让p2回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当 p1 和 p2 再次相遇的时候,就是环路的入口了。

//找到环的入口点
Node* findLoopPort(Node *head) {
    //如果head为空,或者为单结点,则不存在环
    if(head == NULL || head->next == NULL) return NULL;

    Node *slow,*fast;
    slow = fast = head;

    //先判断是否存在环
    while(fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
            break;
    }

    if(fast != slow) return NULL;    //不存在环

    fast = head;                //快指针从头开始走,步长变为1
    while(fast != slow) {            //两者相遇即为入口点
        fast = fast->next;
        slow = slow->next;
    }

    return fast;
}

7. 判断两个链表是否相交(无环)

如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。

那么,我们只要判断两个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。

//判断两个链表是否相交
bool isIntersect(Node *h1,Node *h2){
    if(h1 == NULL || h2 == NULL) return false;    //异常判断
    while(h1->next != NULL) {
        h1 = h1->next;
    }

    while(h2->next != NULL) {
        h2 = h2->next;
    }

    if(h1 == h2) return true;        //尾节点是否相同
    else return false;
}

8. 链表有环,如何判断相交

如果有环且两个链表相交,则两个链表都有共同一个环,即环上的任意一个节点都存在于两个链表上。因此,就可以判断一链表上俩指针相遇的那个节点,在不在另一条链表上。

//判断两个带环链表是否相交
bool isIntersectWithLoop(Node *h1,Node *h2)
{
    Node *circleNode1,*circleNode2;
    if(!hasCircle(h1,circleNode1))    //判断链表带不带环,并保存环内节点
        return false;                //不带环,异常退出
    if(!hasCircle(h2,circleNode2))
        return false;

    if(circleNode2 == circleNode1) 
        return true;
    Node *temp = circleNode2->next;
    while(temp != circleNode2) {
        if(temp == circleNode1)
            return true;
        temp = temp->next;
    }
    return false;
}

9. 两个链表相交的第一个公共节点

采用对齐的思想。计算两个链表的长度 L1 , L2,分别用两个指针 p1 , p2 指向两个链表的头,然后将较长链表的 p1(假设为 p1)向后移动L2 - L1个节点,然后再同时向后移动p1 , p2,直到 p1 = p2。相遇的点就是相交的第一个节点。

//求两链表相交的第一个公共节点
Node* findIntersectNode(Node *h1,Node *h2) {
    int len1 = listLength(h1);          //求链表长度
    int len2 = listLength(h2);
    //对齐两个链表
    if(len1 > len2) {
        for(int i=0;i<len1-len2;i++)
            h1=h1->next;
    }
    else {
        for(int i=0;i<len2-len1;i++)
            h2=h2->next;
    }

    while(h1 != NULL){
        if(h1 == h2)
            return h1;
        h1 = h1->next;
        h2 = h2->next;    
    }
    return NULL;
}

10 相加

You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        if(l1 == null)
            return l2;
        if(l2 == null)
            return l1;
        ListNode dummy = new ListNode(-1);
        dummy.next = null;
        ListNode l3 = dummy;
        int carry = 0;
        while(l1 != null || l2 != null) {
            if(l1 != null) {
                carry += l1.val;
                l1 = l1.next;
            }
            if(l2 != null) {
                carry += l2.val;
                l2 = l2.next;
            }
            ListNode cur = new ListNode(carry % 10);
            carry /= 10;
            l3.next = cur;
            l3 = cur;
        }
        if(carry == 1) {
            l3.next = new ListNode(1);
        }
        return dummy.next;
    }
}

11 总结

在链表的问题中,通过两个的指针来提高效率是很值得考虑的一个解决方案

猜你喜欢

转载自blog.csdn.net/jiangxishidayuan/article/details/50810948