我写的这个系列的博客解题的算法有些不一定是时间和空间复杂度最优的解,一切以能AC和算法模板普适性为原则解题
首先是链表的增删改查,所有问题都是以链表的增删改查为核心去变种的,改和查按照条件去遍历链表即可,下面直接看leetcode原题:
1.移除链表元素(链表删除节点的模板题)
要点:
①进来一定要先记得判空,在访问next的时候也一定要注意代码是否会造成空指针异常,如果一个类比如ListNode题中的两个属性val和next,如果当前节点node为null,那么访问val和next的时候都会报空指针异常,也就是只有当node!=null时才能调用其属性
②构造一个节点指向头节点
③遍历链表不断判断root.next的值找到要删除的值,找到了就让root.next指向root后面的后面那个节点
④保存根节点的备份,最后返回
链表删除算法的时候复杂度为O(n),空间复杂度为常数
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head==null)return null;//一定要记得先判空
ListNode root = new ListNode();
ListNode r = root;
root.next = head;//新建一个新的头节点指向真正的头节点
while(root.next!=null){//遍历链表
ListNode last = root.next;
if(last.val==val){
root.next = last.next;
continue;
}
root = last;
}
return r.next;//因为第一个节点是自己造的,真正的头节点是r.next
}
}
2.合并两个有序链表 (链表的遍历和新增)
这题也是做法很多种,递归非递归都能做,下面给出比较常见也比较好懂的做法:因为用双指针分别指向两个边表头部,循环遍历两个链表,谁小root.next就指向谁,对应的指针就指向下一个节点,最后其中一条链表可能先走完,另一个条链表如果没走完的话,直接root.next指向那个链表剩余部分即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode root = new ListNode();//自己构造的头节点
ListNode r1 = l1;//指向l1的头指针
ListNode r2 = l2;//指向l2的头指针
ListNode ans = root;
//用双指针遍历两个链表,谁小root.next就指向谁
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
root.next = l1;
l1 = l1.next;
}
else{
root.next = l2;
l2 = l2.next;
}
root = root.next;
}
//l1或者l2可能还有一段没加进来
if(l1!=null)root.next = l1;
if(l2!=null)root.next = l2;
return ans.next;
}
}
86. 分隔链表:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/
分隔链表用类似的做法也能做出来
3.环形链表
第二种用Set的做法要背一下,因为当链表中遇到相同值的不同节点时,就是要通过set的方法来判断两个节点的地址是否相等,这个需求经常会碰到
(160.相交链表也是可以用类似的手段来解),传送门:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
/*第一种做法(取巧的方法):因为节点的值范围有给出来,所以设置一个较大的数作为非法值,访问过后就把节点值设成非法值,所以只要遍历到有非法值的点说明存在环*/
public class Solution {
public boolean hasCycle(ListNode head) {
while(head!=null){
if(head.val==100010){
return true;
}
head.val=100010;
head = head.next;
}
return false;
}
}
/*
第二种:直接用set集合的contains方法来判断是否有节点重复加入set,节点已存在会返回false
*/
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<ListNode>();
while (head != null) {
if (set.contains(head)) {
return true;
}
set.add(head);
head = head.next;
}
return false;
}
}
/*第三种:快慢指针,详见题解,原理就是两个速度不一样的指针如果遇到了环,循环到最后一定会相遇*/
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
4.反转链表(很重要!)
反转链表的模板要背一下,也是构造一个新的链表,每次分别操作两个节点,ne是当前要处理的节点,root时刻直线反转后链表的头节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//root表示原链表中后面的节点,pre表示前面的节点
ListNode pre = null;
ListNode root = head;
while(root!=null){
ListNode next = root.next;//提前存一下下一个节点
root.next = pre;
pre = root;
root = next;
}
return pre;
}
}
反转链表II同理,传送门:https://leetcode-cn.com/problems/reverse-linked-list-ii/