剑指offer 关于链表的例题 —— 链表的环的入口结点、倒数第k个结点、两个链表的第一个公共结点、反转链表、合并两个排序的链表、删除排序链表中的重复元素

链表热点题目

1、链表的环的入口结点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == NULL || pHead->next == NULL || pHead->next->next == NULL)
            return NULL;
        ListNode* SlowNode = pHead->next;
        ListNode* FastNode = pHead->next->next;
        while(SlowNode != FastNode)
        {
            if(FastNode->next == NULL || FastNode->next->next == NULL)
                return NULL;
            SlowNode = SlowNode->next;
            FastNode = FastNode->next->next;
        }
        FastNode = pHead;
        while(SlowNode != FastNode)
        {
            SlowNode = SlowNode->next;
            FastNode = FastNode->next;
        }
        return SlowNode;
    }
};

2、倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == NULL || k < 1)
            return NULL;
        ListNode* fast = pListHead;
        while(--k)
        {
            fast = fast->next;
            if(fast == NULL)
                return NULL;
        }
        ListNode* slow = pListHead;
        while(fast->next != NULL)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

3、两个链表的第一个公共结点

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == NULL || pHead2 == NULL)
            return NULL;
        int node = 0;
        ListNode* p1 = pHead1;
        while(p1->next != NULL)
        {
            node++;
            p1 = p1->next;
        }
        ListNode* p2 = pHead2;
        while(p2->next != NULL)
        {
            node--;
            p2 = p2->next;
        }
        if(p1 != p2)
            return NULL;
        p1 = node > 0 ? pHead1:pHead2;
        p2 = p1 == pHead1 ? pHead2 : pHead1;
        node = abs(node);
        while(node > 0)
        {
            node--;
            p1 = p1->next;
        }
        while(p1 != p2)
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

4、反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
 
限制:
0 <= 节点个数 <= 5000

非递归方法:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL)
            return NULL;
        ListNode* Pnow = pHead;
        ListNode* Ppre = NULL;
        while(Pnow->next!=NULL)
        {
            ListNode* Pnext = Pnow->next ;
            Pnow->next = Ppre;
            Ppre = Pnow;
            Pnow = Pnext;
        }
        Pnow->next = Ppre;
        return Pnow;
    }
};

递归方法:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
          return NULL;
        if(head->next == NULL)
           return head;
        ListNode* last = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return last;
    }
};

LeetCode92. 反转链表 II

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

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

示例:

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


非递归

对于链表的问题,根据以往的经验一般都是要建一个dummy node,连上原链表的头结点,这样的话就算头结点变动了,我们还可以通过dummy->next来获得新链表的头结点。

这道题的要求是只通过一次遍历完成,就拿题目中的例子来说,变换的是2,3,4这三个点,我们需要找到第一个开始变换结点的前一个结点,只要让pre向后走m-1步即可。

为啥要减1呢,因为题目中是从1开始计数的,这里只走了1步,就是结点1,用pre指向它。万一是结点1开始变换的怎么办,这就是我们为啥要用dummy结点了,pre也可以指向dummy结点。然后就要开始交换了,由于一次只能交换两个结点,所以我们按如下的交换顺序:

1 -> 2 -> 3 -> 4 -> 5 -> NULL

1 -> 3 -> 2 -> 4 -> 5 -> NULL

1 -> 4 -> 3 -> 2 -> 5 -> NULL

我们可以看出来,总共需要n-m步即可,第一步是将结点3放到结点1的后面,第二步将结点4放到结点1的后面

这是很有规律的操作,那么我们就说一个就行了,比如刚开始,

  • pre指向结点1,cur指向结点2,然后我们建立一个临时的结点t,指向结点3(注意我们用临时变量保存某个结点就是为了首先断开该结点和前面结点之间的联系,这可以当作一个规律记下来),
  • 然后我们断开结点2和结点3,将结点2的next连到结点4上,也就是 cur->next = t->next,再把结点3连到结点1的后面结点(即结点2)的前面,即 t->next = pre->next
  • 最后再将原来的结点1和结点2的连接断开,将结点1连到结点3,即 pre->next = t。这样我们就完成了将结点3取出,加入结点1的后方。

第二步将结点4取出,加入结点1的后方,也是同样的操作,这里就不多说了,请大家自己尝试下吧,参考代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        ListNode* dummy=new ListNode(-1);
        ListNode* pre=dummy;
        dummy->next=head;
        
        for(int i=0;i<m-1;i++)
            pre=pre->next;
        ListNode* cur=pre->next;
        for(int i=m;i<n;i++){
            ListNode* t=cur->next;//t = 3
            cur->next=t->next;// 2->4
            t->next=pre->next;//3->2
            pre->next=t;//1->3 ===>>>> 1->3 ->2->4
        }
        return dummy->next;
    }
};

递归

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* successor = NULL;
    ListNode* reverseN(ListNode* head, int n) {
        if (n == 1) { 
            // 记录第 n + 1 个节点
            successor = head->next;
            return head;
        }
        // 以 head.next 为起点,需要反转前 n - 1 个节点
        ListNode* last = reverseN(head->next, n - 1);

        head->next->next = head;
        // 让反转之后的 head 节点和后面的节点连起来
        head->next = successor;
        return last;
    }  

    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // base case
    if (m == 1) {
        return reverseN(head, n);
    }
    // 前进到反转的起点触发 base case
    head->next = reverseBetween(head->next, m - 1, n - 1);
    return head;

    }
};

5、合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL)
            return pHead2;
        if(pHead2 == NULL)
            return pHead1;
        ListNode* dummy = new ListNode(0);
        ListNode* newHead = dummy;
        while(pHead1 != NULL && pHead2 != NULL)
        {
            if(pHead1->val < pHead2->val)
            {
                newHead->next = pHead1;
                pHead1 = pHead1->next;
            }
            else
            {
                newHead->next = pHead2;
                pHead2 = pHead2->next;
            }
            newHead = newHead->next;
        }
        newHead->next = pHead1 == NULL ? pHead2:pHead1;
        ListNode* temp = dummy;
        dummy = dummy->next;
        delete temp;
        temp = NULL;
        return dummy;
    }
};

合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

 

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6


示例 2:

输入:lists = []
输出:[]


示例 3:

输入:lists = [[]]
输出:[]
 

提示:

k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
   struct cmp 
{
	bool operator()(ListNode *a, ListNode *b)
	{
		return a->val > b->val;
	}
};

ListNode* mergeKLists(vector<ListNode*>& lists)
{
		 if(lists.size() == 0)
            return NULL;
        priority_queue<ListNode*, vector<ListNode*>, cmp> pri_queue;
		for (auto elem : lists) 
		{
			if (elem) 
				pri_queue.push(elem);
		}
		ListNode* dummy = new ListNode(-1);
		ListNode* p = dummy;
		while (!pri_queue.empty()) 
		{
			ListNode* top = pri_queue.top(); pri_queue.pop();
			p->next = top; p = top;
			if (top->next) pri_queue.push(top->next);
		}
		return dummy->next;
}
};

6、删除排序链表中的重复元素

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

示例 1:

输入: 1->1->2
输出: 1->2
示例 2:

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

双指针方法:
1、slowNode:牢牢锁死当前非重复元素的位置,它之前包括它自己都说非重复的元素;
2、fastNode:链表遍历节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;
        ListNode* slowNode = head;
        ListNode* fastNode = head->next;
        while(fastNode != NULL)
        {
            if(fastNode->val != slowNode->val)
            {
                slowNode = slowNode->next;
                slowNode->val = fastNode->val;
            }
            fastNode = fastNode->next;
        }
        slowNode->next = NULL;//链表最后的节点指向空。
        return head;
    }
};

强化版:

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead == NULL || pHead->next==NULL )
            return pHead;
        ListNode* dummy = new ListNode(-1);
        ListNode* pre = dummy;
        ListNode* cur = pHead;
        while(cur!= NULL &&cur->next !=  NULL)
        {
            if( cur->val == cur->next->val)
            {
                while(cur->next != NULL && cur->next->val == cur->val  )
                {
                    cur = cur->next;
                }
                pre->next = cur->next;
                cur = cur->next;
            }
            else
            {
                pre->next = cur;
                cur = cur->next;
                pre = pre->next;
            }
        }
        ListNode* temp = dummy;
        dummy = dummy->next;
        delete temp;
        temp = NULL;
        return dummy;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值