LeetCode——链表
目录
160. 相交链表
题目描述:编写一个程序,找到两个单链表相交的起始节点。
算法1:
- 先计算出两个链表的长度lenA和lenB,以及长度差diff
- 让指针p和q分别指向链表头部,让长链表先走过diff,使两链表的剩余长度相同
- 然后让p、q同时移动,相等时即为交点,或是同时到达结尾两值均为NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *p=headA;
struct ListNode *q=headB;
int lenA=0,lenB=0;
while(p){
lenA++;
p=p->next;
}
while(q){
lenB++;
q=q->next;
}
p=headA;
q=headB;
int diff=lenA-lenB;
if(diff>0){
while(diff--){
p=p->next;
}
}else{
diff=-diff;
while(diff--){
q=q->next;
}
}
while(p!=q){
p=p->next;
q=q->next;
}
return q;
}
算法2:
- 将两个链表相接,即A->B,B->A
- 此时让p从A头部出发,q从B头部出发,最终p将指向B最后一个节点,q指向A最后一个节点,因为两指针都走过A+B的总长
- 如果A、B有相交点,则必然会有一段路是p、q共同走过
- 因此,如果有交点,则在终点前就有p==q,反之,则p、q只有在终点时才同为NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *p=headA;
struct ListNode *q=headB;
if(p==NULL||q==NULL) return NULL;
while(p!=q){
p=p==NULL?headB:p->next;
q=q==NULL?headA:q->next;
}
return p;
}
206. 反转链表
题目描述:反转一个单链表
算法1:迭代
使用头插法的思想,分别用prev和curr两指针前两个元素
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* curr=head;
struct ListNode* prev=NULL;
struct ListNode* tmp;
while(curr){
tmp=curr->next;
curr->next=prev;
prev=curr;
curr=tmp;
}
return prev;
}
算法2:递归
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL||head->next==NULL) return head;
struct ListNode *p=reverseList(head->next);
head->next->next=head;
head->next=NULL;//避免产生循环
return p;
}
21. 合并两个有序链表
题目描述:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
算法1:迭代
- 设置一个头结点head和和一个尾指针res,初始时尾指针指向head
- 分别比较链表头结点处的值大小,较小的结点则接在头结点head后,并让res始终指向最后一个节点
- 最后将两链表剩余的部分接在最后
- 返回头结点的下一个结点
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode *res=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode *head=res;
while(l1&&l2){
if(l1->val>l2->val){
res->next=l2;
l2=l2->next;
}else{
res->next=l1;
l1=l1->next;
}
res=res->next;
}
res->next=l1==NULL?l2:l1;//最终两链表至少有一个为空,如果都为空,则res->next=NULL
return head->next;
}
算法2:递归
struct ListNode* mergeTwoLists(struct ListNode* l1, struct 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(l1,l2->next);
return l2;
}
}
83. 删除排序链表中的重复元素
题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
算法1:迭代
- 删除一个结点必须要知道该结点的前一个结点
- 从头开始用指针p遍历链表,在指针p与p->next都不为NULL的情况下比较所指结点的值
- 注意释放空结点
struct ListNode* deleteDuplicates(struct ListNode* head){
struct ListNode *p=head;
while(p&&p->next){
if(p->val==p->next->val){
struct ListNode *tmp=p->next;
p->next=p->next->next;
free(tmp);
}else{
p=p->next;
}
}
return head;
}
算法2:递归
- 递归终止条件:当前链表为空,或是只有一个元素
- 找返回值:返回给上层处理好的链表的头指针
- 本层中要完成的任务:从宏观上来看,如果当前结点的值与上层返回的头结点的值相等,则要删除当前的结点,即返回上一层的头结点,如果不等,则返回当前结点
struct ListNode* deleteDuplicates(struct ListNode* head){
if(head==NULL||head->next==NULL) return head;
head->next=deleteDuplicates(head->next);
if(head->val==head->next->val) head=head->next;
return head;
}
19. 删除链表的倒数第N个节点
题目描述:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。给定的 n 保证是有效的。
算法1:快慢指针
先构造一个头结点dummy,方便对特殊情况的处理,让快指针从head处先走n个结点,然后让慢指针从dummy处和快指针一起走,直到快指针到达链表结尾,此时慢指针所在的位置即为要删除结点的前一个结点
总结:添加一个头结点,在第一个结点需要修改的情况下很好用
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode *fast = head;//快指针
struct ListNode *dummy = malloc(sizeof(struct ListNode));//头结点
dummy->next=head;
struct ListNode *slow = dummy;//慢指针
while(n--){
fast=fast->next;
}
while(fast){
slow=slow->next;
fast=fast->next;
}
struct ListNode *tmp = slow->next;
slow->next=slow->next->next;
free(tmp);
return dummy->next;
}
算法2:递归
思路:先递归到最后一个结点,在返回上一层的过程中,记录层数,如果为当前层数为n,则说明当前结点即为要删除的结点。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
static int curr=0;
if(head==NULL) return NULL;
head->next=removeNthFromEnd(head->next,n);
curr++;
if(curr==n) return head->next;
return head;
}
24. 两两交换链表中的节点
题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
算法1:递归
- 递归终止条件:链表中只剩一个结点或是没有结点,此时无法交换,则返回head
- 找返回值:返回给上一层交换好的链表头结点
- 本层要完成的任务:从宏观上考虑,当前的链表由头结点,头结点的下一个结点,还有处理好的链表头结点,此时对这三个结点交换即可
struct ListNode* swapPairs(struct ListNode* head){
if(head==NULL||head->next==NULL) return head;
struct ListNode *p=head->next;
head->next=swapPairs(head->next->next);
p->next=head;
return p;
}
算法2:迭代
利用两结点之前结点的指针prev即可完成反转
struct ListNode* swapPairs(struct ListNode* head){
struct ListNode *dummy=malloc(sizeof(struct ListNode));
dummy->next=head;
struct ListNode *prev=dummy;
while(prev->next&&prev->next->next){
struct ListNode *l1=prev->next,*l2=prev->next->next;
l1->next=l2->next;
l2->next=l1;
prev->next=l2;
prev=l2->next;
}
return dummy->next;
}
445. 两数相加 II
题目描述:给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
算法1:双栈
注意最高位的进位
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> st1,st2;
while(l1){
st1.push(l1->val);
l1=l1->next;
}
while(l2){
st2.push(l2->val);
l2=l2->next;
}
int carry=0;
ListNode *dummy=new ListNode(0);
while(!st1.empty()||!st2.empty()||carry==1){
int x1,x2;
if(!st1.empty()){
x1=st1.top();
st1.pop();
}else{
x1=0;
}
if(!st2.empty()){
x2=st2.top();
st2.pop();
}else{
x2=0;
}
int sum=x1+x2+carry;
ListNode *p=new ListNode(sum%10);
carry=sum/10;
p->next=dummy->next;
dummy->next=p;
}
return dummy->next;
}
};
算法2:递归
先将两个链表的长度补齐,然后递归求解
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int len1=0,len2=0;
struct ListNode *h1=l1,*h2=l2;
while(h1){
len1++;
h1=h1->next;
}
while(h2){
len2++;
h2=h2->next;
}
int diff=0;
if(len1<len2){
struct ListNode *tmp=l1;
l1=l2;
l2=tmp;
diff=len2-len1;
}else{
diff=len1-len2;
}
while(diff--){
struct ListNode *p=malloc(sizeof(struct ListNode));
p->val=0;
p->next=l2;
l2=p;
}
int cout=0;
addList(l1,l2,&cout);
if(cout==1){
struct ListNode *p=malloc(sizeof(struct ListNode));
p->val=1;
p->next=l1;
return p;
}
return l1;
}
void addList(struct ListNode* l1,struct ListNode* l2,int *cout){
if(l1==NULL||l2==NULL) return;
addList(l1->next,l2->next,cout);
int sum=l1->val+l2->val+*cout;
l1->val=sum%10;
*cout=sum/10;
return;
}
234. 回文链表
题目描述:请判断一个链表是否为回文链表。你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
算法:
-
先求出中间结点
-
从中间结点处反转链表,反转算法如下图所示
-
比较前后两部分对应结点的值
bool isPalindrome(struct ListNode* head){
int len=0;
struct ListNode *p=head;
while(p){
len++;
p=p->next;
}
if(len==1||len==0) return true;
len=len%2?len/2+1:len/2;//奇数个结点时,中间结点设置为中心位置右边的结点
struct ListNode *mid=head;
while(len--){
mid=mid->next;
}
struct ListNode *prev=NULL;
while(mid){
struct ListNode *ovn=mid->next;
mid->next=prev;
prev=mid;
mid=ovn;
}
while(prev&&head){
if(prev->val!=head->val){
return false;
}
prev=prev->next;
head=head->next;
}
return true;
}
725. 分隔链表
思路:
- 先求出链表总长度len,len/k即为最短链长,len%k为最长链长的个数,最长的链长比最短链长多1
- 根据链长分配链表,这段代码尤其要注意考虑临界情况
struct ListNode** splitListToParts(struct ListNode* root, int k, int* returnSize){
*returnSize=k;
struct ListNode *p=root;
int len=0;
while(p){
len++;
p=p->next;
}
struct ListNode** res=(struct ListNode*)calloc(k,sizeof(struct ListNode));
struct ListNode *prev=NULL,*head=root;//prev一定要初始化为NULL,没有初始化的指针很危险
for(int i=0;i<k;i++){
res[i]=head;
int width=len/k+(i<len%k?1:0);
for(int j=0;j<width;j++){
prev=head;
if(head) head=head->next;//这里的if可省略,不过有head->next还是加上比较好
}
if(prev) prev->next=NULL;
}
return res;
}