链表
Leetcode #160 相交链表
题名:相交链表
描述:编写一个程序,找到两个单链表相交的起始节点。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
方法1:
1.利用指针及链表特性数出两个链表各自长度
2.使较长的链表的头指针先向前移动多出来的长度,使得两链表从当前位置处开始长度相等
3.移动两个指针,直到两个指针值相等时退出循环
4.返回该指针
这个方法很显而易见,就不具体阐述了
方法2:
我们可以得出链表长度由两部分组成,一部分是公共部分,一部分是非公共部分。
要点:如果我知道了A链表和B链表各自非公共部分的长度,那么只要走完这个非公共部分的长度,指针所指即是答案。
假设有链表A、B。 A链表非公共部分长度为a,B链表非公共部分长度为b,公共部分长度为c,那么我们可以可以很容易得出的一个等式: a + c + b = b + c + a其中左边的 a + c 和右边的 b + c 其实就是两个链表各自的长度了。那么这句等式具体是什么意思呢?
我们可以这样理解:PA 和 PB刚开始是分别指向链表A、B的指针,不断的令PA、PB往下一个结点移动,当PA走完A链表的长度后(也就是走完a + c的距离后),令PA指向B链表的头部,再走一个b的距离(也就是B链表非公共部分的长度),这不就实现了我们的那个要点嘛!!!!
上代码:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *la = headA, *lb = headB;
while(la != lb)
{
la = (la == NULL)? headB : la->next;
lb = (lb == NULL)? headA : lb->next;
}
return la;
}
Leetcode #206 翻转链表
题名:翻转链表
描述:反转一个单链表。分别写出迭代和递归算法。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/reverse-linked-list/
方法1:迭代
迭代无非就是按顺序从头遍历,在遍历的同时就行翻转操作!
我们可以设置连个三个指针, pre, cur, next。初始时,pre指向NULL,cur指向头结点(注意这里的头结点也是元素节点,并不是空节点),next指向头结点的下一个结点。
用这三个指针遍历整个数组,每一次将cur的指针指向pre所指的结点,next用于记录下一个结点的位置。结束条件是当cur为空。代码如下:
ListNode* reverseList(ListNode* head) {
ListNode *pre = NULL, *cur = head;
while(cur)
{
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
还有一种方法,速度要快得多,当然也消耗了多余的空间,是空间换时间的思想。它就是多申请了一个头结点,然后用头插法进行翻转。代码如下:
ListNode* reverseList(ListNode* head) {
ListNode *nh = new ListNode(-1);
while(head)
{
ListNode *next = head->next;
head->next = nh->next;
nh->next = head;
head = next;
}
return nh->next;
}
方法2:递归
递归算法思想还是比较好像的,关键在于细节。
此处再次回顾一下递归的思想:把大问题化成具有相同性质的小问题,在解决小问题后再自然而然地解决大问题。
关键是:1.递归出口 2.递归式 3.返回值 4.具体实现
这个翻转链表的问题就是一个典型的递归问题。将一个较长的链表进行翻转,那我就先去翻转较短的子链表。例如要对1->2->3->4->5进行翻转,我就先去翻转2->3->4->5,要对2->3->4->5进行翻转我就先去翻转3->4->5,知道最后仅仅翻转5,这么个最小的问题,所以递归出口也就在这里了。这样大概的就可以写出一个雏形:
ListNode* reverseList(ListNode* head) {
//先写递归出口
if(head == NULL || head->next == NULL)
return head;
//再写递归式
ListNode *next = head->next;//指向下一个较短链表
ListNode *newhead = reverseList(next);//翻转这个较短的链表
......
return newhead;//返回值可以马上就确定下来,就是指向翻转后新链表的头指针
}
这样雏形就出来了,这时候就应该想如何去翻转某个具体的链表。例如翻转4->5,head是指向4的,next是指向5的,那么要变成5->4,就是next->next = head; head->next = NULL;所以具体代码如下:
ListNode* reverseList(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
ListNode *next = head->next;
ListNode *reversed = reverseList(next);
next->next = head;
head->next = NULL;
return reversed;
}
Leetcode #21 合并链表
题名:合并链表
描述:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/merge-two-sorted-lists/
方法1:常规遍历
将两个链表合并成一个链表,是链表基本操作!
为了思考起来比较方便,我们可以引入一个头结点,然后设置一个用于移动遍历的指针p,指向头结点。对l1,l2进行遍历每次穿起较小的那个节点,代码如下:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *newlist = new ListNode(-1);
ListNode *p = newlist;
while(l1 && l2)
{
if(l1->val <= l2->val)
{
p->next = l1;
p = l1;
l1 = l1->next;
}
else
{
p->next = l2;
p = l2;
l2 = l2->next;
}
}
while(l1)
{
p->next = l1;
p = l1;
l1 = l1->next;
}
while(l2)
{
p->next = l2;
p = l2;
l2 = l2->next;
}
return newlist->next;
}
方法2:递归
递归思想回顾:
1.递归出口; 显而易见,当链表为空时,就到了递归的边界了。
2.递归式;
3.返回值; 因为是单链表,只有知道链表头指针才能遍历整个链表,所以返回值是指向新链表的指针。
4.具体实现; 取整个递归过程中,某一层(只需要一层,而不需要完全搞清楚整个递归栈是如何运作的)递归的实现即是具体实现。
其中递归式,就是用来把大问题化解为小问题的。
具体实现要取整个递归过程中的某一层递归,取设计其实现。例如这一题:(取第一层递归)假设我们有两链表L1: 1->3->5->7
L2: 2->4->6->8
;
因为要求升序,所以合并1->3->5->7和2->4->6->8,相当先合并3->5->7和2->4->6->8并返回新合并链表的头指针newlist(由递归的正确性,我们可以得知newlist链表就是2->3->4->5->6->7->8,而newlist是指向2的),再使1->(指向)返回的头指针newlist,结果就是1->2->3->4->5->6->7->8
具体代码如下:
ListNode* mergeTwoLists(ListNode* l1, 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(l2->next, l1);
return l2;
}
}
Leetcode #83 删除排序链表中重复的元素
题名:deleteDuplicates
描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
方法一:
因为链表是sorted已排序的,所以可以用双指针法,然后遍历整个链表,较为简单不再叙述,上代码:
ListNode* deleteDuplicates(ListNode* head) {
ListNode *p = head, *q;
if(p) q = p->next;
bool ndd = false;
while(q != NULL)
{
if(p->val == q->val)
ndd = true;
else
{
if(ndd)
{
p->next = q;
ndd = false;
}
p = q;
}
q = q->next;
}
if(ndd)
p->next = q;
return head;
}
方法二:
可以用递归的方法,能减少代码量:
这里递归式就是要加一个判断。假设有p,q指针,当p->val != q->val
时,再用递归,代码如下:
ListNode* deleteDuplicates(ListNode* head) {
if(!head)
return head;
ListNode *p = head, *q = head->next;
while(q && p->val == q->val) //加一个判断
q = q->next;
ListNode *res = deleteDuplicates(q);
head->next = res;
return head;
}
Leetcode #19 删除链表倒数第n个结点
题名:删除链表倒数第n个结点
描述:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
方法一:
需要扫描2遍链表,第一遍是计算出链表总长度m,令ltf = m - n 再从头扫描到ltf处,删除该位置的结点即可。链表操作一般引入一个空头结点会比较方便。
代码如下:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode hh(-1);//引入一个头结点;在栈空间中,退出该函数后会自动销毁;如果要new一个则在堆空间中,记得最后要自己释放该空间!
hh.next = head;
ListNode *p = head;
int count = 0;
while(p){
count++;
p = p->next;
}
p = head;
ListNode *pre = &hh;
int lft = count - n;
while(lft--){
pre = p;
p = p->next;
}
pre->next = p->next;
return hh.next;
}
方法二:一次扫描
如何做到一次扫描就能删除倒数第n个结点呢?可以将扫描时得到的各节点的地址记录到数组中,因为数字索引是O(1)的,故而可以做到一次扫描就完成。
代码如下:
ListNode* removeNthFromEnd(ListNode* head, int n) {
vector<ListNode *> vi;
ListNode hh(-1);
hh.next = head;
vi.push_back(&hh);
ListNode *p = head;
while(p){
vi.push_back(p);
p = p->next;
}
vi[vi.size() - n - 1]->next = vi[vi.size() - n]->next;
return hh.next;
}
Leetcode #24 两两交换链表中的结点
题名:两两交换链表中的结点
描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/swap-nodes-in-pairs/
方法:
同样的,引入一个空头结点将简化很多思想和操作。要考虑链表中元素个数为奇数个的情况,此时最后一个结点无需交换。
代码如下:
ListNode* swapPairs(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
ListNode hh(-1);
ListNode *p = head, *q = head->next, *f = &hh;
while(p && q){
//需要四个指针: f,p,q,next;用于完成交换
ListNode *next = q->next;
f->next = q;
q->next = p;
p->next = next;
//更新f,p,q
f = p;
p = next;
if(p) q = p->next;//考虑p是空指针的情况
}
return hh.next;
}
Leetcode #328 奇偶链表
题名:奇偶链表
描述:给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/odd-even-linked-list/
方法:
又是一题List + Simulation的题,按照题意进行模拟即可。我们分成奇偶两个链表同时进行。
ListNode* oddEvenList(ListNode* head) {
if(!head || !head->next) return head;//如果是空链表或链表结点数小于2,不需要操作直接返回;
ListNode *oddp = head, *evenp = head->next, *p = head, *op, *ep;//oddp和evenp分别指向奇偶链表的表头;op和ep用于移动;
while(p && p->next){//因为是对奇偶链表同时进行,所以要往后多看一个结点是否为空
op = p;
ep = p->next;
p = p->next->next;//p指向下一次while开始处
if(ep->next){//如果ep的下一个结点也非空
op->next = ep->next;//将结点插入奇数链表
if(ep->next->next){//如果ep的下下个结点非空
ep->next = ep->next->next;//插入偶数链表
}
}
}
if(!p) op->next = evenp;//这里要分情况;如果输入链表结点个数为偶数个,那么从while出来p一定是NULL
else//如果输入链表结点个数为奇数个,那么从while出来p->next一定是NULL,而p是非空
op->next->next = evenp;//将奇数链表的最后一个结点的next指针指向偶数链表表头,以串联起奇偶链表;
ep->next = NULL;//令偶链表,表尾指向NULL;
return head;
}