链表——在节点间穿针引线

例题:反转链表

反转链表原理很简单,就是将原本指向下一个结点的指针,指向上一个结点。但是,需要存储前面和后面的结点,要不就找不到了。
所以使用pre cur next三个结点,存储着上一个结点、当前结点、下一个结点,然后一起向后遍历。在这期间,最需要注意的就是顺序以及边界

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null) return null;
        ListNode pre = null;
        ListNode cur = head;
        ListNode next = head.next;
        while(next!=null){
            cur.next = pre;
            pre = cur;
            cur = next;
            next = next.next;
        }
        cur.next = pre;
        return cur;
        
    }
}

1.反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明: 1 ≤ m ≤ n ≤ 链表长度。

示例: 输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

草草在纸上画了一下,基本上弄清楚大概的思路了。还是和例题一样,用那三个指针。
这次,我们使用一个计数器,来判断循环的次数,当它到达m和n的时候,分别作出对应操作:

  • 到达m时,定义两个指针,first = pre; second = cur;
  • 到达n时,将first.next = cur; second.next = next;,最后返回head即可。但是,这里要注意两个点
    1.first为空则不执行first.next = cur
    2.如果从头结点便开始翻转,那么返回的头结点就是cur

从头到尾只进行了一趟扫描,所以时间复杂度为O(n)。

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if(head==null) return null;
        ListNode pre = null;
        ListNode cur = head;
        ListNode next = head.next;
        int num = 1;    //用来计数此时遍历到的是第几个结点
        while(num<m){
            pre = cur;
            cur = next;
            next = next.next;
            num++;
        }
        ListNode first = pre;   //记录之后要用到的结点
        ListNode second = cur;  
        while(num<n){           //翻转指定的链表
            cur.next = pre;
            pre = cur;
            cur = next;
            next = next.next;
            num++;
        }
        cur.next = pre;
        if(first!=null) first.next = cur;
        second.next = next;
        return m==1?cur:head;   //m等于1,说明头结点变了,要以cur作为头结点
    }
}

2.删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

乍一看思路很明显,因为是排序的,所以只需要比较下一个结点是否和当前结点值相等,相等next便指向下下个结点,然后再判断,知道与下一个结点值不同,再继续向下遍历。

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null) return null;
        ListNode cur = head;
        while(cur!=null && cur.next!=null){
            while(cur.next!=null && cur.next.val == cur.val){
                cur.next = cur.next.next;
            }
            cur = cur.next;
        }
        return head;
    }
}

But,要注意边界条件。毕竟在向后跳的时候,cur.nextcur都有可能为null,所以一定要判断好。

3.分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置

这个题乍一看像是一遍快排,但实际上又不是,因为这是个链表,而且还需要保留相对位置。那么其实我们只需要新建两个链表,一个用来存储比x小的,一个用来存储比x大的,最后将它们拼接不就好了。

思路确定,剩下的就是细节了。首先需要新建两个结点作为链表虚拟头结点,然后再用两个指针分别指向两个链表最末端的元素。
**最需要注意的!!!**在最后一定要把rpointnext设为null,要不然会形成环。
时间复杂度:O(n)。

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode left = new ListNode(0);
        ListNode lpoint = left;
        ListNode right = new ListNode(0);
        ListNode rpoint = right;
        ListNode cur = head;
        while(cur!=null){
            if(cur.val<x){
                lpoint.next = cur;
                lpoint = lpoint.next;
            }else{
                rpoint.next = cur;
                rpoint = rpoint.next;
            }
            cur = cur.next;
        }
        rpoint.next = null;
        lpoint.next = right.next;
        return left.next;
    }
}

4.奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1)时间复杂度应为 O(nodes),nodes 为节点总数。

其实思路和上一题还是挺相似的,都是分为两个新链表再合并,不过要注意:

  • 不能使用两次便利,一次遍历奇数,一次遍历偶数。因为第一次做的修改会对第二次造成影响。所以,必须在同一个循环中,两个新链表交替前进。最后再合并。
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode opoint = head;    //奇数指针
        ListNode even = head.next;  //偶数头结点
        ListNode epoint = even; //偶数指针
        while(opoint.next!=null && epoint.next!=null){
            opoint.next = epoint.next;
            opoint = opoint.next;
            epoint.next = opoint.next;
            epoint = epoint.next;
        }
        opoint.next = even;
        return head;
    }

5.两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

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

思路很简单,因为是逆序,所以设置一个进位c,用来表示从上一位进来的值。然后将当前的两个结点值和进位相加,得到的值进位或者不进位。需要注意的点有:

  • 当前结点为空时,如果另一个结点不为空或者c!=0时,那么相加时本结点值为0.
  • 最后终止条件为两链表都到末尾,且c==0.
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int c = 0; //表示从上一位进的值
        ListNode newList = new ListNode(0);
        ListNode np = newList;
        while(l1!=null || l2!=null || c!=0){
            int val1 = l1==null?0:l1.val;
            int val2 = l2==null?0:l2.val;
            int val = val1+val2+c;
            if(val>=10){
                c = 1;
                val -= 10;
            }else{
                c = 0;
            }
            ListNode temp = new ListNode(val);
            np.next = temp;
            np = np.next;
            if(l1!=null) l1 = l1.next;
            if(l2!=null) l2 = l2.next;
        }
        return newList.next;
    }
}

6.两数相加 II

给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。 进阶: 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

这个题难得的一次AC无任何错误,其实这个题归根结底和上一个差不多,只是顺序倒过来了而已。所以其实只要再倒回来就行了,但是题目要求不能修改链表。那么,对于倒转有一个强大的数据结构——栈。利用栈便可以轻松实现反转操作,且不修改链表。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<ListNode> s1 = new Stack<ListNode>();
        Stack<ListNode> s2 = new Stack<ListNode>();
        while(l1!=null){
            s1.push(l1);
            l1 = l1.next;
        }
        while(l2!=null){
            s2.push(l2);
            l2 = l2.next;
        }
        ListNode newList = null;
        int c = 0;
        while(!s1.empty() || !s2.empty() || c!=0){
            int val1 = s1.empty()?0:s1.pop().val;
            int val2 = s2.empty()?0:s2.pop().val;
            int val = val1+val2+c;
            if(val>=10){
                c = 1;
                val -= 10;
            }else{
                c = 0;
            }
            ListNode temp = new ListNode(val);
            temp.next = newList;
            newList = temp;
        }
        return newList;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值