编程导航算法通关村第二关 | 链表反转的拓展问题

50 阅读5分钟

指定区间反转

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

头插法

反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        for(int i=0; i<left-1;i++) {
            pre = pre.next;
        }
        ListNode cur = pre.next;
        ListNode next;
        for(int i=0; i< right - left;i++) {
            next = cur.next;
            cur.next = next.next;
            next.next = pre.next;
            pre.next = next;
        }
        return dummy.next;
    }
}

穿针引线法

算法步骤:

  • 第 1 步:先将待反转的区域反转;
  • 第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        for(int i=0; i<left-1;i++) {
            pre = pre.next;
        }
        
        ListNode rightNode = pre;
        for(int i=0; i < right - left + 1; i++) {
            rightNode = rightNode.next;
        }

        ListNode leftNode = pre.next;
        ListNode succ = rightNode.next;

        rightNode.next = null;

        reverseLinkedList(leftNode);

        pre.next = rightNode;
        leftNode.next = succ;
        return dummy.next;
    }
    private void reverseLinkedList(ListNode head) {
        // 也可以使用递归反转一个链表
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
    }
}

两两交换链接中的节点

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

这道题因为要成对反转,所以我们可以先画图看一下调转的时候如何调整每个结点的指向。

如果原始顺序是 dummy -> node1 -> node2,交换后面两个节点关系要变成 dummy -> node2 -> node1,事实上我们只要多执行一次next就可以拿到后面的元素,也就是类似node2 = temp.next.next这样的操作。 两两交换链表中的节点之后,新的链表的头节点是 dummyHead.next,返回新的链表的头节点即可。指针的调整可以参考如下图示:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummyHead.next;
    }
}

单链表加1

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

我们先看一下加法的计算过程:

计算是从低位开始的,而链表是从高位开始的,所以要处理就必须反转过来,此时可以使用栈,也可以使用链表反转来实现。

基于栈实现的思路不算复杂,先把题目给出的链表遍历放到栈中,然后从栈中弹出栈顶数字 digit,加的时候再考虑一下进位的情况就ok了,加完之后根据是否大于0决定视为下一次要进位

public ListNode plusOne(ListNode head) {
    Stack<Integer> st = new Stack();
    while (head != null) {
        st.push(head.val);
        head = head.next;
    }
    int carry = 0;
    ListNode dummy = new ListNode(0);
    int adder = 1;
    while (!st.empty() || carry > 0) {
        int digit = st.empty() ? 0 : st.pop();
        int sum = digit + adder + carry;
        carry = sum >= 10 ? 1 : 0;
        sum = sum >= 10 ? sum - 10 : sum;
        ListNode cur = new ListNode(sum);
        cur.next = dummy.next;
        dummy.next = cur;
        adder = 0;
    }
    return dummy.next;
}

链表加法

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

使用栈实现

思路是先将两个链表的元素分别压栈,然后再一起出栈,将两个结果分别计算。之后对计算结果取模,模数保存到新的链表中,进位保存到下一轮。完成之后再进行一次反转就行了。

我们知道在链表插入有头插法和尾插法两种。头插法就是每次都将新的结点插入到head之前。而尾插法就是将新结点都插入到链表的表尾。两者的区别是尾插法的顺序与原始链表是一致的,而头插法与原始链表是逆序的,所以上面最后一步如果不想进行反转,可以将新结点以头插法

public static ListNode addInListByStack(ListNode head1, ListNode head2) {
    Stack<ListNode> st1 = new Stack<ListNode>();
    Stack<ListNode> st2 = new Stack<ListNode>();
    while (head1 != null) {
        st1.push(head1);
        head1 = head1.next;
    }
    while (head2 != null) {
        st2.push(head2);
        head2 = head2.next;
    }
    ListNode newHead = new ListNode(-1);
    int carry = 0;
    //这里设置carry!=0,是因为当st1,st2都遍历完时,如果carry=0,就不需要进入循环了
    while (!st1.empty() || !st2.empty() || carry != 0) {
        ListNode a = new ListNode(0);
        ListNode b = new ListNode(0);
        if (!st1.empty()) {
            a = st1.pop();
        }
        if (!st2.empty()) {
            b = st2.pop();
        }
        //每次的和应该是对应位相加再加上进位
        int get_sum = a.val + b.val + carry;
        //对累加的结果取余
        int ans = get_sum % 10;
        //如果大于0,就进位
        carry = get_sum / 10;
        ListNode cur = new ListNode(ans);
        cur.next = newHead.next;
        //每次把最新得到的节点更新到neHead.next中
        newHead.next = cur;
    }
    return newHead.next;
}

使用链表反转实现

如果使用链表反转,先将两个链表分别反转,最后计算完之后再将结果反转,一共有三次反转操作,所以必然将反转抽取出一个方法比较好,代码如下:

public class Solution {
    public ListNode addInList (ListNode head1, ListNode head2) {
        head1 = reverse(head1);
        head2 = reverse(head2);
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        int carry = 0;
        while(head1 != null || head2 != null) {
            int val = carry;
            if (head1 != null) {
                val += head1.val;
                head1 = head1.next;
            }
            if (head2 != null) {
                val += head2.val;
                head2 = head2.next;
            }
            cur.next = new ListNode(val % 10);
            carry = val / 10;
            cur = cur.next;
        }
        if (carry > 0) {
            cur.next = new ListNode(carry);
        }
        return reverse(head.next);
    }

    private ListNode reverse(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while(cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

再论回文序列问题

之前是使用栈的方法,这次使用反转,而且只反转链表的一半,那么就可以使用快慢指针和反转了

核心就是反转、快慢指针找到中间位置,需要注意 fast 是否为 null 。为 null 说明节点为偶数,否则为技术,后面需要根据这个去遍历判断链表是否相等

public boolean isPalindrome(ListNode head) {
    if(head == null || head.next == null) {
        return true;
    }
    ListNode slow = head, fast = head;
    ListNode pre = head, prepre = null;
    while(fast != null && fast.next != null) {
        pre = slow;
        slow = slow.next;
        fast = fast.next.next;
        //将前半部分链表反转
        pre.next = prepre;
        prepre = pre;
    }
    if(fast != null) {
        slow = slow.next;
    }
    while(pre != null && slow != null) {
        if(pre.val != slow.val) {
            return false;
        }
        pre = pre.next;
        slow = slow.next;
    }
    return true;
}