算法通关村第二关——指定区间反转和两两交换反转

1.链表指定区间反转

力扣(LeetCode)92.反转链表Ⅱ

给你单链表的头指针 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.两两交换链表中的结点

力扣(LeetCode)24.两两交换链表中的结点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 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.给单链表加一

力扣(LeetCode)369.给单链表加一

用一个非空单链表来表示一个非负整数,然后将这个整数加一,高位在链表头部,低位在链表尾部。

示例

输入: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.链表加法

力扣(LeetCode)445.两数相加Ⅱ

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 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;
}

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值