1.链表指定区间反转
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]
1.1.解法一:头插法
在需要反转的区间内遍历每一个结点,让这个新结点来到反转部分的起始位置
每走一步都要考虑各种指针怎么指,既要将摘下来的结点接到对应的位置上,还要保证后续结点能够找到。
public ListNode reverseBetween(ListNode head, int left, int right) {
int count =right-left;
if(head==null||count==0){//left和right在同一位置,不反转,直接返回即可
return head;
}
ListNode dummyNode=new ListNode(-1);//头结点可能反转
dummyNode.next=head;
ListNode pre=dummyNode;
for (int i = 0; i < left-1; i++) {
pre=pre.next;//pre到达需要反转的区间的前一结点
}
ListNode cur=pre.next;//指向需要反转的第一个结点
ListNode node=cur;
for (int i = 0; i < right-left; i++) {//需要反转的次数right-left
node=cur.next;//指向需要反转的第二个结点,需要为cur.next,因为第一此反转之后node和cur互换了位置
cur.next=node.next;//两个结点反转
node.next=pre.next;//第二个结点放到需要反转区间的第一位
pre.next=node;
}
return dummyNode.next;
}
1.2.穿针引线法
将链表分为三段,因为头结点可能在反转区间内,所以需要利用虚拟结点,首先slow指针指向反转区间的前一个结点,即“7”所在的结点,fast指向反转区间的最后一个结点,即“3”所在的结点,新建一个结点,succ=fast.next保证第三段链表不会断,反转区间内部反转。
最后因为slow.next中保存着反转前的下一结点的值,也就是“2”所在的结点,此时已经为最后一个结点,指向第三段succ结点即可。
public ListNode reverseBetween2(ListNode head, int left, int right) {
int count =right-left;
if(head==null||count==0){//left和right在同一位置,不反转,直接返回即可
return head;
}
ListNode dummyNode=new ListNode(-1);//头结点可能反转
dummyNode.next=head;
ListNode fast=dummyNode;
ListNode slow=dummyNode;
for (int i = 0; i < left-1; i++) {//到达需要反转的前一结点
slow=slow.next;
fast=fast.next;
}
for (int i = 0; i < right-left+1; i++) {//到达需要反转的最后一个结点
fast=fast.next;
}
ListNode succ=fast.next;//反转区间之外的第一个结点
ListNode newHead=null;//反转区间反转的新头
ListNode cur=slow.next;//反转区间的第一个结点
fast.next=null;
while (cur!=null){
ListNode node=cur.next;
cur.next=newHead;
newHead=cur;
cur=node;
}
cur=slow.next;//slow.next中保存的还是刚开始反转区间的第一个结点,此时已经为最后一个结点
slow.next=newHead;
cur.next=succ;//反转区间最后一个结点指向第三段链表
return dummyNode.next;
}
2.两两交换链表中的结点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
public static ListNode swapPairs(ListNode head) {
ListNode dummyNode=new ListNode(-1);//处理头结点
dummyNode.next=head;
ListNode slow=null;
ListNode fast=null;
ListNode temp=dummyNode;//temp每次都位于要处理的两个结点前
while (temp.next!=null&&temp.next.next!=null){
fast=temp.next.next;
slow=temp.next;
slow.next=fast.next;
fast.next=temp.next;
temp.next=fast;
temp=slow;
}
return dummyNode.next;
}
3.给单链表加一
用一个非空单链表来表示一个非负整数,然后将这个整数加一,高位在链表头部,低位在链表尾部。
示例
输入:head = [1,2,3,4]
输出:[1,2,3,5]
因为链表的低位在尾部,高位在头部,而加法是从低位开始,所以可以采用链表反转之后再加,再次反转之后返回。也可采用压栈的方法实现链表反转。
解法一:重新创建链表
public static ListNode plusOne2(ListNode head) {
Stack<Integer> stack=new Stack<>();
while (head!=null){
stack.push(head.val);
head=head.next;
}
int carry=0;
int adder=1;
ListNode dummyNode=new ListNode(-1);
while (!stack.empty()||carry>0){
int digit=stack.empty()?0:stack.pop();//取栈顶
int sum=digit+carry+adder;
carry=sum>=10?1:0; //有进位
sum=sum>=10?sum-10:sum;
ListNode node=new ListNode(sum);//重新创建链表返回
node.next=dummyNode.next;
dummyNode.next=node;
adder=0;
}
return dummyNode.next;
}
解法二:将链表结点一同压入栈中
取栈顶结点进行计算,需要加一或有进位才进入while循环,并修改链表中的val值,不需要重新创建链表。
注:此方法笔者只是通过断点测试了一下,可能有些情况无法全部解决,读者可在此思路上进一步优化。
public static ListNode plusOne(ListNode head) {
Stack<ListNode> stack=new Stack<>();
ListNode node=head;
while (node!=null){
stack.push(node);
node=node.next;
}
int carry=0;
int adder=1;
while (!stack.isEmpty() &&(adder>0||carry>0)){
node=stack.pop();
int digit=node.val;
int sum=digit+carry+adder;
carry=sum>=10?1:0; //有进位
sum=sum>=10?sum-10:sum;
node.val=sum;
adder=0;
}
if (stack.isEmpty()&&carry>0){//进位超过链表长度,例如99+1,需要额外的结点放到链表的头部
ListNode carryNode=new ListNode(carry);
carryNode.next=head;
head=carryNode;
}
return head;
}
4.链表加法
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4] 输出:[7,8,0,7]
解法一:入栈
将两个链表的值分别压入栈中,然后一起出栈,计算结果并保留进位,与上面的“给单链表加一”的解法一类似。
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stack=new Stack<>();
Stack<Integer> stack2=new Stack<>();
while (l1!=null){
stack.push(l1.val);
l1=l1.next;
}
while (l2!=null){
stack2.push(l2.val);
l2=l2.next;
}
int carry=0;
ListNode dummyNode=new ListNode(-1);
while (!stack.empty()||!stack2.isEmpty()||carry>0){
int digit=stack.empty()?0:stack.pop();
int digit2=stack2.empty()?0:stack2.pop();
int sum=digit+carry+digit2;
carry=sum>=10?sum/10:0; //有进位,sum可能大于20
sum=sum>=10?sum%10:sum;
ListNode node=new ListNode(sum);
node.next=dummyNode.next;
dummyNode.next=node;
}
return dummyNode.next;
}
解法二:反转链表
首先将两个链表反转,然后同步取值,计算结果并保留进位,利用头插法将计算结果保留下来。
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre=null;
ListNode cur=l1;
while (cur!=null){
ListNode node=cur.next;
cur.next=pre;
pre=cur;
cur=node;
}
l1=pre;
cur=l2;
pre=null;
while (cur!=null){
ListNode node=cur.next;
cur.next=pre;
pre=cur;
cur=node;
}
l2=pre;
int carry=0;
ListNode dummyNode=new ListNode(-1);
while (l1!=null||l2!=null){
int sum=0;
if(l1!=null){
sum+=l1.val;
l1=l1.next;
}
if (l2!=null){
sum+=l2.val;
l2=l2.next;
}
sum+=carry;
carry=sum/10;
ListNode node=new ListNode(sum%10);
node.next=dummyNode.next;
dummyNode.next=node;
}
if (carry>0){//进位超过最长链表长度,需要额外的结点
ListNode carryNode=new ListNode(carry);
carryNode.next=dummyNode.next;
dummyNode.next=carryNode;
}
return dummyNode.next;
}