02-链表练习1——反转链表

1.指定范围的反转
1.1 题目

将一个链表m位置到n位置之间的区间反转,要求使用原地算法,并且在一次扫描之内完成反转。
例如:给出的链表为1->2->3->4->5->NULL, m = 2 ,n = 4, 返回1->4->3->2->5->NULL.
注意: 给出的m,n满足以下条件: 1 ≤ m ≤ n ≤ 链表长度
下面是C/C++语法实现

/**
 * 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) {
        
    }
};

题目来源:

https://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c?tpId=46&tqId=29086&tPage=3&rp=3&ru=/ta/leetcode&qru=/ta/leetcode/question-ranking

1.2 题解
class Solution {
public:
    ListNode *reverseBetween(ListNode *head, int m, int n) {
    ListNode *dummy = (ListNode *)malloc(sizeof(ListNode));
    dummy->next = *head;
    ListNode* preStart = dummy->next; // 注意:preStart必须指向head
    ListNode* start = (*head)->next; // 这里的head不是首元素,而是头结点
    for(int i = 1; i < m; ++i){ // 执行后,start执行索引为m的节点
 	 preStart = start;      // preStart 指向start前一个节点
 	 start = start->next;
    }
   for(int i = 0; i < (n-m); ++i){ // 每一次循环,把start后一节点放在m位置
   	LinkList tmp = start->next; // 循环过程中,start指向始终不变
   	start->next = tmp->next;
  	tmp->next = preStart->next;
   	preStart->next = tmp;
   }
   return dummy->next;
}
    }
};

备注:这里的题解没有验证m和n范围的合法性/有效性,现实中肯定要考虑进去。

1.3 题析与测试用例

理解该方法的一个很好类别:
不妨拿出四本书,摞成一摞(自上而下为 A B C D),要让这四本书的位置完全颠倒过来(即自上而下为 D C B A):
盯住书A,每次操作把A下面的那本书放到最上面
初始位置:自上而下为 A B C D
第一次操作后:自上而下为 B A C D
第二次操作后:自上而下为 C B A D
第三次操作后:自上而下为 D C B A

来源:https://www.nowcoder.com/questionTerminal/b58434e200a648c589ca2063f1faf58c?f=discussion

以我的第2篇博客-链表基础(https://blog.csdn.net/qq_42800075/article/details/105270136)的单链表代码为例:

 LinkList reverseBetween(LinkList* head, int m, int n){
	LinkList dummy = (LinkList)malloc(sizeof(SingleLinkedList));
	dummy->next = *head;
	LinkList preStart = dummy->next;; // 意:preStart必须指向head,
	LinkList start = (*head)->next;  // 这里的head不是首元素,而是头结点
	for(int i = 1; i < m; ++i){ // 执行后,start执行索引m的节点
	  	preStart = start;  // preStart 指向start前一个节点
	  	start = start->next;
	}
	for(int i = 0; i < (n-m); ++i){ // 每一次循环,把start后一节点放在m位置
		LinkList tmp = start->next; // 循环过程中,start指向始终不变
		start->next = tmp->next;
		tmp->next = preStart->next;
	 	preStart->next = tmp;
	}
	return dummy->next;
	}

在main.c的删除操作后面新增一行,如下所示
main.c修改
运行结果
反转后的运行结果
该方法的时间复杂度为O(n),空间复杂度为O(1)

这种链表反转类似汉诺塔的堆叠,每一次操作(循环)的结果都把次大的堆叠在下面。
由此循环即可完成汉诺塔的移动或链表的反转。

2.链表全反转
2.1 题目
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。链表节点定义如下
struct ListNode
{
	int	m_nKey;
	ListNode* m_pNext;
}
2.2 题解
ListNode* ReverseList(ListNode* pHead)
{
	ListNode* pReversedHead = nullptr;
	ListNode* pPrev = nullptr;
	while(pNode != nullptr)
	{
	    ListNode* pNext = pNode->m_pNext;
	    if(pNext == nullptr)
	        pReversedHead = pNode;
	    pNode->m_pNext = pPrev;
	    pPrev = pNode;
	    pNode = pNext;						
	}
	return pReversedHead;
}
2.3 题析与测试用例

上述题解对应的测试用例可参考如下链接:

https://github.com/zhedahht/CodingInterviewChinese2/edit/master/24_ReverseList/ReverseList.cpp

这里再贴出对应我上篇博客单链表实现对应的反转链表函数,如下

// 链表全反转
LinkList reverseAll(LinkList * head)
{
	//备注:LinkList 是 SingleLinkedList *
	LinkList dummy = (LinkList)malloc(sizeof(SingleLinkedList));
 	dummy->next = *head;		   
	LinkList start = dummy->next->next;  //注意这里!因为head是头结点,不是首元素!
	LinkList preStart = NULL;
	while(start != NULL){
 		LinkList nextStart = start->next;
  		if(nextStart == NULL)
 			dummy->next->next = start; // 注意这里,是next->next
 		start->next = preStart;
  		preStart = start;
  		start = nextStart;
 	}
 return dummy->next;
 }

测试结果
链表反转结果图
思路分析:相比指定范围的反转,思路是不同的。
指定范围的反转:每一次循环更新m位置后一节点到m位置,如此循环(n-m)次即可反转[m,n]范围的元素指向
全反转:三个指针分别记录当前节点,前一个节点,后一节点。每一次循环将当前节点指向前一节点。很明显,有
多少个元素就需反转多少次,即时间复杂度是O(n),空间复杂度O(1).

代码写完就完事啦???
其实不然,最后且最重要一点:一定要用几个“测试用例”验证。如
1)输入的链表头指针是nullptr/NULL
2)输入的链表只有一个节点
3)输入的链表有多个节点

总结一下:玩链表就是玩指针,玩数组就是玩下标(谱哥的精辟总结<可关注微信公众号“编程剑谱”>)。
同时,链表问题尤其需注意以下三个问题:
1)输入的链表头指针为nullptr或整个链表只有一个节点时,程序立即崩溃
2)反转后的链表出现断裂
3)返回的反转之后的头节点不是原始链表的尾节点

下一篇:继续链表练习…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值