前言:
紧承上一篇 链表基础题目分享 的博客,本篇博客题目多来自于牛客 TOP101 中链表相关,难度较上一篇稍难半分。博主希望通过言简意赅的思路和代码,为大家提供不同的解题灵感,受限于精力原因只有少部分题目包含了详细图解,很抱歉,后续可能会进行补全。期盼能大家能在下面的链表题解中有所启发思考,共勉。
目录:
链表内指定区间反转
链表中的节点每k个一组翻转
合并两个排序的链表
合并k个已排序的链表
判断链表中是否有环
链表中环的入口结点
两链表的公共节点
链表相加(二)
单链表的排序
链表奇数偶数重排
链表内指定区间反转
1)思路:
(1)区间头插:
添加表头节点newHead,结果返回 newHead.next
初始化pre,cur,pre cur再向前, m - 1 步到达反转区间
将 区间内元素进行头插
(2) 递归尾插:
图示
reverseBetween 递归, 找到反转区间
reverse 递归, 进行 “尾插” 反转
2)解法:
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
//最后返回 表头节点的下一个节点即可,因为 head 位置可能会发生改变
ListNode newHead = new ListNode(-1);
newHead.next = head;
//通过pre 节点可以找到上一次移向 m 位置的节点
ListNode pre = newHead;
ListNode cur = head;
//到达 m 节点
for(int i = 1; i < m; i++){
pre = cur;
cur = cur.next;
}
//将区间内元素进行头插
//通过改变 cur.next 达到cur向前移动的目的,且保持 cur 内容不变
for(int i = m; i < n; i++){
//cur 向前移动
ListNode curNext = cur.next;
cur.next = curNext.next;
//curNext 进行头插
curNext.next = pre.next;
pre.next = curNext;
}
return newHead.next;
}
public ListNode reverseBetween (ListNode head, int m, int n) {
//m == 1到达反转区间,进行反转, 反转完成直接返回
if(m == 1){
return reverse(head, n);
}
//进入子问题,保存反转过后子链表的头结点
ListNode node = reverseBetween(head.next, m - 1, n - 1);
//将本级节点和反转后子链表进行拼接
head.next = node;
//返回拼接后的新链表的头结点
return head;
}
//n节点的 next 节点
public ListNode finalNext = null;
public ListNode reverse(ListNode head, int n){
//到达最后一个节点直接返回, 同时保存 n 节点下一个节点
if(n == 1){
finalNext = head.next;
return head;
}
//进入反转的子问题, 保存的 node 是未经过反转的 n 节点, 该节点也是区间所有节点反转完成后的 区间头结点
ListNode node = reverse(head.next, n - 1);
//进行 "尾插" 反转, 因为本级节点 head 总是子区间反转过后 n 节点的上一个节点
head.next.next = head;
//head 指向 finalNext 节点, 成为新的 n 节点
head.next = finalNext;
//返回 区间头结点
return node;
}
链表中的节点每k个一组翻转
1 ) 思路:
(1)求组数反转:
(2)递归:
2)解法:
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
ListNode newHead = new ListNode(-1);
newHead.next = head;
//计算链表长度 算出反转次数
int count = 0;
ListNode cur = head;
while(cur != null){
cur = cur.next;
count++;
}
int num = count / k;
//遍历链表进行反转
ListNode pre = newHead;
cur = head;
for(int i = 0; i < num; i++){
pre = reverse(cur, pre, k);
cur = pre.next;
}
return newHead.next;
}
public ListNode reverse(ListNode cur, ListNode pre, int k){
ListNode curNext = null;
for(int i = 1; i < k; i++){
//cur 向前
curNext = cur.next;
cur.next = curNext.next;
//头插
curNext.next = pre.next;
pre.next = curNext;
}
pre = cur;
return pre;
}
public ListNode reverseKGroup (ListNode head, int k) {
//链表反转的最后节点的下一个节点
ListNode tail = head;
//中止条件, head 为空 或者 反转不足 k 个
for(int i = 0; i < k; i++){
if(tail == null){
return head;
}
tail = tail.next;
}
ListNode cur = head;
ListNode pre = null;
//进行反转
while(cur != tail){
ListNode curNext = cur.next;
cur.next = pre;
pre = cur;
cur = curNext;
}
//头节点反转过后变为尾节点 指向下一个子问题求得反转链表的头结点
head.next = reverseKGroup(tail, k);
//pre 节点变为反转过后的新链表头结点, 作为子问题的结果返回
return pre;
}
合并两个排序的链表
1 ) 思路
(1)迭代
(2)递归
2 ) 解法
public ListNode Merge(ListNode list1,ListNode list2) {
//迭代
if(list1 == null) {
return list2;
}
if(list2 == null){
return list1;
}
ListNode newHead = new ListNode(0);
ListNode cur = newHead;
while(list1 != null && list2 != null){
if(list1.val < list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(list1 != null){
cur.next = list1;
}else{
cur.next = list2;
}
return newHead.next;
}
public ListNode Merge(ListNode list1,ListNode list2) {
//中止条件 list1 / list2 == null
if(list1 == null) return list2;
if(list2 == null) return list1;
//找到 当前 list1 和 list2 中最小的元素节点, 递归缩小区间,找到当前最小节点的下一个节点
//子问题完成后返回新链表的头节点
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
合并k个已排序的链表
1 ) 思路:
(1)优先级队列
(2) 归并排序
2 ) 解法:
public ListNode mergeKLists(ArrayList<ListNode> lists) {
//1 创建堆小堆保存每个头结点
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(new Comparator<ListNode>(){
@Override
public int compare(ListNode e1, ListNode e2){
return e1.val - e2.val;
}
});
for(ListNode node : lists){
if(node != null) minHeap.offer(node);
}
//2 出队, 放入新链表, 判空入队
// 创建虚拟头结点
ListNode newHead = new ListNode(-1);
ListNode cur = newHead;
while(!minHeap.isEmpty()){
ListNode tmp = minHeap.poll();
ListNode tmpNext = tmp.next;
// 放入新链表
cur.next = tmp;
cur = cur.next;
// 非空后继入队
if(tmpNext != null){
minHeap.offer(tmpNext);
}
}
return newHead.next;
}
public ListNode mergeKLists(ArrayList<ListNode> lists) {
//进行归并排序
return mergeSort(lists, 0, lists.size() - 1);
}
//排序
public ListNode mergeSort(ArrayList<ListNode> lists, int left, int right){
//中止条件为: 只有一个节点或者没有节点了
if(left > right) return null;
if(left == right) return lists.get(left);
//本级目标: 将 mid 分开的左右两区间进行子问题排序, 合并成两张表, 再将两新表进行合并
// 通过 mid 进行区间划分
int mid = (left + right) / 2;
//对下面代码进行优化
//return merge(mergeSort(lists, left, mid), mergeSort(lists, mid + 1, right));
ListNode leftNode = mergeSort(lists, left, mid);
ListNode rightNode = mergeSort(lists, mid + 1, right);
//合并子问题返回的两链表的头结点
ListNode newNode = merge(leftNode, rightNode);
//返回值: 新合并的链表头结点
return newNode;
}
//递归合并
public ListNode merge(ListNode list1, ListNode list2){
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val <= list2.val){
list1.next = merge(list1.next, list2);
return list1;
}else{
list2.next = merge(list1, list2.next);
return list2;
}
}
判断链表中是否有环
1)思路:
2)解法:
public boolean hasCycle(ListNode head) {
//解法1: 快慢指针
//1 判空
if(head == null || head.next == null) return false;
//2 快慢指针
ListNode fast = head;
ListNode slow = head;
//3 向后移动
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
链表中环的入口结点
1)思路:
(1)双指针
(2)哈希
2)解法:
public ListNode EntryNodeOfLoop(ListNode pHead) {
//1 快慢指针找到相遇点
ListNode tmp = hasCycle(pHead);
if( tmp == null) return null;
ListNode fast = tmp;
ListNode slow = pHead;
//2 慢指针从头开始走, 再次相遇为入口点
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
public ListNode hasCycle(ListNode pHead){
ListNode fast = pHead;
ListNode slow = pHead;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow) return slow;
}
return null;
}
public boolean hasCycle(ListNode head) {
HashSet<ListNode> set = new HashSet<>();
while(head != null){
//有重复节点直接返回
if(set.contains(head)){
return true;
}
set.add(head);
head = head.next;
}
return false;
}
两个链表的第一个公共结点
1)思路:
2)解法:
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//1 求两链表长度差
ListNode cur1 = pHead1;
ListNode cur2 = pHead2;
int maxLen = 0;
int minLen = 0;
while(cur1 != null){
maxLen++;
cur1 = cur1.next;
}
while(cur2 != null){
minLen++;
cur2 = cur2.next;
}
//2 长链表先走差值步
cur1 = pHead1;
cur2 = pHead2;
if(maxLen < minLen){
cur1 = pHead2;
cur2 = pHead1;
}
int sub = Math.abs(maxLen - minLen);
while(sub > 0){
cur1 = cur1.next;
sub--;
}
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode cur1 = pHead1;
ListNode cur2 = pHead2;
while(cur1 != cur2){
cur1 = cur1 == null ? pHead2 : cur1.next;
cur2 = cur2 == null ? pHead1 : cur2.next;
}
return cur1;
}
链表相加(二)
1)思路:
(1)反转链表法:
图示
2)解法:
public ListNode addInList (ListNode head1, ListNode head2) {
ListNode cur1 = reverse(head1);
ListNode cur2 = reverse(head2);
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
//用于保存每个节点值 和 进位值
int tmp = 0;
while(cur1 != null || cur2 != null){
int val1 = cur1 == null ? 0 : cur1.val;
int val2 = cur2 == null ? 0 : cur2.val;
//计算值,保存节点
tmp = val1 + val2 + tmp;
ListNode node = new ListNode(tmp % 10);
cur.next = node;
cur = cur.next;
//保存进位
tmp /= 10;
cur1 = cur1 == null ? null : cur1.next;
cur2 = cur2 == null ? null : cur2.next;
}
//判断进位是否为空
if(tmp != 0){
cur.next = new ListNode(tmp);
}
//最后再次反转
return reverse(dummy.next);
}
//反转链表
public ListNode reverse(ListNode root){
ListNode pre = null;
ListNode rootNext = null;
while(root != null){
rootNext = root.next;
root.next = pre;
pre = root;
root = rootNext;
}
return pre;
}
单链表的排序
1)思路:
(1)归并排序:
排序方法传入的 tail 参数为 null, 该节点会在 递的过程中被省略
(2)数组排序:
2)解法:
public ListNode sortInList (ListNode head) {
// write code here
//尾节点传入 null, 因为进行双指针二分过程中. 最后一个节点必定会被省略
return sort(head, null);
}
public ListNode sort(ListNode head, ListNode tail){
//中止条件 没有元素 / 只有两个元素
if(head == null)return head;
//只有两个元素是会舍弃后一个元素, 但是在相邻子问题头结点就是这个被舍弃的节点
//如此迭代, 原链表的最后一个 tail 节点会舍弃, 但是 tail 节点又为空所以没有影响
if(head.next == tail){
//如果要进行链表合并 子链表势必是经过调整后的新链表,
//如果next 带有原本旧链表的节点, 合并过程就无法顺利进行, 所以将 next置为空
head.next = null;
return head;
}
//本级目标: 拼接两个子问题返回的排序过的新链表(逻辑上不被旧链表影响)
// 快慢指针进行二分, 快指针到最后一个节点为止 slow 即是mid
ListNode fast = head;
ListNode mid = head;
while(fast != tail){
fast = fast.next;
mid = mid.next;
if(fast != tail){
fast = fast.next;
}
}
//返回值: 返回拼接后链表的头结点
return merge(sort(head, mid), sort(mid, tail));
// ListNode left = sort(head, mid);
// ListNode right = sort(mid, tail);
// ListNode node = merge(left, right);
// return node;
}
public ListNode merge(ListNode list1, ListNode list2){
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val <= list2.val){
list1.next = merge(list1.next, list2);
return list1;
}else{
list2.next = merge(list1, list2.next);
return list2;
}
}
public ListNode sortInList (ListNode head) {
ArrayList<Integer> array = new ArrayList<>();
ListNode cur = head;
while(cur != null){
array.add(cur.val);
cur = cur.next;
}
Collections.sort(array);
cur = head;
for(int i = 0; i < array.size(); i++){
cur.val = array.get(i);
cur = cur.next;
}
return head;
}
链表的奇偶重排
1)思路:
(1)双指针法
图示
2)解法:
public ListNode oddEvenList (ListNode head) {
// write code
//1 判空 初始化 odd even evenHead
if(head == null) return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
//2 进行奇数偶数链表的分别连接
while(even != null && even.next != null){
//奇数再偶数前面, 先进行奇数连接
odd.next = even.next;
odd = odd.next;
//再进行偶数连接
even.next = odd.next;
even = even.next;
}
//3 奇数偶数链表进行拼接 返回 head
odd.next = evenHead;
return head;
}
文章至此就结束啦,如果看官朋友觉得还不错,博主求 点赞、评论、收藏 三连,十分感谢
奇数总在偶数之前
2)解法:
public ListNode oddEvenList (ListNode head) {
// write code
//1 判空 初始化 odd even evenHead
if(head == null) return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
//2 进行奇数偶数链表的分别连接
while(even != null && even.next != null){
//奇数再偶数前面, 先进行奇数连接
odd.next = even.next;
odd = odd.next;
//再进行偶数连接
even.next = odd.next;
even = even.next;
}
//3 奇数偶数链表进行拼接 返回 head
odd.next = evenHead;
return head;
}
文章至此就结束啦,如果看官朋友觉得还不错,博主求 点赞、评论、收藏 三连,十分感谢