链表的一些算法题

68 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

大致刷了几个链表的算法题吧,感觉好像有说明规律,当然我刷的题少,也许不太准确。

删除链表中的节点

有一个单链表的 head,我们想删除它其中的一个节点 node。

给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。

链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

给定节点的值不应该存在于链表中。 链表中的节点数应该减少 1。 node 前面的所有值顺序相同。 node 后面的所有值顺序相同。

看到这个题目,是不是感觉有点简单,但是好像又不简单,那么我们一起分析一下:

image.png 这就是单链表的大致结构,假设我们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里面的反转,那么这里反转是不是一样的呢?

我们想一想数据结构方面。

对于链表进行反转,第一个考虑的是使用栈解决问题。即把链表结点一个一个入栈,当全部入栈后,再出栈,按照链表先入后出特性完成反转。 image.png

如何入栈? 循环搜索链表。把值加入

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;

image.png