链表
1、移除链表元素(考虑全情况)
问题需求:根据给定的val值,移除链表中值是这个val的节点
203. 移除链表元素 - 力扣(LeetCode)
这里有一个问题就是,如果需要被移除的节点不是中间的某个节点,而是头节点。就没法借助前一个节点了,而是直接将
head = head.next
所以有两种思路,要么是分类处理;要么是在原本的head节点前面增加一个虚拟头节点,这样头节点也有个前面的节点了
还有需要注意的是,这里的代码,不应该用if,因为我们是一直移除的一个过程,所以应该用while
- 思路一:
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val) //之所以不用if,是因为可能从头结点开始有连续的等于val的节点
head = head.next;
ListNode cur = head; //删除非头节点情况
while(cur != null && cur.next != null){
if(cur.next.val = val)
cur.next = cur.next.next;
else
cur = cur.next;
}
return head;
}
- 思路二:
ListNode dummyHead = new ListNode(0); // 设置⼀个虚拟头结点
dummyHead.next = head; // 将虚拟头结点指向head,这样⽅⾯后⾯做删除操作
ListNode cur = dummyHead;
while(cur.next != null){
...
}
return dummyHead.next;
2、设计链表【个人觉得想熟悉链表时可以练这个】
- 707. 设计链表 - 力扣(LeetCode)
就是实现关于链表的各个操作,为了方便,还是用上了虚拟头节点
初始化中的0就是虚拟头节点了
3、反转链表(双指针)
206. 反转链表 - 力扣(LeetCode)
双指针法
遍历链表,按照头插法,将旧链表的一个个节点放到新链表的头节点,最后反转成功
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode first = null;
while(cur!=null){
ListNode tmp = cur.next; // 保存⼀下 cur的下⼀个节点,因为接下来要改变cur->next
cur.next = first; //头部插入
//更新双指针
first = cur;
cur = tmp;
}
return first;
}
4、两两交换链表中的节点(借助虚拟头节点)
24. 两两交换链表中的节点 - 力扣(LeetCode)
需要借助虚拟头节点,这样操作会简单一点
每次需要一个指针cur指向需要被交换的两个节点,的前一个节点
顺序就是,虚拟头节点先指向2,然后让2指向1,最后1指向3。而这里的“1”和“3”是需要两个临时节点记录一下
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
while(cur.next != null && cur.next.next != null){
ListNode tmp = cur.next;
ListNode tmp1 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = tmp;
cur.next.next.next = tmp1;
cur = tmp; // 或者cur = cur.next.next; cur移动两位,准备下⼀轮交换
}
return dummyHead.next;
}
5、删除链表倒数第N个节点(快慢指针)
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
-
由于需要删除的是倒数第N个,所以可以用快慢指针
-
双指针的经典应⽤,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了
-
这里需要注意的就是,需要虚拟头节点,考虑到了如果链表只有一个元素,或者只有两个元素的情况
这种时候,用到虚拟头节点,就很好的可以处理
另外,最后返回的时候,不要返回head,而是返回dummyhead.next,因为如果只有一个节点的情况,删除后,就没有head,就知识null了,所以不能返回head
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyhead = new ListNode(0);
dummyhead.next = head;
ListNode fast = dummyhead;
ListNode slow = dummyhead;
for(int i = 0; i < n; i++)
fast = fast.next;
fast = fast.next; // fast再提前⾛⼀步,因为需要让slow指向删除节点的上⼀个节点
while(fast != null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummyhead.next; //不能返回head,只能是dummyhead.next
}
6、链表相交:寻找两个链表(无环结构)重合结点
面试题 02.07. 链表相交 - 力扣(LeetCode)
-
其实这个链表问题主要需要总结的就是那些经验及方法,而对于两个无环结构的链表,就是常规方法
先是求出连两个链表的长度length1,length2,然后就是需要判断这两个链表的尾结点是不是一样的
如果连尾结点都不一样,就说明两个链表连一个结点都没有重合,所以两个链表绝对不会重合
如果尾结点一样的话,需要将两个链表的长度互减
然后这个数num的绝对值就是这两个链表长度的差值,**让长链表提前走num步,然后两个链表再同时走,**直到遇见了相同的交点处停下来返回此结点,具体代码如下:
public static Node noLoop(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } Node cur1 = head1; Node cur2 = head2; int n = 0; //这里为了简化以及节省空间,就设置了一个变量n,通过下面的两个while循环,一个自加,一个自减,最后求他的绝对值,就是两个链表的长度差值 while (cur1.next != null) { n++; cur1 = cur1.next;} while (cur2.next != null) { n--; cur2 = cur2.next;} if (cur1 != cur2) { //如果最后一个结点都不一样,那么两个链表绝对不可能有交合的地方了 return null; } cur1 = n > 0 ? head1 : head2; //谁长,谁的头变成cur1 cur2 = cur1 == head1 ? head2 : head1; //谁短,谁的头变成cur2 n = Math.abs(n); while (n != 0) { //先让长链表走了n步 n--; cur1 = cur1.next; } while (cur1 != cur2) { //开始寻找相交结点 cur1 = cur1.next; cur2 = cur2.next; } return cur1; }
7、环形链表
142. 环形链表 II - 力扣(LeetCode)
划分为两个问题:
- 判断有环无环
- 如果有环的话,找到环的起始节点
1)判断有环无环
判断链表是否有环,可以借助哈希表set,也可以用快慢指针,
**1)哈希表set:**将所有节点放进set中,每放进去一个节点都查查这个节点是不是在集合中,第一次查到这个节点在集合中的时候,就说明这个节点就是环的起点
2)快慢指针: 只要最后不会指向空【如果快指针指向空了,就说明没有环】,并且快慢指针重合,就说明该链表又环。判断有环之后,就要开始找环的起始点,看下面的描述
//哈希表set
public static boolean hasCycle(Node head){
HashSet<Node> set = new HashSet<Node>();
set.add(head);
Node cur = head.next;
while(true){
if(set.contains(cur))
return true;
else{
set.add(cur);
cur=cur.next;
if(cur==null)
return false;
}
}
}
//快慢指针
public static boolean ifCycle(Node head){
Node slow = head;
Node fast = head.next;
while(fast != slow){
if(fast == null)
return false;
slow = slow.next;
fast = fast.next.next;
}
return true;
}
2)找到环的起始节点
这里一开始还是借助快慢指针,先是判断有无环结构:
-
如果有环的话,也就是说快慢指针在环上的某一个结点上重合了。(这是第一个while循环)
-
此后就到了本方法的骚气处了【这是通过公式推导出来的规律!!记住这个结论!!!】
- 将快指针转移到头结点的位置,慢节点还是在原来环上面两个指针相遇的位置
- 快慢指针的的移动步长都变为一
- 此时他们再相遇重合的那个位置就是环结点的起始处了(这是第二个while循环),直接上代码:
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast)
break;
}
if(fast == null || fast.next == null) //跳出上面循环时有两种可能,一是没有环,另外就是slow == fast。所以需要判断如果 //无环的话,直接返回null
return null;
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
8、判断回文结构
借助栈
public static boolean isPalindrome1(Node head) {
Stack<Node> s = new Stack<Node>();
Node cur = head;
while(cur != null){
s.push(cur);
cur = cur.next;
}
cur = head;
while(head != null){
if(head.val != s.pop().val)
return false;
head = head.next;
}
return true;
}
本文是基于学习代码随想录,以及自刷leetcode总结链表方面的笔记,不仅仅限于代码随想录笔记,是一些自己的思考和整理。并且是Java版本