【算法笔记】链表

判断链表是否为回文的,即从左往右看和从右往左看一样。
例如 1->2->2->1是回文的
1->2->3->2->1是回文的
1->3->2->1不是回文的
简单做法:首先准备一个栈,将链表的元素从左往右依次入栈,然后重新对比链表和栈顶元素。
双指针法:首先找到链表的中间位置mid,可以通过快慢指针实现
然后扭转mid以后的结点,mid的下一个结点指向空,然后两个指针从两边依次对比,最后再把链表恢复。

bool isHuiWen(Node* head) {//head->next是链表第一个元素
	Node* f = head;
	Node* s = head;
	while (f!=NULL && f->next != NULL) {
		f = f->next->next;
		s = s->next;
	}
	//此时s指向的就是中间结点,奇数个结点只有唯一一个中点,偶数个结点取中间两个都可以,这里取左边的
	Node* pre = NULL;
	Node* p = s;
	while (p != NULL) {//扭转结点
		Node* next = p->next;
		p->next = pre;
		pre = p;
		p = next;
	}
	//左右对比
	Node* l = head->next;
	Node* r = pre;
	bool flag = true;
	while (l != NULL && r != NULL) {
		if (l->data != r->data) {
			flag = false;
			break;
		}
		l = l->next;
		r = r->next;
	}
	//此时pre是最右边的结点
	p = pre;
	pre = NULL;
	while (p != NULL) {//恢复链表
		Node* next = p->next;
		p->next = pre;
		pre = p;
		p = next;
	}
	return flag;
}

给你一个链表,长度为偶数,假设链表为:L1->L2->L3->L4->R1->R2->R3->R4
要求将链表变为:L1->R4->L2->R3->L3->R2->L4->R1
分析:可以用到上一个题的双指针做法,先将链表转化为:
在这里插入图片描述
准备两个指针l和r分别指向链表两端,每次都穿插一个r结点。

void changeLink(Node* head) {
	Node* fast = head;
	Node* slow = head;
	while (fast != NULL && fast->next != NULL) {
		fast = fast->next->next;
		slow = slow->next;
	}
	Node* mid = slow;
	Node* pre = NULL;
	Node* p = mid;
	while (p != NULL) {
		Node* next = p->next;
		p->next = pre;
		pre = p;
		p = next;
	}
	Node* l = head->next;
	Node* r = pre;
	while (r!=mid) {
		Node* lnext = l->next;
		Node* rnext = r->next;
		l->next = r;
		r->next = lnext;
		l = lnext;
		r = rnext;
	}
}

链表划分:给定一个链表的头结点和划分值v,要求小于v的结点在左边,等于v的在中间,大于v的在右边(类似于快排的划分)
解1:将链表放数组,做划分,然后在用链表串起来。
解2:只用有限个变量:
准备6个指针分别是:小于区域的头指针和尾指针,等于区域的头指针和尾指针,大于区域的头指针和尾指针。初始都为空
然后从左往右遍历链表,如果小于v,则判断小于区域的头指针是否空,若为空,则头指针和尾指针都指向该结点,否则,尾指针的next指向该结点,尾指针指向该结点……
最后小于区域的尾指针指向等于区域的头指针,等于区域的尾指针指向大于区域的头指针。
有陷阱:小于区域可能为空,等于区域也可能为空……

Node* change(Node* head,int v) {
	Node* sHead = NULL;
	Node* sTail = NULL;
	Node* mHead = NULL;
	Node* mTail = NULL;
	Node* eHead = NULL;
	Node* eTail = NULL;
	Node* p = head;
	while (p != NULL) {
		Node* next = p->next;
		if (p->val < v) {
			if (sHead == NULL) {
				sHead = p;
				sTail = p;
				
			}
			else {
				sTail->next = p;
				sTail = p;
			}
		}
		else if (p->val == v) {
			if (mHead == NULL) {
				mHead = p;
				mTail = p;
			}
			else {
				mTail->next = p;
				mTail = p;
			}
		}
		else {
			if (eHead == NULL) {
				eHead = p;
				eTail = p;
			}
			else {
				eTail->next = p;
				eTail = p;
			}
		}
		p->next = NULL;
		p = next;
	}
	//连接
	if (sTail != NULL) {//小于区域不为空
		sTail->next = mHead;
		if (mTail == NULL) {//等于区域为空
			mTail = sTail;//sTail去连大于区域的头
		}
	}

	if (mTail != NULL) {
		mTail->next = eHead;
	}
	return sHead != NULL ? sHead : (mHead != NULL ? mHead : eHead);

}

拷贝链表,现在每个结点有两个指针,一个next(下一个),一个random(随意指)

class Node {
public:
	int val;
	Node* next;
	Node* random;
	Node() {
		val = 0;
		next = NULL;
		random = NULL;
	}
	Node(int val) :val(val) {
		next = NULL;
		random = NULL;
	}
};

现在要求赋值拷贝一个和原来一样的链表
解1:准备一张哈希表,key为原结点,value为新结点,遍历链表,将新结点都建出来,并且值都和原结点一样,但指针都悬空
然后取每一对键值对,新结点的next就是原来结点的next对应的value,新结点的random就是原来结点的random对应的value。

Node* copyList(Node* head) {
	unordered_map<Node*, Node*>map;
	Node* p = head;
	while (p != NULL) {
		map.emplace(p, new Node(p->val));
		p = p->next;
	}
	for (auto it = map.begin(); it != map.end(); ++it) {
		Node* old = (*it).first;
		Node* newNode = (*it).second;
		if (old->next == NULL) {
			newNode->next = NULL;
		}
		else {
			newNode->next = map[old->next];
		}
		if (old->random == NULL) {
			newNode->random = NULL;
		}
		else {
			newNode->random = map[old->random];
		}
		
		
	}
	return map[head];
}

解2:只用有限几个变量,解1是人为的构造老新结点的对应关系,该解法在结构上构造对应关系。
例如
在这里插入图片描述
现在对链表做如下调整:
每个结点后面连接一个与该节点值相同的结点,新结点next指向原来结点的next,新结点的random暂时悬空,如下图

在这里插入图片描述
从头开始遍历,每次访问两个结点,例如1和1’,1’的random就指向1的random指向结点的下一个结点,如上图,这样就将新结点random全部设置好。此时结点next还没设置好。
从头开始遍历,每次访问两个结点,例如1和1’,1的next等于1’的next,1’的next等于1’的next的next
所以总共需要三个循环

Node* copyList2(Node* head) {
	Node* p = head;
	while (p != NULL) {//插入
		Node* node = new Node(p->val);
		node->next = p->next;
		p->next = node;
		p = node->next;	
	}
	p = head;
	
	while (p != NULL) {//设置random
		Node* q = p->next;
		if (p->random == NULL) {
			q->random = NULL;
		}
		else {
			q->random = p->random->next;
		}
		p = q->next;
	}
	p = head;
	Node* newHead = head->next;
	while (p != NULL) {//设置next
		Node* q = p->next;
		p->next = q->next;
		if (q->next != NULL) {
			q->next = q->next->next;
		}
		p = p->next;
	}
	return newHead;
}

时间复杂度O(N)
203. 移除链表元素

  • 链表的创建
struct ListNode {
	int val;
	ListNode* next;
	//构造函数
	ListNode() : val(0), next(nullptr) {};//nullptr代表空指针
	ListNode(int x) : val(x), next(nullptr) {}
	ListNode(int x, ListNode* next) : val(x), next(next) {}
}
  • 链表删除:对于单结点,需要找到删除结点的上一个结点,由于头结点没有上一个结点,可以采取以下两种方法。
    1.对头结点特殊处理
ListNode* removeElements(ListNode* head, int val) {  
        while(head!=nullptr&& head->val==val){
            ListNode* temp=head;
            head=head->next;
            delete temp;
        }
        ListNode* cur=head;
        while(cur!=nullptr && cur->next!=nullptr){
            if(cur->next->val==val){
                ListNode* temp=cur->next;
                cur->next=temp->next;
                delete temp;
            }
            else{
                cur=cur->next;
            }
        }
        return head;
    }

2.创建一个虚拟头结点

ListNode* removeElements(ListNode* head, int val) {  
        ListNode* h0=new ListNode();
        h0->next=head;
        ListNode* cur=h0;
        while(cur!=nullptr && cur->next!=nullptr){
            if(cur->next->val==val){
                ListNode* temp=cur->next;
                cur->next=temp->next;
                delete temp;
            }
            else{
                cur=cur->next;
            }
        }
        return h0->next;//返回真正的头结点
    }

707. 设计链表

综合考察链表的各项操作
插入删除建一个虚拟头结点更方便

class MyLinkedList {

public:
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val) :val(val), next(nullptr) {}
    };
    MyLinkedList() {
        size = 0;
        virHead = new LinkedNode(0);
    }

    int get(int index) {
        if (index >= size || index < 0) {
            return -1;
        }
        LinkedNode* cur = virHead->next;
        while (index--) {
            cur = cur->next;
        }
        return cur->val;
    }

    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = virHead->next;
        virHead->next = newNode;
        size++;
    }

    void addAtTail(int val) {
        LinkedNode* cur = virHead;
        while (cur->next != nullptr) {
            cur = cur->next;
        }
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = cur->next;
        cur->next = newNode;
        size++;
    }

    void addAtIndex(int index, int val) {
        if (index < 0) {
            addAtHead(val);
        }
        else if (index == size) {
            addAtTail(val);
        }
        else if (index > size) {
            return;
        }
        else {
            LinkedNode* cur = virHead;
            for (int i = 0; i < index; i++) {
                cur = cur->next;
            }
            LinkedNode* newNode = new LinkedNode(val);
            newNode->next = cur->next;
            cur->next = newNode;
            size++;
        }
    }

    void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        LinkedNode* cur = virHead;
        for (int i = 0; i < index; i++) {
            cur = cur->next;
        }
        LinkedNode* temp = cur->next;
        cur->next = temp->next;
        delete temp;
        size--;
    }
private:
    int size;
    LinkedNode* virHead;
};

206. 反转链表

翻转链表:设三个指针,一个指向当前结点,一个指向前一个结点,另一个暂存当前结点的下一个结点。
流程:cur->next=pre,然后pre和cur后移,一直循环,终止条件是cur=nullptr,此时pre为翻转后的头结点。


```/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur=head;
        ListNode* pre=nullptr;
        while(cur!=nullptr){
            ListNode* temp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=temp;
        }
        head=pre;
        return head;
    }
};

解2 递归写法
从左往右翻转,最后返回的就是翻转后的头结点,一直返回到主函数即可。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur==nullptr){
            return pre;//翻转后的头结点
        }
        ListNode* temp=cur->next;
        cur->next=pre;
        pre=cur;
        cur=temp;
        return reverse(pre,cur);
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr,head);
    }
};

第二种递归写法
从右往左翻转,右边是已经翻转好的,左边待翻转,每个子函数都向上返回翻转后的头结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr || head->next==nullptr){//终止条件
            return head;
        }
        ListNode* last=reverseList(head->next);//翻转后的头结点
        head->next->next=head;
        head->next=nullptr;
        return last;//返回给上一层头结点


    }
};

24. 两两交换链表中的节点

涉及到头结点可能会改变的添加一个虚拟头结点会更方便
交换涉及到的中间变量可以通过画图来找,一般指向发生变化的结点所指向的结点需要保存。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
       ListNode* virHead=new ListNode();//由于头结点会改变,设一个虚拟头结点
       virHead->next=head;
       ListNode* cur=virHead;//cur处于一对结点的前一个位置
       while(cur->next!=nullptr && cur->next->next !=nullptr){
           //交换涉及到3个中间变量需要保存
           ListNode* firstPos=cur->next;//原来第1号位置
           ListNode* secondpos=firstPos->next;//原来第2号位置
           ListNode* next=secondpos->next;//下一对结点的1号位置
           cur->next=secondpos;
           secondpos->next=firstPos;
           firstPos->next=next;
           cur=firstPos;
       }
       return virHead->next;
    }
    
};

不设虚拟头结点:由于在第一次交换时,2号结点是新的头结点,所以需要一个变量保存下来

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* last=nullptr;//交换后cur的前结点
        ListNode* cur=head;//当前结点
        ListNode* newHead=nullptr;//新的头结点
        while(cur!=nullptr && cur->next!=nullptr){//保证是两两交换
            ListNode* next=cur->next->next;//下一对结点的首结点
            ListNode* first=cur;//1号结点
            ListNode* second=cur->next;//2号结点
            second->next=first;//交换
            first->next=next;//保证最后一对结点的first->next==nullptr
            if(last!=nullptr){
                last->next=second;//连接前结点
            }else{
                newHead=second;//保存新的头结点
            }
            last=first;//更新cur的前结点
            cur=next;
        }
        return newHead;
    }
};

递归写法:对于这种链表的递归写法,首先要明确操作的顺序是什么样的,例如本题,如果要返回交换后的头结点 自然应该从后往前进行,下一层要返回给上一层交换后的头结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* f(ListNode* head){//返回从head开始交换后的头结点
        if(head==nullptr || head->next==nullptr){//不足两个结点没法交换
            return head;
        }
        ListNode* first=head;//原来1号位置
        ListNode* second=head->next;//原来2号位置
        ListNode* next=f(second->next);//下一层交换后的头结点
        second->next=first;
        first->next=next;
        return second;
        
    }
    ListNode* swapPairs(ListNode* head) {
        return f(head);
    }
};

19. 删除链表的倒数第 N 个结点

分析:
解1 :对于单链表,找第i个结点好找,但找倒数第k个结点,可以通过先计算出一个多少个结点,然后计算出要找的结点位于第j个结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* virHead=new ListNode();
        virHead->next=head;
        
        ListNode* tail=virHead;
        int sum=0;//求结点的个数
        while(tail->next!=nullptr){
            tail=tail->next;
            sum++;
        }
        int index=sum-n;//位于第index位置
        ListNode* cur=virHead;
        while(index--){
            cur=cur->next;
        }
        ListNode* temp=cur->next; 
        cur->next=cur->next->next;
        delete temp;
        return virHead->next;


    }
};

解2 双指针法
设两个指针 slow 和fast,要找倒数第n个结点,始终保持slow与fast之间的距离为n,(slow在左,fast在右),当fast指向nullptr是,slow就是倒数第n个结点。
本题要删除倒数第n个结点,因此实际上要找到倒数第n+1个结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* virHead=new ListNode();
        virHead->next=head;
        ListNode* slow=virHead;
        ListNode* fast=virHead;
        for(int i=0;i<n+1;i++){
             fast=fast->next;
        }
        while(fast!=nullptr){//循环终止时slow指向要删除结点的上一个结点
            slow=slow->next;
            fast=fast->next;
        }
          ListNode* temp=slow->next;
          slow->next=temp->next;
          delete temp; 
        return virHead->next;

    }
};

面试题 02.07. 链表相交

分析:若存在相交结点,那么一定存在一段结点重合。因此可以令两链表的尾部对齐,设两个指针从前往后查找,找到相同的即是相交结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==NULL || headB==NULL){
            return NULL;
        }
        int lena=0;
        int lenb=0;
        ListNode* cura=headA;
        while(cura!=NULL){//求A的长度
            lena++;
            cura=cura->next;
        }
        ListNode* curb=headB;
        while(curb!=NULL){//求B的长度
            lenb++;
            curb=curb->next;
        }
        if(lenb<lena){//长的对齐
            cura=headA;
            int index=lena-lenb;
            while(index--){
                cura=cura->next;
            }
            curb=headB;
            while(cura!=NULL){
                if(cura!=curb){
                    cura=cura->next;
                    curb=curb->next;
                }
                else{
                    return cura;
                }
            }
            return NULL;
        }
        else{
            curb=headB;
            int index=lenb-lena;
            while(index--){
                curb=curb->next;
            }
            cura=headA;
            while(cura!=NULL){
                if(cura!=curb){
                    cura=cura->next;
                    curb=curb->next;
                }
                else{
                    return cura;
                }
            }
            return NULL;
        }
        return NULL;
    }
};

拓展:如果单链表有环呢,如何求相交结点?
这里分三种情况:
第一种:两个链表都有环,但不相交
在这里插入图片描述
第二种情况:两个链表有相同的入环结点
在这里插入图片描述
第三种情况

在这里插入图片描述
其中第一种和第三种情况可以放在一起:
从某一个链表的入口结点开始不断往后走,如果中途与另一个链表的入口结点相遇则是第三种情况,否则就回到了本结点不相交。
第二种情况转化为从头结点到入口结点组成的链表求相交结点,就是本题的做法。

142. 环形链表 II

第一步 先判断是否有环
设两个指针,一个指针一次走一个结点,另一个一次走两个结点,当slow指针也进入环时,由于每走一次,两指针的距离就减一,最终一定会减为0,即相遇;若没有环,快指针会走到尾部。
第二步 找入口结点
第一步可以得到相遇结点的位置,接下来通过列式求入口结点
设从头结点到入口结点的距离为L,从入口结点到相遇结点的距离为X,环的长度为C。
在这里插入图片描述
因此快指针走过的距离:L+nC+X,慢指针走过的距离:L+X,因为快指针每次走的距离是慢指针的2倍,所以L+nC+X=2(L+X),整理得:L=nC-X。
在这里插入图片描述
因此令一个指针指向头结点,另一个指针指向相遇位置,两个指针同时移动一个结点,最终一定会相遇,且相遇的位置即为入口结点的位置。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
            ListNode* slow=head;
            ListNode* fast=head;
            bool flag=false;
            while(fast!=NULL && fast->next!=NULL){//判断是否有环
                slow=slow->next;
                fast=fast->next->next;
                if(slow==fast){
                    flag=true;
                    break;
                }
            }
            if(!flag){
                return NULL;
            }
            slow=head;
            while(fast!=slow){
                fast=fast->next;
                slow=slow->next;
            }
            return fast;
    }
};

总结

  1. 涉及到头结点可能变化的设一个虚拟头结点会比较方便。
  2. 要找倒数第n个结点,设两个指针,保持两指针的距离为n,当fast指针为nullptr时,slow即为倒数第n个结点。
  3. 判断是否有环,设两个指针slow、fast,slow移动步长1,fast移动步长2.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值