判断链表是否存在环
链表是否有环,可以用双指针去实现,慢指针步长为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;
}