链表相关问题

本文详细介绍了链表的各种操作,包括判断链表是否存在环、寻找环的入口点、找到两个链表的第一个公共节点、单链表的反转和分组反转、输出链表倒数第k个节点、删除倒数第k个节点、合并已排序链表、链表相加、链表排序、判断回文、奇偶重排、删除重复元素以及根据x划分链表等。并提供了不同的解决方案,如双指针、栈、队列等数据结构的应用。
摘要由CSDN通过智能技术生成

判断链表是否存在环

链表是否有环,可以用双指针去实现,慢指针步长为1,快指针步长为2,如果快指针和慢指针相遇,那么肯定存在环。

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

寻找链表环中的入口点

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        // 先找到第一个相遇点
        ListNode slow = hasCycle(pHead);
        if(slow == null) return null;
        // 有环, 从头开始遍历,和从第一次相遇点遍历,再次相遇就是入口点
        ListNode p = pHead;
        while(p!= slow) {
            p = p.next;
            slow = slow.next;
        }
        return slow;
    }
    private ListNode hasCycle(ListNode head) {
        if(head == null ) return null;
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) return slow;
        }
        return null;
    }
}

两个链表的第一个公共节点

题目信息:
两个无环的单链表,找出它们的第一个公共节点并返回,如果无则返回null。
此处的公共节点是指 节点是同一个对象,而不是节点的数字相等。

  • 常规思路: 这里很简单,直接双重循环就行了,就行数组比较一样
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) return null;
        while(pHead1 != null) {
            ListNode curP2 = pHead2;
            while(curP2 != null) {
                if(curP2 == pHead1) return curP2;
                curP2 = curP2.next;
            }
            pHead1 = pHead1.next;
        }
        return null;
    }
}
  • 高级方法
    不管两链表是否有公共点,如果我们让两个指针分别从走遍历两条链表。 l1 先遍历链表1,到尾部后遍历链表2; l2 先遍历链表2, 到尾部后再遍历链表1.
    如果有公共节点,则由于公共节点之后的点都相同,那么第二次遍历时肯定会在公共节点处相遇;
    如果没有公共节点,则一同到达尾部,此时二者都为null;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode l1 = pHead1;
        ListNode l2 = pHead2;
        while(l1 != l2) {
            l1 = l1 == null? pHead2: l1.next;
            l2 = l2 == null? pHead1: l2.next;
        }
        return l1;
    }
}

单链表的反转

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

单链表每k个一组反转

地址

利用双向队列实现

由于有些节点可能不需要反转,涉及到正向和反向,所以直接使用双向队列实现。往队列添加内容,反转时从队尾取pollLast;不反转时正常取poll.

import java.util.*;

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

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        if (head == null || head.next == null || k == 1) return head;

        ArrayDeque<ListNode> dq = new ArrayDeque<ListNode>();
        ListNode newHead = new ListNode(-1);
        ListNode newCur = newHead;
        while (head != null) {
            dq.add(head);
            head = head.next;
            if (dq.size() == k) {
                while (!dq.isEmpty()) {
                    ListNode t = dq.pollLast();
                    newCur.next = t;
                    newCur = newCur.next;
                }
            }
        }

        while (!dq.isEmpty()) {
            ListNode t = dq.poll();
            newCur.next = t;
            newCur = newCur.next;
        }
        newCur.next = null;
        return newHead.next;
    }
}
利用栈实现

反转问题,可以使用栈的特性。 往栈push 节点,直到s的size 达到k;达到k时,从栈中pop 节点出来,不断链接成新链。
最终如果栈不为空,由于不需要反转,所以需要再用一个栈来还原。

import java.util.*;

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

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        if (head == null || head.next == null || k == 1) return head;

        Stack<ListNode> s = new Stack<ListNode>();
        ListNode newHead = new ListNode(-1);
        ListNode newCur = newHead;
        while (head != null) {
            s.push(head);
            head = head.next;
            if (s.size() == k) {
                while (!s.isEmpty()) {
                    ListNode t = s.pop();
                    newCur.next = t;
                    newCur = newCur.next;
                }
            }
        }
        Stack<ListNode> s2 = new Stack<ListNode>();
        while(!s.isEmpty()) s2.push(s.pop());
        while (!s2.isEmpty()) {
            ListNode t = s2.pop();
            newCur.next = t;
            newCur = newCur.next;
        }
        newCur.next = null;
        return newHead.next;
    }
}

输出链表倒数第k个及之后的节点

如果k大于链表长度,输出null
思路:快慢指针,先让快指针走k步,然后再一起走,快指针到达尾节点时,慢指针正好是n-k位置处。

import java.util.*;
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int n = 0;
        ListNode fast = pHead;
        ListNode slow = pHead;
        //快指针先行k步
        for (int i = 0; i < k; i++) {
            if (fast != null)
                fast = fast.next;
            //达不到k步说明链表过短,没有倒数k
            else
                return slow = null;
        }
        //快慢指针同步,快指针先到底,慢指针指向倒数第k个
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

删除倒数第k个节点

对于单链表,我们没法逆向遍历,但是我们可以用双指针来找到倒数第k个位置;然后把这个位置删了就行。
由于可能删掉顶节点,所以我们需要一个虚拟节点来指向head;最终返回虚拟节点的下一个节点就行。

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param n int整型
     * @return ListNode类
     */
    public ListNode removeNthFromEnd (ListNode head, int n) {
        // write code here
        ListNode res = new ListNode(-1);
        res.next = head;
        ListNode pre = res;
        ListNode cur = head;
        
        //先找到倒数第n个位置,然后删除就行了
        ListNode fast = head;
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        while (fast != null) {
            fast = fast.next;
            pre = cur;
            cur = cur.next;
        }
        // slow 为倒数第k-1个节点
        pre.next = cur.next;
        return res.next;
    }
}

合并多个已排序链表,使得新链表整体有序

其实这种多个线性表(数组或者链表) 合并的这种,完全可以使用优先级队列来辅助实现。
PriorityQueue 可以传入比较器,来确定升序或者降序。

new PriorityQueue<>((v1, v2)-> v1.val - v2.val); 升序,内部为小根堆。
除此之外,在处理算法题时,常用的数据结构有Stack, ArrayDeque(双向队列),LinkedQueue,HashMap, HashSet, LinkedList
带排序功能的有: TreeMap,PriorityQueue

LinkedList 可以当作栈使用: addLast(i), removeLast();
可以当队列使用(它实现了Queue接口):offer(i), poll()。
ArrayDeque也可以当栈或者队列使用。
add 和addLast是一回事; poll 和pollFirst是一回事。

先将各个子链表头放入 优先级队列,队首即为最小值。
然后每次取出队首元素后,都需要将其next 插入队列,更新队列。

public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        // 小顶堆,自动升序排列
        Queue<ListNode> pq = new PriorityQueue<>((v1, v2)-> v1.val - v2.val);
        for (int i = 0; i < lists.size(); i++) {
            if (lists.get(i) != null)
                pq.add(lists.get(i));
        }
        ListNode res = new ListNode(-1);
        ListNode head = res;
        while (!pq.isEmpty()) {
            ListNode temp = pq.poll();
            head.next = temp;
            head = head.next;
            if (temp.next != null) {
                pq.add(temp.next);
            }
        }
        return res.next;
    }
}

链表相加

在这里插入图片描述

  • 分析
    其实这道题中规中矩,只需要按照加法运算,从低位开始相加,有进位则记录进位信息。
    链表不方便从低位开始处理,我们便用栈来实现;如果某个链表位数不够,那么用0 来替代。
    最终的结果也需要从高到低排列,所以我们还是使用一个栈来存结果,最终再将结果串成链表就行了。
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) {
        // write code here
        Stack<ListNode> s1 = new Stack<>();
        Stack<ListNode> s2 = new Stack<>();
        while (head1 != null) {
            s1.push(head1);
            head1 = head1.next;
        }
        while (head2 != null) {
            s2.push(head2);
            head2 = head2.next;
        }

        Stack<ListNode> res = new Stack<>();
        int flag = 0;// 进位信息
        while (!s1.empty() || !s2.empty()) {
            ListNode node1 = s1.isEmpty() ? new ListNode(0) : s1.pop();
            ListNode node2 = s2.isEmpty() ? new ListNode(0) : s2.pop();
            int sum = node1.val + node2.val + flag;
            flag = sum >= 10 ? 1 : 0;
            sum = sum % 10;
            res.push(new ListNode(sum));
        }
        // 如果还有进位信息,需要再补一个节点
        if (flag == 1) res.push(new ListNode(1));

        // 将结果串成链表
        ListNode head = new ListNode(-1);
        ListNode p = head;
        while (!res.isEmpty()) {
            p.next = res.pop();
            p = p.next;
        }
        return head.next;
    }
}

单链表排序

  • 直接使用java 的优先级队列
public class Solution {
    /**
     * 
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    public ListNode sortInList (ListNode head) {
        // write code here
        Queue<ListNode> pq = new PriorityQueue<>((v1, v2) -> v1.val - v2.val);
        while(head != null) {
            pq.add(head);
            head = head.next;
        }

        ListNode res = new ListNode(-1);
        ListNode node = res;
        while(!pq.isEmpty()) {
            node.next = pq.poll();
            node = node.next;
        }
        node.next = null;
        return res.next;
    }
}
  • 使用数组来间接实现
    使用数组将数据拿出来,然后使用Collections.sort进行排序;然后遍历链表,依次赋值就行。
public ListNode sortInList (ListNode head) {
        // write code here
        List<Integer> list = new ArrayList<>();
        ListNode p = head;
        while (p != null)  {
            list.add(p.val);
            p = p.next;
        }
        p = head;
        Collections.sort(list);
        for (int i = 0; i < list.size(); i++) {
            p.val = list.get(i);
            p = p.next;
        }
        return head;
    }
  • 也可以使用冒泡排序等
    只不过时间复杂度较大。
public ListNode sortInList (ListNode head) {
        // write code here
        for (ListNode cur = head; cur != null; cur = cur.next) {
            for (ListNode next = cur.next; next != null; next = next.next) {
                if (cur.val > next.val) {
                    int t = cur.val;
                    cur.val = next.val;
                    next.val = t;
                }
            }
        }
        return head;
    }

判断链表是否是回文

所谓回文就是从前往后输出和从后往前输出结果都是一样的。
如12321 就是一个回文。

  • 直接使用一个栈,先将链表节点值入栈。 然后再次遍历链表,和栈顶元素对比就实现了首位对比了。
public boolean isPail (ListNode head) {
        // write code here
        Stack<Integer> s = new Stack<>();
        ListNode p = head;
        while (p != null) {
            s.push(p.val);
            p = p.next;
        }
        p = head;
        while (p != null) {
            if (p.val != s.pop()) return false;
            p = p.next;
        }
        return true;
    }

链表的奇偶重排

将链表奇数位放在一起,偶数位放在一起。
例如: {1,2,3,4,5,6}, 输出: {1,3,5,2,4,6}

  • 分析
    直接用两个list将奇数位置的数 和偶数位的数存下来,然后再遍历链表赋值。
 public ListNode oddEvenList (ListNode head) {
        // write code here
        List<Integer> odd = new ArrayList<>(); // 奇数
        List<Integer> even = new ArrayList<>(); // 偶数
        ListNode p = head;
        int i = 1;
        while(p != null) {
            if(i %2 != 0) {
                odd.add(p.val);
            } else {
                even.add(p.val);
            }
            p = p.next;
            i++;
        }
        p = head;

        for(i = 0;i < odd.size();i++) {
            p.val = odd.get(i);
            p = p.next;
        }
        for(i = 0;i < even.size();i++) {
            p.val = even.get(i);
            p = p.next;
        }
        return head;
    }

删除链表中的重复元素, 只保留一个

怎么知道链表中有重复元素呢?
最简单的方法就是用set将元素值存在,遍历时检查,如果没有set中包含此节点值,则删除;否则将节点值存入set。

  • 真正删除节点
public ListNode deleteDuplicates (ListNode head) {
        // write code here
        HashSet<Integer> set = new HashSet<>();
        ListNode p = head;
        ListNode pre = p;
        while(p != null) {
            int val = p.val;
            if(set.contains(val)) {
                pre.next = p.next;
                p = p.next;
                continue;
            } else {
                set.add(val);
            }
            pre = p;
            p = p.next;
        }
        return head;
    }

删除有序链表中的重复元素,重复的都不保留

在这里插入图片描述

public ListNode deleteDuplicates (ListNode head) {
        // write code here
        // 一般这种可能把首节点删掉的,都最好添加一个虚拟节点。
        ListNode res = new ListNode(-1);
        res.next = head;
        ListNode p = res;
        while (p.next != null && p.next.next != null) {
            if (p.next.val == p.next.next.val) {
                int val = p.next.val;
                while (p.next != null && p.next.val == val)
                    p.next = p.next.next;
            } else {
                p = p.next;
            }
        }
        return res.next;
    }

给定一个链表和一个数x,编写代码使得链表左边小于x,右边大于x

使用了两个指针(left和right)来遍历链表,并将小于x的节点放在left链表上,大于等于x的节点放在right链表上;最后将left 的next接上right的head就行了。

下面的代码是chatgpt写的

public ListNode partition(ListNode head, int x) {
    ListNode leftDummy = new ListNode(0);
    ListNode rightDummy = new ListNode(0);
    ListNode left = leftDummy, right = rightDummy;
    
    while (head != null) {
        if (head.val < x) {
            left.next = head;
            left = left.next;
        } else {
            right.next = head;
            right = right.next;
        }
        head = head.next;
    }
    
    right.next = null;
    left.next = rightDummy.next;
    return leftDummy.next;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值