leetcode刷题记录day012:206和21

206、难度简单:

要求:链表可以选用迭代或递归方式完成反转
从概念上讲,递归就是指程序调用自身的编程思想,即一个函数调用本身;迭代是利用已知的变量值,根据递推公式不断演进得到变量新值得编程思想。从直观上讲,递归是将大问题化为相同结构的小问题,从待求解的问题出发,一直分解到已经已知答案的最小问题为止,然后再逐级返回,从而得到大问题的解(一个非常形象的例子就是分类回归树 classification and regression tree,从root出发,先将root分解为另一个(root,sub-tree),就这样一直分解,直到遇到leafs后逐层返回);而迭代则是从已知值出发,通过递推式,不断更新变量新值,一直到能够解决要求的问题为止。
从“编程之美”的角度看,可以借用一句非常经典的话:“迭代是人,递归是神!”来从宏观上对二者进行把握。

方法一:迭代法:时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。空间复杂度:O(1)

/**
 * 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) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

原理:假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。
在遍历链表时,将当前节点的 \textit{next}next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

pre是前一个指针,由于链表的最后指向null,所以pre初始值为null
curr是指当前指针
next是当前指针的下一个指针
循环过程:当前指针指向前一个指针,一个循环结束;所以当前指针成为了下一个指针的前一个指针;当前指针成为了下一个指针;
为防止当前指针指向前一个指针后丢失其原先的下一个指针及往后,所以新创建指针next用于保存。

方法二:递归:时间复杂度:O(n) 其中 n 是链表的长度。需要对链表的每个节点进行反转操作。
空间复杂度:O(n) 其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为 n 层。

//以链表1->2->3->4->5举例
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            /*
                直到当前节点的下一个节点为空时返回当前节点
                由于5没有下一个节点了,所以此处返回节点5
             */
            return head;
        }
        //递归传入下一个节点,目的是为了到达最后一个节点
        //直到节点.next = null为止,可以看成一直在执行上面的if判断和下面这一行的传递head.next语句。
        ListNode newHead = reverseList(head.next);
                /*
            第一轮,head为5,head.next为空,进入if判断内,上面的return返回5,newHead成为5。
            	   此时head为4,开始执行下面的head.next.next = head;让5指向4;
            	   head.next = null;让4指向空;返回 5;
            第二轮,此时链表顺序为1->2->3->4<-5
				  此时head为3,head.next为5,执行head.next.next=head也就是4.next=3,同理往下得
                   此时链表为1->2->3<-4<-5,返回 5
            直到1<-2<-3<-4<-5时,返回5。没有下一轮了,结束。
            最终头节点5->4->3-2->1
         */
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

原理:关键在于反向工作。假设链表的其余部分已经被反转,现在应该如何反转它前面的部分?
假设链表为:n1 →…→ nk−1 → nk → nk+1 → … → nm →∅
若从节点 nk+1到 nm 已经被反转,而我们正处于 nk
n1→…→ n k−1 → nk → nk+1 ← … ← nm
我们希望 nk+1 的下一个节点指向 nk
所以,nk.next.next = nk 需要注意的是 n1 的下一个节点必须指向 ∅。如果忽略了这一点,链表中可能会产生环。

21、难度简单:

方法一:递归:

/**
 * 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) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

原理:也就是当list1的头值小于list2时,就把list[0]和list2进行拼接,否则,就把list[2]和ist1进行拼接。对该过程进行递归
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQILa0AJ-1632619750314)(21. 合并两个有序链表.assets/image-20210926085539311.png)]

如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。

代码过程:
两链表谁为空就返回另一方
否则,执行上面的原理。假设list1 = 1 4 5 9,list2 = 2 3 6 10
比较list1 [0]和list2 [0]:1小于2,应将list1 [0]和list2 [0]连接,让list1 [0]指向mergeTwoLists(l1.next, l2);
比较list1 [1]和list2 [0]:4大于2,应将list2 [0]和list1 [1]连接,让list2 [0]指向mergeTwoLists(l1, l2.next);
比较list1 [1]和list2 [1]:略
直到两个参数中的一个.next为null时,返回另一方。
也就是比较完list1 [3]和list2 [3]后传递参数list1中3.next时发现为空,返回list2(值10),此时将list1 [3]指向了list2 [3],返回list1 [3]
也就是list2 [2]又将指向list1 [3]
同理…

方法二:迭代

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);

        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 == null ? l2 : l1;

        return prehead.next;
    }
}

原理:创建一个新节点,作为结果链表的头结点。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

代码过程:
prehead.next作为最后返回的结果链表的头结点存在
prev作为构建prehead链表的辅助结点
while循环:prev指向 l1 和 l2中更小的一方,同时将被指向的节点向后移一位,以便下次比较。
当l1和l2有一方为空后就退出循环,若存在不为空的一方,就让prev指向那一方。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeYello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值