IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

链表算法题型的总结

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

前言

最近也是在刷算法题,从刷题刷的开始怀疑人生,觉得自己好’菜’,到后来慢慢的找到了写算法的感觉,然后再到对算法产生了极大的兴趣。其实一开始做算法确实挺痛苦的,那种挫败感让我很沮丧,但是最近两个星期做下来,感觉算法没有想象的那么恐怖吧,因为我们不需要去创造算法,我们只需要掌握解题的思路和方法,然后加以刻意训练都是可以掌握的。

这里我建议大家,如果刷算法题不要直接从LeetCode列表上开始刷,因为算法题实在是太多了刷根本是不可能刷完的,所以我建议大家可以从某一类的题型开始刷,我建议先从链表或者树开始去做,这里我给出我的理由:

第一、像数组类型的算法题数量实在是太多,而且题型考察的形式也非常多,短时间内你看不到自己的进步很容易放弃,树和链表的算法题型比较少,考察的形式也不是很多,掌握起来相对还是比较容易的,短时间内可以提升自己做算法题的信心。

第二、像单链表的一些题目,它对指针的使用是非常多的,像我们做算法题比较难的还是对于指针的一些使用。虽然有些算法题可以借助一些语言提供的工具类来做,但是会额外的使用多余的内存空间,可能某些算法题要求你必须使用O(1)的空间复杂度,这种情况下就必须使用指针来做了。

第三、当你刷完单链表的题目,你对于指针的使用会初步的有一定感觉,对于后面一些算法题目是很好的帮助。

所以我建议大家如果开始刷题,建议从链表题目开始刷起。

1.需要操作单链表中倒数某个指定节点

1.1 解题思路及代码模板

这类算法题目,核心的关键是需要定位到题目所给的具体某个节点的位置,只要找到这个指定的节点位置,剩下的问题也就很好解决了。我下面列出了几道LeetCode上的算法题,一起感受一下。

1、 返回一个链表的倒数第 k 个节点
2、 删除链表的倒数第N个节点
3、 旋转链表

我们做这类的题目,思路基本上都差不多,我们可以把这种类似的题目总结出来一个模板:这类题经常使用的就是快慢指针来解题,通过快慢指针定位到我们想要操作的节点位置,找到了相应的位置,根据题目对其进行相应的操作就可以了。这里我简单的做了一个动图,先让快指针走k步,然后让快慢指针一起向后移动,就找到了倒数第二个链表节点。

67_1.png

其模板代码如下:

public static ListNode method(ListNode head, int k) {
        //快指针,我们先让它走k步,
        ListNode fast = head;
        for (int i = 0; i < k; i++) {
            fast = fast.next;
 } //然后将快的指针和慢的指针一起走,当快指针走到链表末尾,慢指针指向的就是倒数第K个节点 ListNode temp = head; //你要找的倒数第k个节点的前一个节点。因为如果你要操作第k个节点,一般需要找到第K个节点的前前驱节点 //像上面第2道的算法题,删除链表的第K个节点就需要用到preNode ListNode preNode = null; while (fast != null ) { preNode = temp; temp = temp.next; fast = fast.next; } return temp; } 

第一道算法题,我们利用上面这个模板代码就可以搞定。

第二道算法题,需要删除倒数第k个节点,我们还是利用上面的代码模板找到,不过这里要操作倒数第k个节点,一定要先找到这个节点的之前节点。、

第三道算法题,在Leetcode上是一道中等难度的题,其思路和前面两道算法题大致思路都是一样的,我这里就不把原题copy过来了,上面的链接直接就能跳转到题目。题意是:给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

这个题目没有限制说旋转的长度不能超过链表的长度,所以说旋转的长度可能会超过链表的长度,所以这道算法题主要的思路还是要找到需要旋转的节点:

1、 计算链表的长度
2、 判断旋转的长度是多少,这里利用 k % length;
3、 利用我们上面说的方法定位到需要旋转的节点。
4、 得到我们想要的结果。

其完整代码如下:

 public ListNode rotateRight(ListNode head, int k) {
      if(head ==null||head.next ==null){
            return head; 
        }
        //先拿到链表的长度。
 int nodeLength = 0; ListNode d = head; while (d != null) { nodeLength++; d = d.next; } //拿到长度以后,计算需要移动几个节点。 int remaind =k % nodeLength ; //如果mid等于0,相当于没有移动。 if (mid == 0) { return head; } d = head; //这里就是我们上面所说的快慢指针,先让快的指针走remaind for (int i = 0; i < remaind; i++) { d = d.next; } ListNode temp = head; //记录慢指针指向的前一个节点 ListNode prev = null; //记录快指针指向的前一个节点 ListNode tailPrev = null; while (d != null) { prev = temp; tailPrev = d; temp = temp.next; d = d.next; } prev.next = null; tailPrev.next = head; return temp; } 

除了这类需要找到倒数第k个节点以外,其实还有一类也是需要找到指定的节点位置,这个节点是比较特殊的一个中间节点,这样类似的题目我们也需要利用快慢指针就可以解决,我们让快指针每次走两步,我们让慢指针每次走一步,这样当快指针走到链表的末尾,慢指针就指向了链表的中间节点。

public static ListNode findMidNode(ListNode head) {
        ListNode fastNode = head;
        ListNode slowNode = head;
        ListNode prev = null;
        while (fastNode != null && fastNode.next != null) {
 prev = slowNode; fastNode = fastNode.next.next; slowNode = slowNode.next; } return slowNode; } 

1.2 练习题目

看完上面的,那就拿这个题来练一练,原题目在这里:重排链表

给定一个单链表 L:L0→L1→…→L**n-1→Ln你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。示例 1:给定链表 1->2->3->4, 重新排列为 1->4->2->3. 示例 2:给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3

首先我说一下我的思路,其实看到这道题我们就会发现,其实这道题就是以链表的中点为准,将链表分成了两部分,将链表的后半部分倒序以后,挨个插入到前半部分的链表中。这样的话就可以用我们上边所说的那个模板,拿到两部分的链表了,得到前后两部分的链表以后,就合并这两部分链表就可以了。这里如何将后半部分的链表翻转,也是有模板可以套用的。

翻转链表的思路其实有两种一种是在原始链表上,另外一种利用栈这种数据结构,当空间复杂度为O(1)的时候,我们不能使用额外的数据结构,只能使用指针来进行操作链表进行翻转。

public static ListNode reverseList(ListNode head) {
        ListNode curr = head, prev = null;
        ListNode temp;
        while (curr != null) {
            temp = curr.next;
 curr.next = prev; prev = curr; curr = temp; } return prev; } 

另外一种方法就是利用栈来进行,写法比较简单,这里大家可以自己写一下。下面是重排链表完整的代码

public class Solution {  
 public void reorderList(ListNode head) {
        if (head == null || head.next == null || head.next.next == null) {
            return;
        }
 //找到链表的中间节点位置 ListNode fast = head; ListNode slow = head; ListNode prev = null; while (fast != null && fast.next != null) { prev = slow; slow = slow.next; fast = fast.next.next; } //这里就改变了原链表 head,head是前半部分链表 prev.next = null; //slow 就是后半部分的链表,这里将后半部分链表反转。这里可以利用栈。 ListNode listNode = reverseList(slow); //这里就将两个链表合并成一个,这里你可以新建一个链表去做,也可以直接在head的上操作。但是我测试性能的效果没差, //在head上操作可能会稍微复杂一些,需要你用好几个指针来做,不熟练的可以直接新建一个链表,因为指针指来指去的容易把自己弄迷了。 //利用栈也是可以实现的,这里自己可以试一下。 ListNode dummy = new ListNode(-1); ListNode curr = dummy; while(head != null && listNode != null){ curr.next = head; head = head.next; curr.next.next = listNode; listNode = listNode.next; curr = curr.next.next; } //这里需要注意的是判断后半部分的链表是否为空,可以知道链表是奇数节点还是偶数节点。 //如果是奇数节点的话,链表的后半部分会比前半部分多一个节点,因为我们把中间的那个节点算到的后半部分里面。 if(listNode != null){ curr.next = listNode; } head = dummy.next; } public static ListNode reverseList(ListNode head) { ListNode curr = head, prev = null; ListNode temp; while (curr != null) { temp = curr.next; curr.next = prev; prev = curr; curr = temp; } return prev; } } 

2. 回文链表 && 环形链表类型算法

回文链表和环形链表的题目相对还是简单一些。回文链表的判断就和第一节我们说的那样,利用快慢指针找到中点的位置,然后对比后半部分和前半部是否是逆序的就可以了。这里我们就重点来说一下环形链表的解题思路。

2.1 解题思路及代码模板

LeetCode上原题:判断一个链表是否是环形链表

第一种思路,我们可以利用hash表,我们遍历整个链表,如果存在环,那肯定会出现一个节点访问两次的情况。

 public static boolean hasCycle2(ListNode head) {
    HashSet set = new HashSet();
     while (head != null) {
         if (set.contains(head)) {
             return true;
 } else { set.add(head); } head = head.next; } return false; } 

利用hash表这种解法,其时间复杂度为O(n),空间复杂度也是O(n)。这种方法是最简单也是最容易想到的。

第二种思路,利用快慢指针,我们让快指针的速度是慢指针的两倍,这样去遍历整个链表,如果单链表中存在有环,那么两个指针一定会在某一个节点相遇。代码如下:

public  boolean hasCycle(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
 slow = slow.next; if (fast == slow) { return true; } } if (head == null || head.next == null) { return false; } return false; } 

环形链表的进阶题: 返回一个链表入环的第一个节点,如果没有环返回Null

67_2.png

如果一个单链表存在环,那么肯定会在环上的某一个节点两个指针相遇。当两个指针相遇时,慢指针走了len + s1 的长度;快指针走了len+n(s1+s2)+s1的长度,因为快指针一直在环上循环所以是n(s1+s2)。因为快指针是慢指针速度的两倍,相同时间内走过的长度肯定是相等的,所以可以得出公式:2(len+S1) = len + n(S1+S2)+S1,化简之后得到公式len = (n-1)S1 + nS2。n代表的是快指针和慢指针相遇的时候,快指针在环上转了几圈。这个等式是恒成立的,可以自己自行带入测试一下哦。当我们忽略 n 这个变量的时候,让n =1 ,公式就变成了 len = s2。那么就是当两个指针相遇以后,分别再让两个指针从A和C节点同时出发,每次移动一个节点,当再次相遇的时候就是环形链表的入口节点了。代码如下:

public  ListNode hasCycle(ListNode head) {
    ListNode slow = head,fast = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
 if (fast == slow) { break; } } if (fast == null || fast.next == null) return null; fast = head; while (fast != slow) { fast = fast.next; slow = slow.next; } return fast; } 

3. 链表排序 && 合并链表

算法题目:

1、 链表排序
2、 合并两个有序链表
3、 合并K个有序链表

3.1 解题思路

第一道算法题,链表排序和正常的数据排序是一样解题思路,因为链表不能随机访问链表中的节点,我们只能通过遍历整个链表,这里我们可以利用归并排序的方法对链表进行排序,相信大家对归并排序已经很熟悉了,这里我们直接来上代码。

对链表进行归并排序,我们首先要找到链表的中点将链表分成两部分,对这两部分链表进行排序。然后分别在对链表的这两部分重复前面的动作,直到结束。

public static ListNode sortList(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode fast = head.next, slow = head;
 while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } ListNode tmp = slow.next; slow.next = null; ListNode left = sortList(head); ListNode right = sortList(tmp); return merge(left, right) ; } //合并左右两部分 private static ListNode merge(ListNode l1, ListNode l2) { ListNode temp1 = l1, temp2 = l2; ListNode dummy = new ListNode(-1); ListNode prev = dummy; while (temp1 != null && temp2 != null) { if (temp1.val > temp2.val) { dummy.next = temp2; temp2 = temp2.next; } else { dummy.next = temp1; temp1 = temp1.next; } dummy = dummy.next; } dummy.next = temp1 != null ? temp1 : temp2; return prev.next; } 

其实合并两个有序链表,是我们第一道算法题中的一部分,合并两个有序链表的代码就直接可以用到第一道算法题的后面部分。这里需要注意的是,此方法只能用于合并两个有序链表

那像第三道算法题合并K个有序链表该怎么做呢?这里其实有很多种方法,比如:

1、利用Java中的优先队列PriorityQueue来实现,因为PriorityQueue底层使用的是二叉堆来实现的,最小的元素永远在堆顶,这样我们就可以多个有序链表合并了。不过这里要注意的是,多个链表是有序的我们才可以这样做,当合并多个无序的链表,我们就不能这样做了。

2、我们可以还是继续利用我们第二题的思路来,不管合并几个链表,我们可以将他们两两合并,最后汇总成一个链表,这种解法无论链表是有序还是无序的,我们都可以这样操作。

1、 优先队列

public static ListNode mergeKLists(ListNode[] lists) {
    Queue<ListNode> pq = new PriorityQueue<>((v1,v2)->v1.val - v2.val);
    for (ListNode node : lists) {
        if (node != null) {
            pq.offer(node);
 } } ListNode dummyHead = new ListNode(-1); ListNode tail = dummyHead; while (!pq.isEmpty()) { ListNode minNode = pq.poll(); tail.next = minNode; tail = minNode; if (minNode.next != null) { pq.offer(minNode.next); } } return dummyHead.next; } 

1、 两两合并

public static ListNode mergeKLists2(ListNode[] lists) {
    if (lists == null || lists.length == 0) {
        return null;
    }
    return helper(lists, 0, lists.length - 1);
} private static ListNode helper(ListNode[] lists, int begin, int end) { if(begin==end) { return lists[begin]; } int mid = begin+(end-begin)/2; ListNode left = helper(lists,begin,mid); ListNode right = helper(lists,mid+1,end); return merge(left,right); } //合并两个有序链表 private static ListNode merge(ListNode l1, ListNode l2) { ListNode temp1 = l1, temp2 = l2; ListNode dummy = new ListNode(-1); ListNode prev = dummy; while (temp1 != null && temp2 != null) { if (temp1.val > temp2.val) { dummy.next = temp2; temp2 = temp2.next; } else { dummy.next = temp1; temp1 = temp1.next; } dummy = dummy.next; } dummy.next = temp1 != null ? temp1 : temp2; return prev.next; } 

4.链表交换类型题目

1、 翻转指定节点位置的链表
2、 两两交换链表中的节点
3、 K个一组翻转链表

这类型的题目没有特别固定模板可以套用,你唯一需要做的就是掌握到解决这种题目的感觉。虽然说没有固定的模板可以套用,但是还是有重点可以画出来的。第一步:我们需要判断出什么时候需要交换两个节点。就拿上面的第一道算法题来说,需要交换的节点是第三个至第五个节点。第二步:然后找到需要交换节点的前驱节点和后继节点。这样你才能成功移动节点。第三步:这一步就需要判断如何移动指针,可以满足你后续循环动作。

67_3.png

我们就拿第一道算法题来说,为了方便理解,可以参考上面的动画,按照我们上面说的一起来看一下时候解决问题。

第一步:根据题意,我们找到需要交换的节点位置。 这里题目给出了需要交换 k —> n 之间的节点,那之外的节点是不需要动的。所以这里我们需要判断我们遍历的链表长度是否是在k – n 之间的节点。

第二步:找到需要交换节点的前驱和后继节点。这里我遍历的是3-5之间的链表节点。那既然已经确定了需要改变的节点位置,那就找到需要改变节点的前驱和后继节点,利用两个指针preNodenextNode 保存前驱和后继节点。然后将链表交换到我们想要让它去的位置。

第三部:我们将节点交换完成以后,我们还需要将我们需要用到的指针也做相应的移动。

完整代码如下:

public static ListNode reverseBetween(ListNode head, int m, int n) {
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    ListNode prev = dummy;
    int length = 0;
 ListNode temp; while (head.next != null) { length++; //找到需要交换节点的条件 if (length >= m && length < n) { //保存head 的后继节点 temp = head.next; //head.next 指向后继节的后继节点 head.next = temp.next; temp.next = prev.next; prev.next = temp; } else { if (length < m) { prev = prev.next; } head = head.next; } } return dummy.next; } 

第二道算法题,两两交换链表,这都题目其实很简单,这里最主要的还是控制好指针指向的位置。

第三道算法题在LeetCode上是一道困难的算法题,其实我们抛开题的难度,把题目分解一下就会发现其实也很简单。题目的要求是K个一组翻转链表,且K是小于链表的长度的。其实K个一个翻转链表,其实和翻转单个链表没有什么两样,只不过说我们要把K个链表分为一组进行翻转,然后将每个翻转以后的链表再拼接起来就可以了。

public static ListNode reverseKGroup(ListNode head, int k) {
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    ListNode pre = dummy;
    ListNode end = dummy;
 while (end.next != null) { for (int i = 0; i < k && end != null; i++) { end = end.next; } if (end == null) { break; } ListNode start = pre.next; ListNode next = end.next; end.next = null; pre.next = reverse(start); start.next = next; pre = start; end = start; } return dummy.next; } public static ListNode reverse(ListNode head) { ListNode curr = head, prev = null; ListNode temp; while (curr != null) { temp = curr.next; curr.next = prev; prev = curr; curr = temp; } return prev; } 

这里就不把所有的类型都一一给列举出来了,大部分的算法题目都是差不多的,我这里把他们大致分为了:链表指定节点的操作、链表排序、环形链表、链表节点交换、链表查找和删除、链表的反转等类型,思路是一样的,但是还是要多多的练习,有思路和能写出来是完全两码事。

最后

这里就不把所有的类型都一一给列举出来了,大部分的算法题目都是差不多的,我这里把他们大致分为了:链表指定节点的操作、链表排序、环形链表、链表节点交换、链表查找和删除、链表的反转等类型,思路是一样的,但是还是要多多的练习,有思路和能写出来是完全两码事。

文章永久链接:https://tech.souyunku.com/?p=37331


Warning: A non-numeric value encountered in /data/wangzhan/tech.souyunku.com.wp/wp-content/themes/dux/functions-theme.php on line 1154
赞(93) 打赏



未经允许不得转载:搜云库技术团队 » 链表算法题型的总结

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码
IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

评论 抢沙发

大前端WP主题 更专业 更方便

联系我们联系我们

觉得文章有用就打赏一下文章作者

微信扫一扫打赏

微信扫一扫打赏


Fatal error: Uncaught Exception: Cache directory not writable. Comet Cache needs this directory please: `/data/wangzhan/tech.souyunku.com.wp/wp-content/cache/comet-cache/cache/https/tech-souyunku-com/index.q`. Set permissions to `755` or higher; `777` might be needed in some cases. in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php:367 Stack trace: #0 [internal function]: WebSharks\CometCache\Classes\AdvancedCache->outputBufferCallbackHandler() #1 /data/wangzhan/tech.souyunku.com.wp/wp-includes/functions.php(5109): ob_end_flush() #2 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(303): wp_ob_end_flush_all() #3 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(327): WP_Hook->apply_filters() #4 /data/wangzhan/tech.souyunku.com.wp/wp-includes/plugin.php(470): WP_Hook->do_action() #5 /data/wangzhan/tech.souyunku.com.wp/wp-includes/load.php(1097): do_action() #6 [internal function]: shutdown_action_hook() #7 {main} thrown in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php on line 367