持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情
大致刷了几个链表的算法题吧,感觉好像有说明规律,当然我刷的题少,也许不太准确。
删除链表中的节点
有一个单链表的 head,我们想删除它其中的一个节点 node。
给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。
链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
给定节点的值不应该存在于链表中。 链表中的节点数应该减少 1。 node 前面的所有值顺序相同。 node 后面的所有值顺序相同。
看到这个题目,是不是感觉有点简单,但是好像又不简单,那么我们一起分析一下:
这就是单链表的大致结构,假设我们head里面有四个数 4,5,1,9 并且要删除的node=5,那么我们期待的答案是4,1,9
已知:链表定义如下
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next;//对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
也可以加入范型
class ListNode<E>{ //类名 :Java类就是一种自定义的数据结构
E val; //数据 :节点数据
ListNode<E> next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(E val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
那么这里就很简单了,我们直接通过node定义到改位置,然后通过引用的改变,达到删除node。 即:node.val=node.next.val;node.next=node.next.next;
看到这个你是不是觉得这个是复制不是删除。确实算吧,我们只是把node下一个位置复制到node里面,然后node下一个可以被gc处理。
还有一种:删除node的下一个: 那么就是直接node.next=node.next.next;
当然,如果要求我们把结点从内存删除,还需要对要删除结点加标记,保证我们把他移除单链表后可以找到。
删除链表里面倒数第n个结点。
对上一题拓展,怎么删除倒数第n个?
需要找到倒数第n+1,然后n+1的next=n+1的next.next
确实这样可以,那么代码怎么写呢,怎么找到那个数呢??
我们可以得到链表长度,然后确定位置
private int length(ListNode head) {
int len = 0;
while (head != null) {
len++;
head = head.next;
}
return len;
这样我们就可以通过参数得到链表长度了。
那么接下来是不是直接删除??再想一想,我们是不是还需要判断是不是为null?
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = head;
int last = length(head) - n;
//如果last等于0表示删除的是头结点
if (last == 0)
return head.next;
//这里首先要找到要删除链表的前一个结点
for (int i = 0; i < last - 1; i++) {
pre = pre.next;
}
//然后让前一个结点的next指向要删除节点的next
pre.next = pre.next.next;
return head;
}
反转
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
第一反应是不是想起来String里面的反转,那么这里反转是不是一样的呢?
我们想一想数据结构方面。
对于链表进行反转,第一个考虑的是使用栈解决问题。即把链表结点一个一个入栈,当全部入栈后,再出栈,按照链表先入后出特性完成反转。
如何入栈? 循环搜索链表。把值加入
Stack<ListNode> stack = new Stack<>();
while(head!=null){
stack.push(head);
head=head.next;
}
这时候的stack里面存储了相关数据。 接下来进行出栈。 同样需要循环
ListNode node = stack.pop();
ListNode dummy = node;
while(!stack.isEmpty()){
//出栈
ListNode temp=stack.pop();
node.next=temp;
//链接成新链表
node=node.next;
}
主函数:
public ListNode reverseList(ListNode head) {
Stack<ListNode> stack = new Stack<>();
//把链表节点全部摘掉放到栈中
while (head != null) {
stack.push(head);
head = head.next;
}
if (stack.isEmpty())
return null;
ListNode node = stack.pop();
ListNode dummy = node;
//栈中的结点全部出栈,然后重新连成一个新的链表
while (!stack.isEmpty()) {
ListNode tempNode = stack.pop();
node.next = tempNode;
node = node.next;
}
//最后一个结点就是反转前的头结点,一定要让他的next
//等于空,否则会构成环
node.next = null;
return dummy;
}
第二种方法,使用头插法。循环读取原始链表数据,然后在链表头插入 可以递归: 递归模板如下
public ListNode reverseList(参数0) {
if (终止条件)
return;
逻辑处理(可能有,也可能没有,具体问题具体分析)
//递归调用
ListNode reverse = reverseList(参数1);
逻辑处理(可能有,也可能没有,具体问题具体分析)
}
这里终止条件就是链表是空
if (head == null || head.next == null) return head;