链表反转的拓展问题(算法村第二关白银挑战)

理解解决链表题目的逻辑和代码的精髓在于画图,动手画图

反转指定区间的结点

题目描述

92. 反转链表 II - 力扣(LeetCode)

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

示例 1:

链表反转的拓展问题(算法村第二关白银挑战)_第1张图片

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

头插法

public ListNode reverseBetween(ListNode head, int left, int right) 
{
    ListNode dummy = new ListNode(-1, head);
    ListNode pre_left = dummy; //left结点的前驱

    for (int i = 0; i < left - 1; i++)
        pre_left = pre_left.next;

    ListNode leftNode = pre_left.next;

    for (int i = 0; i < right - left; i++) 
    {
        ListNode suc = leftNode.next;   //suc是leftNode的后继
        leftNode.next = suc.next;
        suc.next = pre_left.next; 
        pre_left.next = suc;    
    }

    return dummy.next;
}

链表反转的拓展问题(算法村第二关白银挑战)_第2张图片

链表反转的拓展问题(算法村第二关白银挑战)_第3张图片

先反转,再拼接

public ListNode reverseBetween_2(ListNode head, int left, int right)
{
    ListNode dummy = new ListNode(-1,head);

    //定位左结点和左结点的前驱
    ListNode leftNode_pre = dummy;

    for (int i = 0; i < left - 1; i++)
        leftNode_pre = leftNode_pre.next;

    ListNode leftNode = leftNode_pre.next;

    //定位右结点和右结点的后继
    ListNode rightNode = leftNode;

    for (int j = 0; j < right - left; j++)
        rightNode = rightNode.next;

    ListNode rightNode_suc = rightNode.next;

    //确定反转区间的最后一步
    rightNode.next = null;

    //反转指定区间
    reverseList(leftNode);

    //拼接
    leftNode_pre.next = rightNode;
    leftNode.next = rightNode_suc;

    return dummy.next;
}

public ListNode reverseList(ListNode head)
{
    ListNode dummy = new ListNode(-1);
    ListNode suc;   //后继结点

    while(head != null)
    {
        //头插法
        suc = head.next;
        head.next = dummy.next; //把当前结点插在dummy和dummy的下一个结点(或null)之间
        dummy.next = head;  //让当前结点作为dummy的下一个结点
        head = suc;
    }

    return dummy.next;
}

两两交换链表中的结点

题目描述

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

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

示例 1:

链表反转的拓展问题(算法村第二关白银挑战)_第4张图片

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

public ListNode swapPairs(ListNode head)
{
    ListNode dummy = new ListNode(-1, head);
    ListNode t = dummy;

    while (t.next != null && t.next.next != null)
    {
        //两两交换
        ListNode node1 = t.next;
        ListNode node2 = t.next.next;

        t.next = node2;
        node1.next = node2.next;
        node2.next = node1;

        t = node1;
    }

    return dummy.next;
}

链表反转的拓展问题(算法村第二关白银挑战)_第5张图片

单链表+1

题目描述

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

给定一个用链表表示的非负整数, 然后将这个整数 再加上 1

这些数字的存储是这样的:最高位有效的数字位于链表的首位 head

示例 1:

输入: head = [1,2,3]
输出: [1,2,4]

示例 2:

输入: head = [0]
输出: [1]

提示:

  • 链表中的节点数在 [1, 100] 的范围内。

栈+头插

public ListNode plusOne(ListNode head)
{

   ArrayDeque<Integer> stack = new ArrayDeque<>();

   //结点的值入栈
   while (head != null)
   {
       stack.push(head.val);
       head = head.next;
   }



    ListNode dummy = new ListNode(-1);

   int carry = 0;   //进位
   int adder = 1;
    while (!stack.isEmpty() || carry == 1)
    //栈为空但进位仍为1的情况:999->1000
    {
        int val = stack.isEmpty() ? 0 : stack.pop();

        int sum = val + carry + adder;
        adder = 0;  //只让adder起一次作用,即只给尾结点加一次1

        carry = sum / 10;

        ListNode node = new ListNode(sum % 10);

        node.next = dummy.next;
        dummy.next = node;
    }

   return dummy.next;
}

反转,+1,再反转

public ListNode plusOne_2(ListNode head)
    {
        ListNode lastNode = head;   //lastNode是第一次反转后的尾结点
        head = reverseList(head);

        int carry = 0;
        int adder = 1;

        ListNode cur = head;
        while (cur != null)
        {
            int sum = cur.val + carry + adder;
            adder = 0;

            cur.val = sum % 10;
            carry = sum / 10;

            cur = cur.next;
        }


        if(carry == 1)
            lastNode.next = new ListNode(1);

        head = reverseList(head);

        return head;
    }

public ListNode reverseList(ListNode head)
{
    ListNode pre = null;    //头结点的前驱是null
    ListNode suc;    //后继

    while (head != null)
    {
        suc = head.next;
        head.next = pre;    //当前结点的后继更新为前驱
        pre = head;     //前驱后移一位
        head = suc;     //当前结点后移一位
    }

    return pre; //此时前驱指向原链表的尾结点,即反转链表的头结点
}

链表加法

题目描述

445. 两数相加 II - 力扣(LeetCode)

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

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:

链表反转的拓展问题(算法村第二关白银挑战)_第6张图片

输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

示例2:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]

示例3:

输入:l1 = [0], l2 = [0]
输出:[0]

提示:

  • 链表的长度范围为 [1, 100]

**进阶:**如果输入链表不能翻转该如何解决?

栈+头插

public ListNode addTwoNumbers(ListNode l1, ListNode l2)
{
    ArrayDeque<Integer> stack1 = new ArrayDeque<>();
    ArrayDeque<Integer> stack2 = new ArrayDeque<>();

    while (l1 != null)
    {
        stack1.push(l1.val);
        l1 = l1.next;
    }

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

    ListNode dummy = new ListNode(-1);
    int carry = 0;

    while (!stack1.isEmpty() || !stack2.isEmpty() || carry ==1)
    {
        int val1 = stack1.isEmpty() ? 0 : stack1.pop();
        int val2 = stack2.isEmpty() ? 0 : stack2.pop();

        int sum = val1 + val2 + carry;
        carry = sum / 10;

        ListNode node = new ListNode(sum % 10);

        node.next = dummy.next;
        dummy.next = node;
    }

    return dummy.next;
}

反转+头插

public ListNode addTwoNumbers_2(ListNode l1, ListNode l2)
{
    l1 = reverseList(l1);
    l2 = reverseList(l2);

    ListNode dummy = new ListNode(-1);
    int carry = 0;

    while (l1 != null || l2 != null || carry == 1)
    {
        int val1 = l1 == null ? 0 : l1.val;
        int val2 = l2 == null ? 0 : l2.val;

        int sum = val1 + val2 + carry;
        carry = sum / 10;

        ListNode node = new ListNode(sum % 10);

        node.next = dummy.next;
        dummy.next = node;

        if (l1 != null)
            l1 = l1.next;
        if (l2 != null)
            l2 = l2.next;
    }

    return dummy.next;
}

public ListNode reverseList(ListNode head)
{
    ListNode pre = null;    //头结点的前驱是null
    ListNode suc;    //后继

    while (head != null)
    {
        suc = head.next;
        head.next = pre;    //当前结点的后继更新为前驱
        pre = head;     //前驱后移一位
        head = suc;     //当前结点后移一位
    }

    return pre; //此时前驱指向原链表的尾结点,即反转链表的头结点
}

栈+反转。以l1和l2中较长链表为答案

public ListNode addTwoNumbers_3(ListNode l1, ListNode l2)
{
    ArrayDeque<Integer> stack1 = new ArrayDeque<>();
    ArrayDeque<Integer> stack2 = new ArrayDeque<>();

    ListNode t;

    t = l1;
    while (t != null)
    {
        stack1.push(t.val);
        t = t.next;
    }

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

    ListNode ans = stack1.size() >= stack2.size() ? l1 : l2;

    ListNode lastNode = ans;   //lastNode是第一次反转后的尾结点
    ans = reverseList(ans);

    int carry = 0;

    ListNode cur = ans;
    while (cur != null)
    {
        int val1 = stack1.isEmpty() ? 0 : stack1.pop();
        int val2 = stack2.isEmpty() ? 0 : stack2.pop();

        int sum = val1 + val2 + carry;
        cur.val = sum % 10;
        carry = sum / 10;

        cur = cur.next;
    }


    if(carry == 1)
        lastNode.next = new ListNode(1);

    ans = reverseList(ans);

    return ans;
}

public ListNode reverseList(ListNode head)
    {
        ListNode pre = null;    //头结点的前驱是null
        ListNode suc;    //后继

        while (head != null)
        {
            suc = head.next;
            head.next = pre;    //当前结点的后继更新为前驱
            pre = head;     //前驱后移一位
            head = suc;     //当前结点后移一位
        }

        return pre; //此时前驱指向原链表的尾结点,即反转链表的头结点
    }

"快慢指针+一半反转"法解决回文链表

题目描述

234. 回文链表 - 力扣(LeetCode)

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

示例 1:

img

输入:head = [1,2,2,1]
输出:true

public boolean isPalindrome(ListNode head) 
    {
        if(head == null)
            return true;
        
        // 找到前半部分链表的尾节点并反转后半部分链表
        ListNode firstHalfEnd = endOfFirstHalf(head);
        ListNode secondHalfStart = reverseList(firstHalfEnd.next);

        //判断是否是回文链表
        ListNode p = head;
        ListNode q = secondHalfStart;
        boolean ans = true;
        while(q != null)
        {
            if(p.val != q.val)
            {
                ans = false;
                break;
            }

            p = p.next;
            q = q.next;      
        }

        //恢复后半部分链表
        firstHalfEnd.next = reverseList(secondHalfStart);

        //返回判断结果
        return ans;
    }
//寻找中间结点。若链表有奇数个节点,则中间的节点应该看作是前半部分。
    public ListNode endOfFirstHalf(ListNode head)
    {
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null)
        {
            fast = fast.next.next;
            slow = slow.next;
        }

        return slow;
    }

    //反转链表
    public ListNode reverseList(ListNode head)
    {
        ListNode pre = null;
        ListNode suc = head;

        while(head != null)
        {
            suc = head.next;
            head.next = pre;     
            pre = head;     
            head = suc;     
        }

        return pre;
    }

你可能感兴趣的:(算法村,链表,算法,数据结构)