92. Reverse Linked List II (补全)
仅供自己学习
思路:
这里先使用的是递归的方法,首先是反转整个的链表,在判断已经递归到最后一个元素后,将该元素作为新的头节点,返回该元素,我们在倒数第二个元素,现在将这个元素的next的next指向该元素,再将该元素的next指向NULL,然后再次返回头节点,再次进行。
例如1->2->3->4,我们返回4,作为翻转后的头结点,我们在3,将3->next->next=3,即4->next=3,4->3,然后3->next=NULL,那么此时是 1->2->3->NULL,而此时4又指向3。再次返回头节点4,在重复以上步骤,2->next->next=2,2->next=NULL,那么就是1->2->NULL,4->3->2->null。一直重复即可。
代码
1 ListNode* reverse(ListNode* head){
2 if(head->next==NULL) return head;
3 ListNode* last = reverse(head->next);
4 head->next->next=head;
5 head->next=back;
6 return last;
7 }
如果是前n个反转呢
那么我们需要一个来记录第n+1个的位置,然后其余的是不变的。
1 ListNode* back =NULL;
2 ListNode* reverse(ListNode* head,int n){
3 if(n==1) { back=head->next; return head;} //分到n=1后,就是前n个最后的一个节点了,那么记录后面的节点的位置,返回该节点作为反转头节点,其余不变
4 ListNode* last = reverse(head->next,n-1); //每次后退一个元素,那么从该元素开始到n,就应该减1,就是前n-1个反转,一直分,分到最小的n=1后
5 head->next->next=head;
6 head->next=back;
7 return last;
8 }
那如果是反转 第m到n个元素呢。
同样当m=1是,就是反转前n个元素了
那么同样是递归将m~n不断分小,每次递归传入的是head->next,left-1,right-1。因为我们传入的是next下一个元素,那么以他为链表的新起点,对他来说第left的元素的下标应该-1,第right元素的下标也会-1.那么我们递归一直到head的位置为left为止,那么就变成了前right个元素来反转。所以当我们的left-1减到1后,就调用反转前n个元素的代码,然后这个函数调用完后,那么left~right之间的节点就反转完成,那么直接返回这个函数返回的翻转后链表的头节点,因为这个返回是赋值给head->next就相当于直接更新了连接反转区间的那个元素的next。
如:1->2->3->4->5,left=2,right=4,那么我们递归head->next=reverseBetween(head->next,left-1,right-1); 然后就是在2->3->4->5这个链表中 从left=1,right=3这个范围进行翻转,调用前n个元素反转的代码后得到 4->3->2->5,然后将这个翻转后的链表的头节点4返回给1->next那么就得到1->4->3->2->5
代码:
1 /**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11 class Solution {
12 public:
13 ListNode* back =NULL;
14 ListNode* reverse(ListNode* head,int n){
15 if(n==1) { back=head->next; return head;}
16 ListNode* last = reverse(head->next,n-1);
17 head->next->next=head;
18 head->next=back;
19 return last;
20 }
21 ListNode* reverseBetween(ListNode* head, int left, int right) {
22 if(left==1){
23 return reverse(head,right);
24 }
25 head->next=reverseBetween(head->next,left-1,right-1);
26 return head;
27 }
28 };
还有一种迭代的方法。就是移动到left后开始反转。
首先我们需要定义一个虚拟头结点,因为如果left为1,第一个节点也要反转,但是head并不会还指向反转后的第一个元素,而是继续指向反转前的第一个节点。所以如果用一个虚拟节点指向head,那么当head也要反转的时候,还能通过虚拟节点指向真正的链表。
我们用三个指针,pre,用来指向反转区间的左边缘节点的前一个结点;cur刚开始用来指向反转区间的做边缘节点,反转后会跟着移动到下一个节点,而back指针用来指向cur的下一个节点,辅助操作能使反转更为方便。
刚开始将虚拟节点 dummy->next指向head,然后pre指向dummy,因为如果pre指向dummy根据后边反转的代码,可以让反转后的链表头节点被dummy->next所指。然后移动pre到left-2的位置。然后开始反转。
首先将 back指向cur->next,再将cur->next=back->next,再将back->next指向pre->next(这里不是cur的原因是,当cur不断移动后,cur一直指向的是这个节点,如果写的cur就会一直指的同一个节点了,只是这个节点一直在后移),最后pre->next=back。
如 dummy->9->7(pre)->2(cur)->5(back)->4->3->6. 反转区间为【3,6】。 cur->next=back->next,dummy->9->7(pre)->2(cur)->4->3->6,但此时 还有->5(back)->4->3->6。back->next指向pre->next,dummy->9->7(pre)->2(cur)->4->3->6还有5(back)->2(cur)。pre->next=back,就是dummy->9->7(pre)->5(back)->2(cur)->4->3->6,这样就将5和2翻转,同样会对之后的进行这样反转。还能看到cur仍然指向2节点,如果下一步back->next不是指向pre->next,而是cur,那么就错误了。
代码:
1 /**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11 class Solution {
12 public:
13 ListNode* reverseBetween(ListNode* head, int left, int right) {
14 ListNode* dummy=new ListNode(-1);
15 dummy->next=head;
16 ListNode* pre=dummy;
17 for(int i=0;i<left-1;++i){
18 pre=pre->next;
19 }
20 ListNode* cur = pre->next;
21 ListNode* back = NULL;
22 for(int i=0;i<right-left;++i){
23 back=cur->next;
24 cur->next=back->next;
25 back->next=pre->next;
26 pre->next=back;
27 }
28 return dummy->next;
29 }
30 };