【刷题记录】链表系列

链表

1.反转链表

核心思想:

1.准备指向当前和前序结点指针。

2.在当前的指针反转之前,保存后续,以便后移

class SolutionBM1 {
    public ListNode ReverseList(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

2.反转指定区间链表

具象的描述:一共六本书,要把第2-4反转位置,每次把第二本下面的书放到第一本下面,重复三次就完成了。类似于后插法逆置。

核心思想:

1.因为涉及到第一个结点也可能被操作,所以设置 头结点,next指向head。

2.然后把指定位置前一位的结点作为头,然后指定区间内的结点使用头插法插入。

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param m int整型
     * @param n int整型
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // write code here
        ListNode headnode = new ListNode(-1);//对于头指针会被操作的结点,引入头结点来使其操作统一。
        headnode.next = head;
        ListNode pre = headnode;
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            ListNode temp = cur.next;
            count++;
            //不看if的话就是单纯的遍历链表
            //发现在区间内使用头插法。
            //在例子1 2 3 4 5【2,4】中。找到2时,固定pre,也就是1,对2-4进行头插。
            //头插的关键在于,先让当前结点继承后结点的next,再让后结点插入pre和cur之间。
            if (count >= m && count < n) {
                cur.next = temp.next;
                temp.next = pre.next;
                pre.next = temp;
                continue;
            }
            pre = cur;
            cur = temp;
        }
        return headnode.next;
        // while(headnode!=null){
        //     System.out.print(headnode.val+" ");
        //     headnode = headnode.next;
        // }
        // // return headnode.next;
        // return head;
    }
}

3.链表中的结点每k个一组翻转

第2题时指定k个,此题是每k个。

关键思路就是,先理清楚到底有多少段(每段k个)需要反转,每一段的反转逻辑是一样的,所以理所当然的使用循环控制。

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        if (head == null) {
            return head;
        }
        //count先作为计数,来统计链表中结点个数
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        //不足k个的段,其结点保持原样。
        if (count == 0) {
            return head;
        }
        //定义头结点,方便头指针的反转。
        ListNode headnode = new ListNode(-1);
        headnode.next = head;
        ListNode pre = headnode;
        cur = head;
        //此时count除k之后,count表示,原链表中前面count段是需要反转的。
        count = count / k;
        

        for (int i = 0; i < count; i++) {
            //注意是k-1,举例:反转4个结点只需要移动三次。
            for (int j = 0; j < k-1; j++) {
                //经典的头插法,见第2题。
                ListNode next = cur.next;
                cur.next = next.next;
                next.next = pre.next;
                pre.next = next;
                
            }
            //每次完成一段的反转之后,要移动pre和cur,才能完成后续操作
            pre = cur;
            cur = cur.next;
        }
        return headnode.next;
    }
}

4.合并两个排序的链表

这个题可以理解成双指针,比较两链表当前cur1、cur2的大小,则其小者放入cur3(cur3最初指向头结点,头结点的next任意指向一个链表即可)

public class Solution {
        public ListNode Merge(ListNode list1, ListNode list2) {
            if (list2 == null) {
                return list1;
            }
            if (list1 == null) {
                return list2;
            }
            ListNode head = new ListNode(-1);
            head.next = list1;

            ListNode cur1 = list1;
            ListNode cur2 = list2;
            ListNode pre = head;
            while (cur1 != null && cur2 != null) {
                if (cur1.val < cur2.val) {
                    pre.next = cur1;
                    cur1 = cur1.next;
                } else {
                    pre.next = cur2;
                    cur2 = cur2.next;
                }
                pre = pre.next;
            }
            if (cur1 != null) {
                pre.next = cur1;
            } else {
                pre.next = cur2;
            }
            return head.next;
        }
    }

5.BM5_合并k个已排序的链表

我还没有具体使用ArrayList,目前一个比较简单的想法是。count=ArrayList.size()得到要创建的指针数,每一轮得到最小的数(然后有指针空,count–),当count=1时,直接接上,但是这个做法的时间复杂度是 n*n。

6.BM6_判断链表中是否有环

判断是否有环最好用的就是 双指针中的快慢指针

一个指针走两步,一个走一步,有环的话就必相遇,无环的话,就快指针必然率先为空。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode cur1 = head;
        ListNode cur2 = head;

        while (cur2 != null && cur2.next != null) {
            cur1 = cur1.next;
            cur2 = cur2.next.next;
            if (cur1 == cur2) {
                return true;
            }
        }
        return false;
    }
}

7.BM7 链表中环的入口结点

此题是一个6的基础上一道数学题。

在这里插入图片描述
(上图是来自东哥公众号,侵删)

快指针每次移动两步,慢指针每次移动一步。所以相遇时,快比慢多走了k步(设经历了k次while循环),而此时慢也走了k步,设相遇点距离环起点为m。

慢指针距离起点是k-m;

一圈是k,慢再走k-m可以到环起点;

所以此时再令一个p3从头出发,慢指针继续走,两指针走k-m必然相遇。且相遇点为环起点。

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if (pHead == null) {
        return null;
    }
	ListNode cur1 = pHead;
	ListNode cur2 = pHead;
	ListNode cur3 = pHead;
	while (cur2 != null && cur2.next != null) {
        cur1 = cur1.next;
        cur2 = cur2.next.next;
        if (cur2 == cur1) {
            while (cur3 != cur1) {
                cur3 = cur3.next;
                cur1 = cur1.next;
            }
            return cur1;
        }
    }
    return null;
}

8.BM8 链表中倒数最后k个结点

此题我想的是弄一个能压缩伸长的杆。

在这里插入图片描述

但是因为循环条件是 cur1为空,所以实际做的时候,要注意控制条件的 增1/减1

public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        // write code here
        int count = 0;
        ListNode cur1 = pHead;
        ListNode cur2 = pHead;
        while (cur1 != null) {
            count++;//此处懒得再写了,其实可以count=k之后,就停止count的增加,但是不影响
            if (count > k) {//为什么是>,因为cur1指向空的时候,cur2相当于多往后移动了1位。所以不能是>=
                cur2 = cur2.next;
            }
            cur1 = cur1.next;
        }
        if (count >= k) {
            return cur2;
        } else {
            return null;
        }
    }
}

9.BM9 删除链表的倒数第n个结点

本题相当于就是找倒数第n+1和结点,然后执行删除操作。

因为本体保证n一定有效,所以就不需要像上题最后还要验证,倒数第n+1个到底存不存在。

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param n int整型
     * @return ListNode类
     */
    public ListNode removeNthFromEnd (ListNode head, int n) {
        // write code here
        if (head == null) {
            return null;
        }
        ListNode headnode = new ListNode(-1);//建立头结点,因为head也是有可能变动的,这也可以统一处理。
        headnode.next = head;
        ListNode pre = headnode;
        ListNode cur = headnode;
        int count = 1;//代表相对于cur1,pre是倒数第几个结点。所以当cur指向空时,我们需要删除的是倒数第n+1个结点(cur为空,相当于整体后移了,所以要删除的是此时倒数n+1)。
        while (cur != null) {
            if (count > n + 1) {//要删除的是n+1,所以要找倒数第n+2个,所以是>,而非>=。
                pre = pre.next;
            }
            count++;
            cur = cur.next;
        }
        pre.next = pre.next.next;
        return headnode.next;
    }
}

10.BM10 两个链表的第一个公共结点

这个题老演员了,我一下想到了两种方法。

法一:遍历两链表,记录长度,结合双指针,长的先走|长度差|,然后一起走,结点相等时,则是第一个公共点。

法二:双指针一起走,走完就指向另外一个链表的头指针,然后第一个相遇点就是第一个公共结点。(该方法也最后先算出谁长谁短,方便在代码中加以控制)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值