玩转数据结构(07)--链表和递归

这篇博客探讨了链表和递归的关系,通过LeetCode 203题(Remove Linked List Elements)的解题过程,详细阐述了链表的天然递归性质。博客解释了递归的定义,通过数组求和的例子展示了递归解决问题的思路,并分析了递归运行的微观机制。同时,提供了不同解决方案的代码示例,包括如何在链表中删除元素。此外,还提到了双链表、循环链表和数组链表等扩展话题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表和递归

题目:Leetcode 203. Remove Linked List Elements

示例代码:Solution.java【不使用虚拟头结点】

class Solution {

    public ListNode removeElements(ListNode head, int val) {	

        while(head != null && head.val == val){//传入的链表不能为空【如果传入的 val 在头部,则删除头结点】
		//【如果要删除的头结点与给定的头结点相等的话,则第二个节点仍与给定的节点相同,故使用 while 】
            ListNode delNode = head;	//待删除节点就是head节点
            head = head.next;		//绕过要删除的节点
            delNode.next = null;	//删除节点
        }

        if(head == null)
            return head;

        ListNode prev = head;	//【如果传入的 val 在中间,则要找到待删除元素的前一个节点】
        while(prev.next != null){	//还没有遍历到最后一个节点,每次查看下一个节点是否需要被删除
		//【if 中删除结束后,prev 不要向后诺,因为下一个元素任然可能是要删除的元素,故加while】
            if(prev.next.val == val) {	//如果 val 和传入的 val 相同,则需要删除
                ListNode delNode = prev.next;	//prev 的下一个节点就是要删除的节点
                prev.next = delNode.next;
                delNode.next = null;
            }
            else
                prev = prev.next;	//不用被删除,则 prev 向后挪一下
        }

        return head;
    }
}

简化代码:Solution2.java

class Solution2 {

    public ListNode removeElements(ListNode head, int val) {

        while(head != null && head.val == val)
            head = head.next;

        if(head == null)
            return head;

        ListNode prev = head;
        while(prev.next != null){
            if(prev.next.val == val)
                prev.next = prev.next.next;
            else
                prev = prev.next;
        }

        return head;
    }
}

示例代码:Solution3.java【使用虚拟头结点】

class Solution3 {

    public ListNode removeElements(ListNode head, int val) {

        ListNode dummyHead = new ListNode(-1);//dummyHead  是虚拟头结点,永远不会访问到,所以随意设置为 -1
        dummyHead.next = head;	//dummyHead 成为第一个节点的前一个节点

        ListNode prev = dummyHead;	//从头查看下一个元素是否是待删除元素
        while(prev.next != null){
            if(prev.next.val == val)
                prev.next = prev.next.next;
            else
                prev = prev.next;
        }

        return dummyHead.next;	//要返回虚拟头结点的下一个
    }
}

输出:

2.代码在本地调试

示例代码:Solution3.java

/// Leetcode 203. Remove Linked List Elements
/// https://leetcode.com/problems/remove-linked-list-elements/description/

class Solution3 {

    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)
                prev.next = prev.next.next;
            else
                prev = prev.next;
        }

        return dummyHead.next;
    }

    public static void main(String[] args) {    //(新增代码)

        int[] nums = {1, 2, 6, 3, 4, 5, 6};	//声明数组
        ListNode head = new ListNode(nums);	//构造函数中传入数组nums
        System.out.println(head);	//打印以 head 作为头结点的整个链表对应的字符串 

        ListNode res = (new Solution3()).removeElements(head, 6);//将 head 和待删除节点6作为参数传入
        System.out.println(res);
    }
}

输出:

3.链表与递归

递归定义:本质上,将原来的问题,转换为更小的同一问题

例:数组求和--

Sum(arr[0...n-1]) = arr[0] + Sum(arr[1...n-1])[更小的同一问题]

Sum(arr[1...n-1]) = arr[1] + Sum(arr[2...n-1])[更小的同一问题]

. . .

 Sum(arr[n-1...n-1]) = arr[n-1] + Sum([])[最基本的问题]---(只要 Sum([]) 值解决,根据同一逻辑可解决整个问题)

代码实现:

public class Sum {

    public static int sum(int[] arr){	// 这个sum是由用户来使用的
        return sum(arr, 0);		//调用下面的sum
    }

    // 计算arr[l...n)这个区间内所有数字的和
    private static int sum(int[] arr, int left){	//这个 sum 函数是真正的递归函数
        if(left == arr.length)	//相当于整个数组为空的时候
            return 0;
        return arr[left] + sum(arr, left + 1);	//递归调用【从计算 left--n 的和变为 left+1 -- n 的和】
    }

    public static void main(String[] args) {
 
        int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
        System.out.println(sum(nums));
    }
}

输出:

在上述代码中,1.求解最基本的问题

if(left == arr.length)  
            return 0;

2.把原问题转换为更小的问题,要根据更小的问题的答案构建出原问题的答案  arr[left] + 

 return arr[left] + sum(arr, left + 1);

4. 链表的天然递归结构性质

链表可以看做是一个 头结点 0+ 一个更短的链表,在图中,1 就是更短链表的头结点;也可以看做是 头结点 0+ 一个更短的链表,2 就是更短链表的头结点;以此类推,直到最后,可理解为 NULL 本身也是一个链表,是哪个最普通的链表

解决链表中删除元素的问题:

对于最原始的链表,可看做是 头结点e + 一个更短的链表

如何通过得到解来解决原问题的解?对原问题,就是没有考虑 头结点 :如果头结点e != v(无需删除),最终原问题的结果,就是

头结点e +求得的子问题节表;如果需要删除的话,就是后续求出的链表;

代码示例:Solution4.java

class Solution4 {

    public ListNode removeElements(ListNode head, int val) {

        if(head == null)
            return head;

        ListNode res = removeElements(head.next, val);	//res 中存储将头节点后面跟着的链表中所有值为 val 的值删除后剩下的链表
        if(head.val == val)	//head这个节点也满足要被删除的条件
            return res;
        else{
            head.next = res;//不删除 head,head 接head后面的链表删除 val 后的链表
            return head;
        }
    }

    public static void main(String[] args) {

        int[] nums = {1, 2, 6, 3, 4, 5, 6};
        ListNode head = new ListNode(nums);
        System.out.println(head);

        ListNode res = (new Solution4()).removeElements(head, 6);
        System.out.println(res);
    }
}

简化代码:

class Solution5 {

    public ListNode removeElements(ListNode head, int val) {

        if(head == null)
            return head;

        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;
    }

    public static void main(String[] args) {

        int[] nums = {1, 2, 6, 3, 4, 5, 6};
        ListNode head = new ListNode(nums);
        System.out.println(head);

        ListNode res = (new Solution5()).removeElements(head, 6);
        System.out.println(res);
    }
}

输出:

5. 递归运行的机制:递归的微观解读

递归的实质:递归函数的调用就是函数调用,只不过调用的是它自身

数组求和

链表中删除元素

图解过程:

6.递归算法的调试

示例代码:

public class Solution {

    public ListNode removeElements(ListNode head, int val, int depth) {

        String depthString = generateDepthString(depth);

        System.out.print(depthString);
        System.out.println("Call: remove " + val + " in " + head);

        if(head == null){
            System.out.print(depthString);
            System.out.println("Return: " + head);
            return head;
        }

        ListNode res = removeElements(head.next, val, depth + 1);
        System.out.print(depthString);
        System.out.println("After remove " + val + ": " + res);

        ListNode ret;
        if(head.val == val)
            ret = res;
        else{
            head.next = res;
            ret = head;
        }
        System.out.print(depthString);
        System.out.println("Return: " + ret);

        return ret;
    }

    private String generateDepthString(int depth){
        StringBuilder res = new StringBuilder();
        for(int i = 0 ; i < depth ; i ++)
            res.append("--");
        return res.toString();
    }

    public static void main(String[] args) {

        int[] nums = {1, 2, 6, 3, 4, 5, 6};
        ListNode head = new ListNode(nums);
        System.out.println(head);

        ListNode res = (new Solution()).removeElements(head, 6, 0);
        System.out.println(res);
    }

}

输出:

补充:

1.双链表


2.循环链表

3.数组链表(明确知道要处理元素的个数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值