链表虽好,但是像绳子一样易断易乱…
数组也好,就是移动效率低…
判断一个链表里是否有环
快慢指针:快指针比慢指针多1的步长 (Vs-Vf=1)
S+Vs-(F+Vf) = N-1
Vs-Vf = 1
设S是慢指针步长 F是快指针步长
差值:S-F=N
S+1-(F+2)=N-1
…
N次后:S-F=0
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
1.暴力遍历 O(N) O(N)
用数组遍历链表
结束得到长度len
然后直接拿到链表中点 l [len//2]
// vector::back() 返回vector容器的最后一个元素...
// vector::push_back(Q) 向vector容器里加入一个元素Q
// vector::pop_back() 删除vector最后一个元素
class Solution {
public:
ListNode* middleNode(ListNode* head) {
vector<ListNode*> A = {head};
while(A.back()->next!=NULL)
{
A.push_back(A.back()->next);
}
return A[A.size()/2];
}
};
2.单指针 O(N) O(1)
第一次遍历得到链表长度N
第二次遍历到N//2的位置即为链表中点
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int len = 0;
ListNode* t = head,*tt = head;
while(t!=NULL)
{
t = t->next;
len+=1;
}
for(int i=0;i<len/2;i++)
tt = tt->next;
return tt;
}
};
3.快慢指针(又是你…)O(N) O(1)
定义快慢指针都在头节点位置
快指针比慢指针快一步,快指针一次2步长,慢指针一次1步长
终止条件是快指针为空或者快指针的下一节点为空(因为是偶数时返回中间节点的第二个)
这里实践一下:实验之后发现确实很巧妙,其他的步长都不能满足…
A.链表长度是偶数的时候 假设是4 快指针必然指向NULL,慢指针此时正好在中间的第二个位置
slow------slow-----------slow
fast---------------------fast----------------------fast
1 -> 2 -> 3 -> 4 ->NULL
B.链表长度是奇数的时候 假设是5 快指针必然指向末尾节点,慢指针此时正好在中间节点的位置
slow----------slow-----------slow
fast-------------------------fast--------------------------fast
1 -> 2 -> 3 -> 4 -> 5 -> NULL
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *fast=head,*slow=head;
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
设置快慢指针都在都指向头节点。
先让快指针先走k步,此后设置快慢指针都走一步,快指针遍历到尾部节点的时候,慢指针指向的就是倒数第K个节点.
how?
1->2->3->4->5->NULL, k=2 ====> [4->5]
faster:先走k步,指向3
slow:指向头1
然后后面两个指针同时走1步,直到快指针到末尾->NULL
faster: 指向NULL
slow: 指向4
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *faster = head, *slow = head;
int cnt = 0;
while(1){
faster = faster->next;
cnt += 1;
if(cnt == k) break;
}
while(faster!=NULL)
{
slow = slow->next;
faster = faster->next;
}
return slow;
}
};
好鸡难,什么妖怪0-0!!!
头插法 把最后的节点插入到开头 注意head和head->next代表的含义不是一样的…
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *newHead = nullptr;
while (head != nullptr) {
//使用了一个 newHead 指针来记录新链表的头部,以及一个 head 指针来遍历原始链表
ListNode *next = head->next;
head->next = newHead;
newHead = head;
head = next;
}
return newHead;
}
};
//newHead -> nullptr
//head -> A -> B -> C -> nullptr
//newHead -> A -> nullptr
//head -> B -> C -> nullptr
//newHead -> B -> A -> nullptr
//head -> C -> nullptr
//newHead -> C -> B -> A -> nullptr
//head -> nullptr
尾插法 并不适用于反转链表 因为它只是做了一个拷贝
// 注意啊 这里的什么*都是指针,并不是节点啊!没有节点需要被操作,我们操作的是他们之间的指针指向
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 双指针辅助移动 pre->指向头节点 cur指向空
ListNode* pre = head, *cur = NULL;
while(pre!=NULL)
{
//需要存储一下pre指向下一节点的指针 不然没法移动
ListNode* temp = pre->next;
pre->next = cur; //让pre指向cur
cur = pre; //让cur变成pre实现向右/左移动
pre = temp;//这里不能写pre->next 因为这时候pre->next=cur...这里就要用temp
}
return cur;
}
};
遍历到尾部,然后退回来每次修改当前节点的下一个节点的指向为当前节点,并重置下一个指向的节点的指向为空
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 使用递归 出口是到尽头NULL
// 遍历到最后一个节点 然后它将会变成头节点(因为是反转)
// 让当前节点的下一个节点指向当前节点???4->5->NULL 5->4->NULL
// 让当前节点next指向NULL
if(head == NULL || head->next==NULL)
{
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return ret;
}
};
///
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL) return NULL;
ListNode* cur = head;
while(head->next!=NULL)
{
// head->next->next => head 先把head->next->next存起来 是新节点t
// cur = head->next 移动下一个位置
// 此时head->next要指向下一个新节点 t
ListNode* t = head->next->next;//存好之后不需要担心之后找不到新节点的问题
head->next->next = cur;
cur = head->next;
head->next = t;
}
return cur;
}
};
把固定l-r的节点顺序反转… 作为反转链表的进阶版
主要是将链表段反转之后需要嵌入原来的链表的话需要调整头尾的指针顺序
头插法:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 定义一个dummyHead, 方便处理
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
// 初始化指针
ListNode g = dummyHead;
ListNode p = dummyHead.next;
// 将指针移到相应的位置
for(int step = 0; step < left - 1; step++) {
g = g.next; p = p.next;
}
// 头插法插入节点
for (int i = 0; i < right - left; i++) {
ListNode removed = p.next;
p.next = p.next.next;
removed.next = g.next;
g.next = removed;
}
return dummyHead.next;
}
直接反转的…
双指针:
还需要插入哨兵节点 因为当left==1时,直接反转会出错…
pre是指向遍历节点 cur是指向初始化节点NULL
p0是反转节点的前一个节点位置…从dummy开始…
链表:d->1->2->3->4->5->NULL
反转[3,4] p0 = d; 遍历次数是(3-1)两次,p0 ->1->2 正好到前一个节点
如果写的是3则是正好反转的节点, 这样的话无法保存反转前一个节点的位置,然后无法连接前面节点的链表…
前面的不需要反转的链表是d->1->2 p0保存了这个路径终止位置下标
//自己实现的
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummy = new ListNode(0);
dummy->next = head;
ListNode *p0 = dummy;
for(int i=0;i<left-1;i++)
//遍历到left-1的位置 因为我需要把p0控制在反转节点的前一个节点的位置
p0 = p0->next;
//反转次数 right - left + 1
// cur 指向当前的被反转的元素
// pre 指向上一个元素
ListNode *cur = p0->next, *pre = NULL;
for(int i=0;i<right-left+1;i++)
{
ListNode *nxt = cur->next;//指向cur的下一个节点
cur->next = pre;//修改cur的指向
pre = cur; // 移动pre到已经反转的节点
cur = nxt;// 移动cur到下一个节点
}
// 还没完 我们只是单纯反转了其中的[l,r]的方向
// 拼接链表
// 此时pre = 已经反转的最后一个节点 是头节点
// 此时cur = 已经反转节点的下一个节点 未反转节点的第一个
// 以下的顺序不能改 不然无法修改p0->next->next的指向
p0->next->next = cur;
// p0->next 反转节点的第一个节点 再指向未反转链表的头节点cur
p0->next = pre;
// p0指向下一个节点是已经反转的最后一个节点
return dummy->next;//head
}
};
考察三个问题:(毒妇!纯纯为难)
1->2->3->4->5->6
->截断 1->2->3 || 4->5->6
->反转后半截 1->2->3 || 6->5->4
->间插合并 6->1->5->2->4->3
伪代码:
A
// 快慢指针找到中间节点...但是这里求的是中间节点的第一个
// 所以终止条件要模拟改变一下...
// 我们希望在链表长度是偶数的时候,slow落在中间部分的左侧
假设len = 4 以下类型是我们想要的 可以发现此时fast没有到末尾并且下一个节点也不是NULL,但是它下一个节点的下一个指向是NULL;
slow----slow
fast-----------fast
1 2 3 4 NULL
假设len = 5 以下类型是我们想要的:此时fast只到末尾节点
slow----slow---slow
fast-----------fast----------fast
1 2 3 4 5 NULL
总结得到只要满足: fast->next!=NULL and fast->next->next!=NULL 就继续走
fast,slow = head,head
while fast->next!=NULL and faste->next->next!=NULL:
fast = fast->next->next
slow = slow->next
return slow
temp = slow
B
反转链表的操作...在A之后已经获得切割出来的头节点temp
由于是单链表后半段,故末尾一定指向NULL
head = temp
pre = NULL
while(head!=NULL)
{
t = head->next;
head->next = pre;
pre = head;
head = t;
}
return pre //头节点
C
ListNode *i = head1 , *j = head2
while i!=NULL and j!=NULL:
t1 = i->next
t2 = j->next
i->next = j
j->next = t
i = t1
j = t2
return head1
我的代码:
首先实现3个函数分别对应任务目标
1.寻找链表中点
2.反转链表
3.合并链表
class Solution {
public:
void reorderList(ListNode* head) {
// 特判
if(head == NULL) return;
// 找中间节点
ListNode *mid = FindMidNode(head);
// 这里注意是中间节点的第一个 所以反转的后半段是第二个中间节点开头的
ListNode *reverse_right = reverseList(mid->next);
// 断链之后要把中点指向的改写NULL
mid->next = NULL;
merge_List(head,reverse_right);
}
//找截断中点
ListNode* FindMidNode(ListNode* head)
{
ListNode *fast = head, *slow = head;
//这里注意和找中点的那个题不一样,偶数的长度要截断的是中点左侧的那个点
while(fast->next != NULL && fast->next->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
// 反转链表 这里因为是直接给出头节点所以直接遍历就可以
// 如果没有找中点的话,就要根据left,right写一个反转链表2的函数...
ListNode* reverseList(ListNode* head)
{
ListNode* pre = NULL;
while(head!=NULL)
{
ListNode *t = head->next;
head->next = pre;
pre = head;
head = t;
}
return pre;
}
void merge_List(ListNode *i, ListNode *j)
{
// 依次接起来 i -> j -> i->next -> j->next -> ...
while(i!=NULL && j!= NULL)
{
ListNode *ti = i->next;
ListNode *tj = j->next;
i->next = j;
j->next = ti;
i = ti;
j = tj;
}
}
};
148. 排序链表
归并排序 C++的归并排序板子题还记得吗???
//先写一遍归并排序的板子
#include
using namespace std;
const int N = 1005;
int tmp[N];
int a[N];
void merge_sort(int q[],int l,int r)
{
//特判
if(l>=r) return;
//先划分中点
int mid = l+r>>1;
int i = l, j =mid+1, k = 0;;
//快乐递归...
merge_sort(q,i,mid);
merge_sort(q,j,r);
while(i<=mid && j<=r)
{
if(q[i]<=q[j]) tmp[k++] = q[i++];//i++ 先返回i 再++
else tmp[k++] = q[j++];
}
// 长的一方并到tmp里
while(i<=mid) tmp[k++] = q[i++];
while(j<=r) tmp[k++] = q[j++];
for(int i=l,j=0;i<=r;i++,j++)
q[i] = tmp[j];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
merge_sort(a,0,n-1);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
cout<<endl;
return 0;
}
力扣的代码:
递归
1.分段 找中点
2.对两个子链表分别排序
3.合并子链表
//新建一个头节点用于存储合并结果
newnode = new ListNode(0)
*p = newnode
//传入 head1 head2 链表
//同时遍历 head1 head2 的节点 并比较当前节点值大小
while head1!=NULL and head2!=NULL:
if head1->val < head2->val:
p->next = head1
head1 = head1->next
else:
p->next = head2
head2 = head2->next
//合并长的链表
if(head1!=NULL) p->next = head1
if(head2!=NULL) p->next = head2
return newnode
正式的代码:
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* head1 = head;
ListNode* head2 = split_(head);
head1 = sortList(head1); //一条链表分成两段分别递归排序
head2 = sortList(head2);
return merge(head1, head2); //返回合并后结果
}
//双指针找单链表中点模板 中点左侧
ListNode* split(ListNode* head)
{
ListNode *slow = head, *fast = head;
while (fast->next->next != nullptr && fast->next != nullptr)
{
slow = slow->next;
fast = fast->next->next;
}
// 这里必须要处理把链表断开,不然指针乱指
ListNode* mid = slow->next;
slow->next = nullptr; //断尾
return mid;
}
//双指针找单链表中点模板
ListNode* split_(ListNode* head)
{
ListNode *slow = head, *fast = head;
//中点右侧
ListNode *p = NULL;
while(fast!= nullptr && fast->next != nullptr)
{
p = slow;
slow = slow->next;
fast= fast->next->next;
}
p->next = NULL;
return slow;
}
//合并两个排序链表模板
ListNode* merge(ListNode* head1, ListNode* head2)
{
ListNode *dummy = new ListNode(0), *p = dummy;
while (head1 != nullptr && head2 != nullptr)
{
if (head1->val < head2->val)
{
p = p->next = head1;
head1 = head1->next;
}
else
{
p = p->next = head2;
head2 = head2->next;
}
}
if (head1 != nullptr) p->next = head1;
if (head2 != nullptr) p->next = head2;
return dummy->next;
}
};