1.合并两个有序链表
方法一:创建了一个新的链表
题目要求是将两个升序链表合并成一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。这道题目和合并两个有序数组的那道很像。这个代码在leetcode C++代码中提交无法通过,但是在codeblocks里是可以的,查了下是发生了内存错误,C++中不建议使用malloc。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if(l1==NULL) return l2;
if(l2==NULL) return l1;
struct ListNode* p=NULL;
struct ListNode* temp=(struct ListNode*)malloc(sizeof(struct ListNode));
if(l1->val<=l2->val){
temp->val=l1->val;
temp->next=NULL;
l1=l1->next;
}
else{
temp->val=l2->val;
temp->next=NULL;
l2=l2->next;
}
p=temp;
while(l1&&l2){
if(l1->val<=l2->val){
struct ListNode* a=(struct ListNode*)malloc(sizeof(struct ListNode));
a->val=l1->val;
a->next=NULL;
temp->next=a;
temp=temp->next;
l1=l1->next;
}
else{
struct ListNode* a=(struct ListNode*)malloc(sizeof(struct ListNode));
a->val=l2->val;
a->next=NULL;
temp->next=a;
temp=temp->next;
l2=l2->next;
}
}
if(l2==NULL) temp->next=l1;
if(l1==NULL) temp->next=l2;
return p;
}
方法二:递归
参考了官方题解和详解递归的题解。关于递归:函数在运行时调用自己,这个过程就叫作递归。关于边界情况:比如空链表,像上面的方法一样,两个链表中有一个为空,不需要任何合并操作,直接返回非空链表。否则,要判断哪个链表的头节点的值更小。如果两个链表有一个为空,递归结束。递归函数必须有终止条件否则会出错。此题目的终止条件是:如果两个链表中有一个为空。如何递归:较小节点的next指针指向其余节点的合并的结果。递归解法在执行用时上不如上一种解法。
ListNode* mergeTwoLists1(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;
}
}
方法三:迭代
其实最开始我想到的就是这种方法,但不知道设置一个哨兵节点,以便于在最后返回合并后的链表。另外还需要维护一个pre指针,pre最开始就是指向这个哨兵节点的。这里懂了之前说的头节点的存在有时是为了实际问题的好处理。但奇怪的是这种方法的执行效率和二差不多,不如一。
ListNode* mergeTwoLists2(ListNode* l1, ListNode* l2){
ListNode* prehead=new ListNode;
//struct ListNode* prehead=(struct ListNode*)malloc(sizeof(struct ListNode));
prehead->val=-1;prehead->next=NULL;
ListNode* pre=prehead;
if(l1==NULL) return l2;
if(l2==NULL) return l1;
while(l1&&l2){
if(l1->val<=l2->val){
pre->next=l1;
l1=l1->next;
pre=pre->next;
}
else{
pre->next=l2;
l2=l2->next;
pre=pre->next;
}
}
if(l1!=NULL) pre->next=l1;
if(l2!=NULL) pre->next=l2;
return prehead->next;
}
2.删除中间节点
要求实现一种算法删除单向链表中间的某个节点,假设你只能访问该节点。原来题目给的并不是表头啊,如果给的是表头就可以使用双指针的方法。似乎只能这样来做,因为是单链表你不可能找的到这个节点的前驱节点。
void deleteNode(struct ListNode* node) {
node->val=node->next->val;
node->next=node->next->next;
}
3.删除链表的倒数第N个节点
懂了双指针这种思维方式以后这道题目就变得简单了。类似于上一题的方法三,需要设置哑结点或者叫哨兵节点,一般需要设置这样的节点是因为返回的节点不确定。这里的哑结点用来简化极端情况,比如链表只有一个节点或者需要删除链表的头部。下面的代码采用一次遍历的方法,两次遍历先确定链表的长度也是可以的。删除时需要单独设置一个指针指向被删除的节点,以防丢失。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode* dummy=(struct ListNode*)malloc(sizeof(struct ListNode));
dummy->val=-1;dummy->next=head;
struct ListNode* p=dummy,*q=dummy;
for(int i=0;i<n+1;i++)
p=p->next;
while(p!=NULL){
p=p->next;
q=q->next;
}
struct ListNode* del=q->next;
q->next=q->next->next;
free(del);
return dummy->next;
}
4.返回倒数第k个节点
和上一道题目类似。
int kthToLast(struct ListNode* head, int k){
struct ListNode* p=head,*q=head;
for(int i=0;i<k;i++)
p=p->next;
while(p!=NULL){
p=p->next;
q=q->next;
}
return q->val;
}