算法通关村第二关——链表指定区间反转问题解析

1. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
在这里插入图片描述

2. 分析

2.1 头插法,首先肯定需要找到left的位置,但是由于这个节点需要改变,所以需要找到这个节点的前一个节点作为prev,然后这个节点就是cur,但是还需要记录后一个节点next,因为是将cur和next进行调换,所以需要保存next节点,然后将cur指向next的下一个节点,此时next节点需要指向prev的下一个节点位置,然后调整prev的下一个节点为next。

核心图
在这里插入图片描述

    public ListNode reverseBetween(ListNode head, int left, int right) {
    
    
        ListNode sentinel = new ListNode(-1);
        sentinel.next = head;
        ListNode prev = sentinel;
        // 移动到left前一个节点
        for(int i=1;i<left;i++){
    
    
            prev=prev.next;
        }
        // 记录left节点
        ListNode cur = prev.next;
        // 存储left节点的后一个节点
        ListNode next;
        for(int i=0;i<right-left;i++){
    
    
            // 存储left节点的后一个节点
            next = cur.next;
            // 将left节点指针指向后一个节点的后一个节点
            cur.next = next.next;
            // 将后一个节点的指针指向left前一个节点的下一个节点
            next.next = prev.next;
            // 然后将prev的指针指向next节点
            prev.next = next;
        }
        return sentinel.next;
    }

运行截图:
在这里插入图片描述
时间复杂度:O(right) 第一次循环O(left)第二次循环O(right-left)
空间复杂度:O(1)

总结:头插法涉及到了4个节点的这种指向,需要好好画图才能了解,不然很头晕,一开始一直疑惑 next.next = prev.next;作用,画完图了才知道这个只是为了后面的断开节点做准备,对于链表的这个删除插入节点还是不熟悉。

2.2 穿针引线法,这个名字也挺绕的,总体思路简单,就是将链表分成三个部分,然后将中间链表反转,然后其余两个链表分别接入新链表。

public ListNode reverseBetween(ListNode head, int left, int right) {
    
    
       ListNode sentinel = new ListNode(-1);
       sentinel.next = head;
       ListNode prev = sentinel;
        for(int i=0;i<left-1;i++){
    
    
            // 找到left左边的节点
            prev =prev.next;
        }
        ListNode rightNode = prev;
        for(int i=0;i<right-left+1;i++){
    
    
            // 找到right节点
            rightNode=rightNode.next;
        }
        // right右边节点
        ListNode rightNodeNext = rightNode.next;
        // left节点
        ListNode leftNode = prev.next;
        // 分割原链表
        rightNode.next = null;
        prev.next = null;
        // 反转链表
        reverseList(leftNode);
        // 当前第一个链表指向右节点
        prev.next = rightNode;
        // 第二个链表的右节点指向当前第三个链表
        leftNode.next = rightNodeNext;

        return sentinel.next;

    }
    // 反转链表
    public ListNode reverseList(ListNode head){
    
    
        ListNode cur = head;
        ListNode prev = null;
        while(cur!=null){
    
    
            ListNode nextNode = cur.next;
            cur.next = prev;
            prev=cur;
            cur = nextNode;
        }
        return prev;
    }

运行截图
在这里插入图片描述
时间复杂度: O(right-left)
空间复杂度:O(1)

总结:尤其需要注意反转链表前需要断开之前的链表

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

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

2.1 分析

2.1.1 这一题的思路主要是记录好第一个和第二个节点,然后让哨兵节点先指向第二个节点,第一个节点指向第二个节点的后一个节点,第二个节点然后指向第一个节点,那么此时交换已经完成,继续移动cur

在这里插入图片描述

    public ListNode swapPairs(ListNode head) {
    
    
        if(head==null || head.next == null) return head;
        ListNode sentinel = new ListNode(-1);
        sentinel.next = head;
        ListNode cur = sentinel;
        while(cur.next!=null && cur.next.next!=null){
    
    
            // 第一个节点
            ListNode next = cur.next;
            // 第二个节点
            ListNode nextNext = cur.next.next;
            // 当前节点指向第二个节点
            cur.next = nextNext;
            // 第一个节点指向第二个节点的下一个节点
            next.next = nextNext.next;
            // 第二个节点指向第一个节点
            nextNext.next = next;
            // cur变成第二个节点
            cur=next;
        }
        return sentinel.next;
    }

运行截图:
在这里插入图片描述
时间复杂度:O(n)
空间复杂度:O(1)

3. 单链表+1

用一个非空单链表来表示一个非负整数,然后将这个整数加一。你可以假设这个整数除了 0 本身,没有任何前导的 0。这个整数的各个数位按照 高位在链表头部、低位在链表尾部 的顺序排列。
单链表加1
在这里插入图片描述
在这里插入图片描述

3.1 分析

3.1.1 可以将链表元素写入栈里面,然后栈依次弹出,先判断时候之前没有进位,然后计算出元素和,继续判断是否有进位,然后依次将元素添加至新链表里面。总体思路和整型数据这种加法一致。

  public static ListNode plusOne(ListNode head) {
    
    
        Stack<Integer> stack = new Stack<>();
        while (head != null) {
    
    
            stack.push(head.value);
            head = head.next;
        }
        // 进位
        int carry = 0;
        // 1
        int adder = 1;
        ListNode sentinel = new ListNode(0);
        while (!stack.isEmpty() || carry > 0 || adder != 0) {
    
    
            // 取出第一个弹出的元素
            int num = stack.isEmpty() ? 0 : stack.pop();
            // 计算总和
            int sum = num + adder + carry;
            // 先判断是否有进位
            carry = sum >= 10 ? 1 : 0;
            // 判断值是否大于0
            sum = sum >= 10 ? sum - 10 : sum;
            // 创建新链表
            ListNode cur = new ListNode(sum);
            cur.next = sentinel.next;
            sentinel.next = cur;
            // 加数设置为0
            adder = 0;
        }
        return sentinel.next;
    }

时间复杂度:O(n)将链表元素压入栈。
空间复杂度:O(n) 新建了一个链表。

总结:思路基本上都能想到,但是中间进位一开始放在判断求和是否大于10之后,就导致了进位永远都是0,应当是先进位,然后再处理和。

3.1.2 先将链表反转,然后先从第一个开始计算,依次进位计算,不过最后需要判断进位是否为1,如果为1表名第二位有了进位,需要添加到里面去

    public static ListNode plusOne(ListNode head) {
    
    
        ListNode newHead = reverseList(head);
        int carry = 0;
        int adder = 1;
        ListNode temp = null;
        ListNode cur = newHead;
        while (cur != null) {
    
    
            int num = cur.value;
            int sum = carry + adder + num;
            carry = sum >= 10 ? 1 : 0;
            sum = sum >= 10 ? sum - 10 : sum;
            ListNode node = new ListNode(sum);
            node.next = temp;
            temp = node;
            cur = cur.next;
            adder = 0;
        }

        // 检查是否有进位
        if (carry > 0) {
    
    
            ListNode node = new ListNode(carry);
            node.next = temp;
            temp = node;
        }

        return temp;
    }

    public static ListNode reverseList(ListNode head) {
    
    
        ListNode cur = head;
        ListNode prev = null;
        while (cur != null) {
    
    
            ListNode listNodeNext = cur.next;
            cur.next = prev;
            prev = cur;
            cur = listNodeNext;
        }
        return prev;
    }

4. 两数相加 II

力扣
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
在这里插入图片描述

4.2 分析

4.2.1 这一题和上一题类似,都可以将节点里面的值放入到栈里面去,然后依次取出相加,计算进位,使用哨兵节点存储。

   public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    
    
        Stack<Integer> stackL1 = new Stack<>();
        Stack<Integer> stackL2 = new Stack<>();
        while(l1!=null){
    
    
            stackL1.push(l1.val);
            l1=l1.next;
        }

        while(l2!=null){
    
    
            stackL2.push(l2.val);
            l2=l2.next;
        }

        int carry =0;
        ListNode sentinel = new ListNode(0);
        while(!stackL1.isEmpty() || !stackL2.isEmpty()|| carry>0){
    
    
            int num1 = stackL1.isEmpty() ? 0:stackL1.pop();
            int num2 = stackL2.isEmpty()?0:stackL2.pop();
            int sum = num1+num2+carry;
            carry = sum>=10?1:0;
            sum=sum>=10?sum-10:sum;
            ListNode res = new ListNode(sum);
            res.next = sentinel.next;
            sentinel.next = res;
        }

        if(carry>0){
    
    
            ListNode res = new ListNode(carry);
            res.next = sentinel.next;
            sentinel.next =res;
        }
        return sentinel.next;
    }

运行截图
在这里插入图片描述

时间复杂度:O(l1+l2)遍历两个链表的长度
空间复杂度:O(l1+l2)使用了两个栈来存储,两个栈的长度之和

4.2.2 一样的采用反转链表,先将链表反转,然后相加,计算进位,然后返回。

   public  ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    
    
        ListNode newL1 = reverseList(l1);
        ListNode newL2 = reverseList(l2);

        int carry = 0;
        ListNode sentinel = new ListNode(0);
        while (newL1 != null || newL2 != null || carry > 0) {
    
    
            int num1 = (newL1 == null) ? 0 : newL1.val;
            int num2 = (newL2 == null) ? 0 : newL2.val;
            int sum = num1 + num2 + carry;
            carry = sum >= 10 ? 1 : 0;
            sum = sum >= 10 ? sum - 10 : sum;
            ListNode res = new ListNode(sum);
            res.next = sentinel.next;
            sentinel.next = res;
            if (newL1 != null) newL1 = newL1.next;
            if (newL2 != null) newL2 = newL2.next;
        }
        return sentinel.next;
    }

    public  ListNode reverseList(ListNode head) {
    
    
        ListNode cur = head;
        ListNode prev = null;
        while (cur != null) {
    
    
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }

运行截图:
在这里插入图片描述
时间复杂度:O(l1+l2)两个链表的长度,然后链表比较值的时候,再次比较O(max(l1,l2)总体上O(l1+l2)
空间复杂度:新建反转链表O(l1+l2),然后额外链表长度O(max(l1,l2)),总体上O(l1+l2)

猜你喜欢

转载自blog.csdn.net/qq_52843958/article/details/131837300