【面试必刷101】链表

摘要

【面试必刷101】系列blog目的在于总结面试必刷101中有意思、可能在面试中会被考到的习题。总结通用性的解题方法,针对特殊的习题总结思路。既是写给自己复习使用,也希望与大家交流。

【面试必刷101】递归/回溯算法总结I(十分钟理解回溯算法)
【面试必刷101】递归/回溯算法总结II(十分钟刷爆回溯算法题)

1 链表基础知识

单链表

public class ListNode {
    int val;
    ListNode next = null;
    
    ListNode(int val) {
        this.val = val;
    }
}

双链表

public class ListNode {
    int val;
    ListNode next = null;
    ListNode pre = null;
    ListNode(int val) {
        this.val = val;
    }
}

链表这类题感觉出的比较少,注意以下几个题型就可以了。

2 面试必刷习题

2.1 k个一组反转链表

普通的反转链表:
在这里插入图片描述
很简单的一张图,就是将当前节点指向前一个节点,然后将当前节点变成下一个节点来进行遍历。

import java.util.*;
public class Solution {
public class Solution {
    public ListNode ReverseList(ListNode cur) {
        ListNode pre = null;
        ListNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

然后来看链表中每k个节点一组翻转

在这里插入图片描述

首先可以用递归来实现:每k个一组其实可以先做后面的再做前面的,大问题由很多小问题组成。
然后小问题可以用简单的链表翻转来做。具体看代码,还是很值得学习的。

import java.util.*;
public class Solution {
    public ListNode reverseKGroup (ListNode head, int k) {
        if (head == null || head.next == null) return head;
        ListNode tail = head;
        for (int i = 0; i < k; i++) {
            if (tail == null) {
                return head;
            }
            tail = tail.next;
        }
        // 翻转前k个元素
        ListNode newHead = reverse(head, tail);
        // head已经是最后一个元素了。这个递归很有灵性。
        head.next = reverseKGroup(tail, k);
        return newHead;
    }
    // 反转[head, tail)
    public ListNode reverse (ListNode cur, ListNode tail) {
        ListNode pre = null;
        ListNode next = null;
        while (cur != tail) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

2.2 合并k个链表

题目链接:BM5 合并k个已排序的链表
在这里插入图片描述
为啥这道题值得一做呢?主要考察点有三个:

  • 合并策略是啥? 基于归并排序思路的两两合并;优先队列。
  • 怎么合并两个有序链表? 建立一个头结点,谁小就插入谁。
import java.util.*;
public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        return mergeList(lists, 0, lists.size() - 1);
    }
    
    public ListNode mergeList(ArrayList<ListNode> list, int l, int r) {
        if (l== r) {
            return list.get(l);
        }
        if (l > r) {
            return null;
        }
        int mid = l + ((r - l) >> 1);
        ListNode left = mergeList(list, l, mid);
        ListNode right = mergeList(list, mid + 1, r);
        return merge2Lists(left, right);
    }
    
    // 合并两个链表
    public ListNode merge2Lists(ListNode list1, ListNode list2) {
        ListNode head = new ListNode(0);
        ListNode cur = head;
        while (list1 != null && list2 != null) {
            if (list1.val > list2.val) {
                cur.next = list2;
                list2 = list2.next;
            } else {
                cur.next = list1;
                list1 = list1.next;
            }
            cur = cur.next;
        }
        if (list1 != null) cur.next = list1;
        if (list2 != null) cur.next = list2;
        return head.next;
    }
}

2.3 链表相加

题目链接:链表相加(二)
在这里插入图片描述
乍一眼看起来好难呀?该从哪里开始相加呢?不清楚。为啥不清楚,因为从next Node找不到pre Node,这就是单链表的特点。但是,如果翻转之后就很好做了。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    public ListNode addInList (ListNode head1, ListNode head2) {
        ListNode h1 = reverse(head1);
        ListNode h2 = reverse(head2);
        int tag = 0;
        int val = 0;
        ListNode cur = null, pre = null;
        while (h1 != null || h2 != null) {
            if (h1 != null) {
                val += h1.val;
                h1 = h1.next;
            }
            if (h2 != null) {
                val += h2.val;
                h2 = h2.next;
            }
            val += tag;
            if (val >= 10) {
                tag = 1;
                val = val % 10;
            } else {
                tag = 0;
            }
            cur = new ListNode(val);
            val = 0;
            cur.next = pre;
            pre = cur;
        }
        if (tag == 1) {
            cur = new ListNode(1);
            cur.next = pre;
        }
        return cur;
    }
    
    public ListNode reverse (ListNode cur) {
        ListNode pre = null;
        ListNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

2.4 单链表的排序

题目链接:BM12单链表的排序
在这里插入图片描述
基于归并排序的思想来进行处理。首先进行划分,利用快慢指针找到中点,然后递归,最后讲左右的归并两个有序链表。注意的是:进行左右区间划分的时候需要将左右链表断开,即(slow.next = null)这一步非常重要。

直接上代码:

import java.util.*;

public class Solution {
    public ListNode sortInList (ListNode head) {
        if (head == null || head.next == null) return head;
        // 基于快慢指针查找中点。
        ListNode fast = head.next, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode ne = slow.next;
        slow.next = null;
        // 递归实现左右的排序
        ListNode left = sortInList (head);
        ListNode right = sortInList(ne);
        // 合并两个有序链表
        ListNode h = new ListNode(0);
        ListNode cur = h;
        while (left != null && right != null) {
            if (left.val < right.val) {
                cur.next = left;
                left = left.next;
            } else {
                cur.next = right;
                right = right.next;
            }
            cur = cur.next;
        }
        if (left != null) cur.next = left;
        if (right != null) cur.next = right;
        return h.next;
    }
}

2.5 判断一个链表是否是回文结构

BM13 判断一个链表是否为回文结构
在这里插入图片描述
这个利用的是一个小trick:判断回文结构只需要将后半部分翻转与前半部分做对比就好了,为哈嘞?因为如果整体翻转,在后半部分还是再进行了一次相同的对比。但是,队列的长度奇偶性似乎与划分结果有关。
在这里插入图片描述
基于这种思想找到的一定是中点,slow.next后半段的首字符。如果是奇数,那么slow指向的就是中央的奇数那个listNode,如果是偶数,那么slow指向的就是前半部分最后一个节点。所以用slow.next来进行处理,采用后半段的长度判空就可以实现判别了。

import java.util.*;

public class Solution {
    public boolean isPail (ListNode head) {
        // 找到中点
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 翻转
        ListNode cur = slow.next;
        ListNode pre = null;
        ListNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        // 采用后半段来判定
        while (pre != null) {
            if (head.val != pre.val) {
                return false;
            }
            head = head.next;
            pre = pre.next;
        }
        return true;
    }
}

2.6 删除链表的重复元素

BM16 删除有序链表中重复的元素-II
在这里插入图片描述

这道题就是注意方法(引入头结点)和边界条件(判空)。直接上代码

import java.util.*;
public class Solution {
    public ListNode deleteDuplicates (ListNode head) {
        if (head == null) return null;
        ListNode h = new ListNode(0);
        h.next = head;
        ListNode cur = h;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int tmp = cur.next.val;
                while (cur.next != null && cur.next.val == tmp) {
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return h.next;
    }
}

3 总结一下知识点

3.1 快慢指针

上面好几个题用到了“快慢指针”,这个小trick可以有以下作用:

(1)寻找中点

		ListNode slow = head, fast = head.next;
		while (fast != null && fast.next != null) {
		    slow = slow.next;
		    fast = fast.next.next;
		}

(2)判断链表中是否有环

        if (head == null) return false;
        // 通过快慢指针来做
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;

(3)找到环的中点
我一直觉得这个是一个很棒的trick,可以证明这是正确的,这里就不耍大刀了,随便一搜就有很多了。

import java.util.*;
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode slow = pHead, fast = pHead;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (slow == fast) {
                // 此时存在环,需要进行判断入口是哪 2 (a + b) = a + b + n (b + c)
                // a = (n - 1)(b + c) + c
                // n == 1时,c==a
                ListNode res = pHead;
                while (res != slow) {
                    res = res.next;
                    slow = slow.next;
                }
                return res;
            }
        }
        // 不存在环,返回空值
        return null;
    }
}

3.2 翻转链表

关于翻转链表,有很多解法,如递归啥的。但是我觉得下面这种是最好理解的,上文也介绍了,就看指针是咋变的。

        ListNode pre = null;
        ListNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;

注意可以有变体:不一定是要全员翻转,可以以k为长度进行翻转,此时就不是cur!= null了,如2.1所示。

3.3 合并链表

不用多说,这个很重要。

    // 合并两个链表
    public ListNode merge2Lists(ListNode list1, ListNode list2) {
        ListNode head = new ListNode(0);
        ListNode cur = head;
        while (list1 != null && list2 != null) {
            if (list1.val > list2.val) {
                cur.next = list2;
                list2 = list2.next;
            } else {
                cur.next = list1;
                list1 = list1.next;
            }
            cur = cur.next;
        }
        if (list1 != null) cur.next = list1;
        if (list2 != null) cur.next = list2;
        return head.next;
    }

3.4 归并的思想

合并k个链表和单链表的排序都用到了。就相当于,希望将O(n)降到O(logn)的复杂度,就可以考虑归并了,这种写法很精髓。

总结

需要做点有创造力的活。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值