算法通关村第二关——终于学会链表反转了

回归正题,今天的主题是链表反转。顾名思义,就是将链表进行反转,通俗的来讲就是将正过来的变成反过来的。

不知名网友:直接上代码不就行了。

:有道理啊!好吧,大早上起来上班,身体是不愿意的,脑子是昏昏的。 

输入:

{ 1, 5, 6, 9, 7, 8 }

 输出:

{ 8, 7, 9, 6, 5, 1 }

解题思路:我们既然要解题,我们首先要理解题目,理解题目过后,我们要思考我们要做什么才能解决题目? 那我们又要采取什么技术来达到我们想做的呢?  这些步骤明确了,那我们也就自然的把问题解决了。

现在我们题目已经理解了,我们需要做的就是将之前指向下一个节点反过来指向上一个节点。那我们又要采取什么方法来实现这个目的呢?

实现:既然这个题目是链表的题目,那我们首先想到的肯定是使用链表的方式,除此之外,思考一下其他的常用的数据结构和算法,发现利用栈应该也可以。

1、链表

1.1头插法

既然使用链表,我们最容易想到的是创建一个新的链表newlist,然后遍历原来的链表,将当前的节点利用我们之前讲过的头插法插入到newlist中。最后得到的newlist就是反转后的链表。

重点: 我们需要的哪些节点的信息?我们直接将当前节点插入行不行?

我们需要当前节点的信息curr,和下一节点的信息curr.next。直接插入是不行的,试想一下,假如你直接将当前节点curr插入newlist中,那curr.next就是newlist中的,那你再想获取原本链表中的next节点怎么办?因此我们要在每次插入的时候保存curr.next。(因为采用头插的方法,我们也叫头插法)

代码

    public static ListNode reverseListByDummyNotCreate(ListNode head) {
        ListNode ans = new ListNode(-1); 创建虚拟节点
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;  //保存当前节点的下一个节点信息
            cur.next = ans.next;   //将当前节点利用头插法插入到新链表中
            ans.next = cur;
            cur = next;   //继续原节点的遍历
        }
        return ans.next;
    }
1.2 穿针引线法(重要!!)

有网友就讲了,你这直接反转不就行了为什么还要弄一个新的链表,这位网友说的很好。我很赞同,那么接下来我们就来看一下穿针引线法。(处理方式类似于穿针引线)

重点: 我们需要的哪些节点的信息?我们直接将当前节点指向反转后的头结点行不行?

这种方法我们还是需要三个节点信息,curr当前节点,curr.next当前节点的下一个节点,prev反转后的链表的头节点。 那肯定不行呀,你看上一种解题思路,那你当前节点的下一个节点不就获取不到了。因此我们要将curr.next存储下来。

代码

    public static ListNode reverseListSimple(ListNode head) {
        ListNode prev = null; //创建反转后的头结点
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;  //保存当前节点的下一个节点信息
            curr.next = prev;  ///当前节点指向反转链表的头节点
            prev = curr;   //反转后的链表的头结点为当前节点
            curr = next;  //继续原链表的遍历
        }
        return prev;
    }

 2、栈

我们都知道栈的特点是:先入后出!那我们是不是可以将原链表中的节点进行入push入栈处理,之后再pop()取栈顶元素直至栈为空实现链表的反转。

代码

    public ListNode ReverseList(ListNode head) {
        Stack<ListNode> stack = new Stack<>();    // 栈
        while(head != null){     //把链表中的节点入栈
            stack.push(head);
            head = head.next;
        }
        //判断栈为空,则返回null
        if(stack.isEmpty()) {   
            return null;
        }
        //从栈中取出元素,然后组成链表,则为反转后的链表
        ListNode dummy = stack.pop();
        ListNode temp = dummy;
        while(!stack.isEmpty()){
            temp.next = stack.pop();
            temp = temp.next;
        }
        //最后一个节点的next要置为空,否则会构成环
        temp.next = null;
        return dummy;
    }

扩展:递归法!

一般这种递归法,只有做了很多算法的老手,才会想起来使用,同时递归也比较难理解。

什么是递归?

递归就是在运行过程中不断的调用自己。递归有两个重要的过程:一个是递的过程,一个是归的过程。

上代码:

public void fun(参数) {
    if (终止条件) {
        return;
    }
    fun(参数);
    (其他判断条件或语句);
}
  1. 当第一次进入函数时,先判断是否符合终止条件,符合则直接结束函数,不符合入下一语句,调用自己重新进入下一层自身函数,(注意这是最外一层将不向下继续执行语句,外层卡在fun(参数处)),这个调用自己进入自身函数的操作过程即为“递”的过程。
  2. 假设进入下一层后符合终止条件,返回结果,此时之前进入自身函数执行完成返回最外一层函数,最外一层函数递归调用处得到结果,(即内层函数执行完成得到结果返回值),这个过程即为“归”的过程。这时最外一层函数才能继续执行下一语句,直至函数运行完成。 

什么场景适合用递归?

1.大问题可以拆分为多个子问题。

2.原问题和拆分后的子问题除了数据规模不同,解决思路完全相同。

3.存在递归终止条件。

注:递归在线性数据结构中使用不太明显,迭代基本可以很容易地解决问题。

递归在非线性结构中非常重要,比如二叉树,回溯,典型的树形问题-九宫格字母组合。

递归必须满足的条件?

  1. 有边界,即终止条件。
  2. 需要调用自己。

注:这两个条件缺一不可,并且其中终止条件语句必须在递归调用语句之前。如果顺序颠倒则递归函数会进入死循环,永远退不出来,会出现堆栈溢出异常(StackOverflowError)。

更多关于递归的知识,想学习的友友可以自己学看~

我们希望n(k+1)的下一个节点指向n(k)。所以:n(k).next.next = n(k)

注:n1的下一个节点必须指向∅。要不然可能会形成环。

代码:

    /**
     * 以链表1->2->3->4->5举例
     * @param head
     * @return
     */
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            /*
                直到当前节点的下一个节点为空时返回当前节点
                由于5没有下一个节点了,所以此处返回节点5
             */
            return head;
        }
        //递归传入下一个节点,目的是为了到达最后一个节点
        ListNode newHead = reverseList(head.next);
                /*
            第一轮出栈,head为5,head.next为空,返回5
            第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
                      把当前节点的子节点的子节点指向当前节点
                      此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
                      此时链表为1->2->3->4<-5
                      返回节点5
            第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
                      此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
                      此时链表为1->2->3<-4<-5
                      返回节点5
            第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
                      此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
                      此时链表为1->2<-3<-4<-5
                      返回节点5
            第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
                      此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
                      此时链表为1<-2<-3<-4<-5
                      返回节点5
            出栈完成,最终头节点5->4->3->2->1
         */
        head.next.next = head;
        head.next = null;
        return newHead;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值