链表与递归-代码实现

目录

一、链表的使用:LeetCode例题

1、解法一:不使用虚拟头节点

2、解法二:使用虚拟头节点

二、递归

1、链表的天然递归性

2、递归运行机制


一、链表的使用:LeetCode例题

删除链表中等于给定值val的所有元素。

示例:给定 1->2->6->3->4->5->6,val = 6;返回:1->2->3->4->5

ListNode为题中给出的链表代码

public class ListNode {
    public int val;
    public ListNode next;

    public ListNode(int x){
        val = x;
    }
}

1、解法一:不使用虚拟头节点

public ListNode removeElements(ListNode head, int val) {
        // 循环遍历头节点
        while (head != null && head.val == val) {
            ListNode delNode = head;
            // 绕过删除节点,链接转移
            head = head.next;
            delNode.next = null;
        }
        if (head == null) {
            return null;
        }
        // 循环头节点以外的节点
        ListNode prev = head;
        while (prev.next != null) {
            if (prev.next.val == val) {
                prev.next = prev.next.next;
            } else {
                // 循环到下一个
                prev = prev.next;
            }
        }
        return head;
    }

2、解法二:使用虚拟头节点

使用虚拟头节点,可以统一链表的操作,简化实现逻辑。

public ListNode removeElements(ListNode head, int val) {
        // 设立虚拟头节点
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        // 设立虚拟头节点以后,就不需要再另外处理头节点了
        // 以为,从虚拟头节点开始遍历,此时每一个有效的节点都有前一个节点
        ListNode prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val) {
                ListNode delNode = prev.next;
                prev.next = delNode.next;
            } else {
                // 循环到下一个
                prev = prev.next;
            }
        }
        return dummyHead.next;
    }

为了能在本地进行测试,重新编写了一下ListNode的相关代码,自定义了ListNode构造函数,代码入下:

public class ListNode {
    public int val;
    public ListNode next;

    public ListNode(int x){
        val = x;
    }

    // 链表节点的构造函数
    // 使用arr作为参数,创建一个链表,当前的ListNode为链表头节点
    public ListNode(int[] arr){
        if(arr == null && arr.length == 0){
            throw new IllegalArgumentException("arr cannot be empty.");
        }
        // 头节点
        this.val = arr[0];
        ListNode cur = this;
        for (int i = 1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            // 不断循环赋值
            cur = cur.next;
        }
    }

    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        ListNode cur = this;
        while(cur != null){
            sb.append(cur.val + "->");
            // 循环移动
            cur = cur.next;
        }
        // 表示已经到达了链表末尾
        sb.append("NULL");
        return sb.toString();
    }
}

二、递归

递归的本质就是把原来的问题转化为更小的同一问题

理解递归:本质无非是一个函数里边调用了另一个函数,只是所调用的函数是他本身而已。

举例:数组求和

1、链表的天然递归性

如下是一个链表的基本构成图示:

我们如果用递归的视角来看它,就是一个节点链接了一个更短的链表,如下:

利用上边递归的思想,我们重新来实现上边leetcode的例题,示例代码如下:

public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 从头节点开始判断起
        if(head == null){
            return null;
        }
        // 除头节点的外,更短的链表
        ListNode res = removeElements(head.next,val);
        if(head.val == val){
            // 如果是删除元素,丢弃头节点
            return res;
        }else{
            // 如果非删除元素,头节点保留
            head.next = res;
            return head;
        }
    }
}

进一步简化为:

public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 从头节点开始判断起
        if(head == null){
            return null;
        }
        // 除头节点的外的,更短的链表
        head.next = removeElements(head.next,val);
        return head.val == val ? head.next : head;
    }
}

使用递归的核心思想是:使用基本问题的解去构建整体问题的解。图示解如下:

2、递归运行机制

递归的运行机制,还是使用基本问题的解去构建整体问题的解,下边为了更加清楚的描述递归的运行机制,使用一个数组求和的简单例子来做步骤分解:

求解:arr = [6,10] 数组元素的和,过程示例如下,n为数组的长度,此处为简写

上图示例的过程可以理解为:0 + 10 + 6;

1、遍历到数组最后一个元素(l = n = 2),没有相加的数据,所以函数执行返回0;

2、在上一个函数求解返回之后,函数在停顿后继续执行,得到上一个调用计算的值 x = 0 ; 那么 res = arr[1] + 0;即res = 10;

3、当前一个函数执行完后,得到计算的值 x = 10;函数继续执行,那么 res = arr[0] + 10;所以 res = 16;至此,整个函数执行结束。

注意:递归调用是有代价的 ——> 函数调用+系统栈空间(递归深度)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章