题解
双指针(快慢指针)
环形链表
思路一:快慢指针
假如两个人绕操场跑步,跑的快的人必定追得上慢的人。这就是快慢指针的思想,所以通过设置步长为2和1的两个指针,从链表都出发,不断往后移,如果存在环形链表,两指针必定会相遇。
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(slow != null && fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(fast == slow){
return true;
}
}
return false;
}
}
思路二:HashSet集合,具体见下一题
环形链表 II
思路一:借助Set集合的唯一性,往Set添加节点,若链表有循环,说明节点会重复遇到。
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while(head != null) {
if(set.contains(head)) {
return head;
}
set.add(head);
head = head.next;
}
return null;
}
}
!思路二:双指针,节省空间
由于快慢指针只是判断链表中有环,并不能得出相遇点就是交叉点。所以需要了解其中的一点规律才能做出:快慢指针相遇时,慢指针走到交叉节点的路径等于从首节点走到交叉节点的路径
具体证明见力扣
简单理解如下图:-4到2等于3到2的距离,-4才是相遇点
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
ListNode tmp = null;
while(fast != null && fast.next != null && slow != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
tmp = head;
while(tmp != null && slow != null && tmp != slow) {
tmp = tmp.next;
slow = slow.next;
}
break;
}
}
return tmp;
}
}
链表中倒数第k个节点
思路一:list集合(代码略)
用list集合存放所有节点,然后通过get(size-k)即可返回结果
代码略
思路二:快慢指针
让b指针先走k步,然后a指针和b指针同时开始往后走,待b走完时,a所到的位置就是倒数第k个节点,保持因为a和b的距离一直保持是k。
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode tmp = head;//b指针
while(k-- > 0) {//先走k步
tmp = tmp.next;
}
while(tmp != null) {//同时走
tmp = tmp.next;
head = head.next;
}
return head;//a指针
}
}
思路三:二次遍历
第一次先求长度len,第二遍历到len-k的位置即可
代码略
链表相交
思路一:HashMap
对每个节点进行计数(不是节点值,是节点的引用),若存在计数为2的,说明该节点为交点。
代码略
思路二:交换遍历
难以确定交点的原因在于两路径到达交点的长度不一致,于是我们可以通过交换路径实现长度一致。例如a路径长度是4,b路径长度是5,a遍历完就跳去遍历b,b遍历完就去遍历a,那么两者的长度就能得到互补,也就是9。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA;
ListNode b = headB;
while(a!=b) {
a = (a == null) ? headB : a.next;
b = (b == null) ? headA : b.next;
}
return a;//b也可
}
}
链表骚操作
删除链表中的节点
思路:节点删除
a->b->c->d,删除b,就是把a指向b的引用指向c即可。但是我们并不知道a指向b的引用,只知道有个引用指向b。
a指向b这种属于引用删除,还有一种删除就是替换,既然删不了b,那就用c替换b,即b.val = b.next.val。对于c后面的节点就没必要用替换了,而是只需要删除c就可以,因为我们知道b指向c的引用,所以可以删除,即b.next = b.next.next
class Solution {
public void deleteNode(ListNode node) {
if(node == null) return;
if(node.next!=null) {
node.val = node.next.val; //替换
node.next = node.next.next; //删除
}
}
}
移除链表元素
力扣链接:https://leetcode-cn.com/problems/remove-linked-list-elements/
思路:递归或迭代
class Solution {
public ListNode removeElements(ListNode head, int val) {
//递归
if(head == null) return head;//边界
//判断当前节点,相等就移除,引用往后移,只考虑此刻,后面的节点由递归搞定。
if(head.val == val) {
head= head.next;
head = removeElements(head,val);
} else {
head.next = removeElements(head.next,val);
}
return head;//返回当前符合节点的引用
//简化递归
if(head == null) return head;//边界
head.next = removeElements(head.next,val);
//返回当前符合节点的引用,要么当前,要么后一个,只考虑此刻,后面的节点由递归搞定。
return head.val == val ? head.next : head;
//迭代--可用假节点或者替换删除的方式减少代码
if(head == null) return head;
//固定头节点
while(head !=null && head.val == val) {
head = head.next;
}
//移除
ListNode tmp = head;
while(tmp != null && tmp.next != null) {
if(tmp.next.val == val) {
tmp.next = tmp.next.next;
} else {
tmp = tmp.next;
}
}
return head;
}
}
移除重复节点
思路:HashSet
根据Set集合唯一性,如果遇到重复,就进行链表元素移除即可
class Solution {
public ListNode removeDuplicateNodes(ListNode head) {
HashSet<Integer> set = new HashSet();
ListNode front = head;
ListNode current = head;
while(current != null) {
if(set.contains(current.val)) { //存在就移除
front.next = current.next;
} else {
set.add(current.val);
front = current; //记录上一个节点,用于删除
}
current = current.next; //遍历一个
}
return head;
}
}
改进:元素有范围,可使用数组代替Set
思路二:暴力双层for循环
class Solution {
public ListNode removeDuplicateNodes(ListNode head) {
ListNode first = head;
while(first != null) {
ListNode front = first;
ListNode current = first.next;
while(current != null) {
if(current.val == first.val) {
front.next = current.next;
} else {
front = current;
}
current = current.next;
}
first = first.next;
}
return head;
}
}
旋转链表
力扣门:https://leetcode.cn/problems/rotate-list/
思路:简单点说就是将后半部分与前半部分进行拼接
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null || head.next == null) return head; //特判
//记录链表的长度,用于取模
int l = 0;
//记录最后一个节点,用于拼接
ListNode last = null;
ListNode tmp = head;
while(tmp != null) {
last = tmp;
tmp = tmp.next;
l++;
}
k = k % l;
if(k == 0) return head; //特判
//获取后半部分的第一个节点,用于返回结果
ListNode prev = head;
int lk = l - k;
while(lk-- > 1) {
prev = prev.next;
}
ListNode res = prev.next; //头部
prev.next = null; //末尾断开连接,否则会变成循环链表
last.next = head; //拼接
return res;
}
}
数学
二进制链表转整数
思路:二进制与十进制的转化
101 = ((((0 x 2)+1) x 2)+0) x 2)+1=5
其实就是1 x 22 + 0 x 21 + 1 x 20 ,上式就是高中的秦九韶算法
class Solution {
public int getDecimalValue(ListNode head) {
int res = 0;
while(head != null) {
res = res * 2 + head.val;
head = head.next;
}
return res;
}
}
两数相加
思路:不难,就是加法运算,不过每次需要记录进位。其中如果选择新建节点能省去不少代码,如果把相加后的结果赋值给其中一个链表,那就会多出一些比较繁琐的判断。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode res = l1;
ListNode prev = null; //保留前一个节点
int jw = 0; //进位
while(l1 != null && l2 != null) {
prev = l1;
//直接存到l1,不新建节点
l1.val = l1.val + l2.val + jw;
if(l1.val > 9) {
l1.val = l1.val - 10;
jw = 1;
} else {
jw = 0;
}
l1 = l1.next;
l2 = l2.next;
}
//判断
if(l2 != null) {
//如果是l2不为空,也就是比l1长,需要prev来拼接l2了
prev.next = l2;
while(l2 != null) {
prev = l2;
l2.val = l2.val + jw;
if(l2.val > 9) {
l2.val = l2.val - 10;
jw = 1;
} else {
jw = 0;
break;
}
l2 = l2.next;
}
//最后一位
if(jw == 1) prev.next = new ListNode(1);
} else {
while(l1 != null) {
prev = l1;
l1.val = l1.val + jw;
if(l1.val > 9) {
l1.val = l1.val - 10;
jw = 1;
} else {
jw = 0;
break;
}
l1 = l1.next;
}
//最后一位
if(jw == 1) prev.next = new ListNode(1);
}
return res;
}
}