zoukankan      html  css  js  c++  java
  • leetcode 递归编程技巧-链表算法题

    今天主要想分享2个小Tip:

    1. 快慢指针的类比理解

    2. 谈谈递归编程技巧,以及我自己是怎么理解并运用它的。

    开篇问题1:leetcode 141:环形链表

    题目描述:

      给定一个链表,判断链表中是否有环。
      如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
      如果链表中存在环,则返回 true 。否则,返回 false 。

    示例:

    解题思路:

      实用快慢指针的思想。
      类比:就比如学校操场,A、B两人跑围着操场跑步,A跑的慢,B跑的快,他们从开始起跑后,随着时间的推移,最终他们会在操场的某个地点相遇。 
      如果A、B在一个高速公路上跑,一个慢一个快,他们始终都没有机会相遇。
      快慢指针就是使用上述的原理,slow指针一次走一步,quick指针一次走两步。通过这样的方式遍历整个链表。如果没相遇就说明没有环,就像高速公路。如果彼此相遇了,说明有环,就像学校操场上的环形跑道。

    编码实现

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     public var val: Int
     *     public var next: ListNode?
     *     public init(_ val: Int) {
     *         self.val = val
     *         self.next = nil
     *     }
     * }
     */
    class Solution: NSObject {
        func hasCycle(_ head: ListNode?) -> Bool {
            if head == nil || head?.next == nil { return false }
            var slow = head
            var fast = head?.next
            while fast != nil && fast?.next != nil {
                if slow === fast { return true }
                slow = slow?.next
                fast = fast?.next?.next
            }
            return false
        }
    }

    问题2:leetcode 141:反转链表

    题目描述:

      反转一个单链表。

    示例:

    输入: 5->4->3->2->1->NULL
    输出: 1->2->3->4->5->NULL

    递归:

      先举一个例子:【非原创】
      周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊?电影院里面太黑了,看不清,没法数,现在你怎么办?

      别忘了你是程序员,这个可难不倒你,递归就开始排上用场了。于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。这就是一个非常标准的递归求解问题的分解过程,去的过程叫“递”,回来的过程叫“归"。基本上,所有的递归问题都可以用递推公式来表示。刚刚这个生活中的例子",我们用递推公式将它表示出来就是这样的:

    f(n)=f(n-1)+1 其中,f(1)=1

    f(n) 表示你想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1)=1 表示第一排的人知道自己在第一排。有了这个递推公式,我们就可以很轻松地将它改为递归代码,如下:

    int f(int n) {
      if (n == 1) return 1;
      return f(n-1) + 1;
    }

    通过上述的例子,大家对递归应该有一个比较清楚的认识了。那么在实际开发中,递归中的代码是怎么运行的了,我们来看下面的代码:

       func test(index: Int) -> Int{
            if index == 0 { return 0}
            print("-------(index)") //第一行打印,记为A
            let result = test(index: index-1)
            print("=======(index)") //第一行打印,记B
            return result
        }
    text(index:2)

    【问题:里面的A和B打印顺序是怎么样的?】

    打印结果:
    -------2
    -------1
    =======1
    =======2

    怎么理解了? 我把代码改写成这样你就理解了

        func test(index: 2){
            print(-------2)
            let result = {
                func test(1){
                    print(-------1)
                    let result = test(0)
                    print(=====0)
                }
            }
            print(======2)
            return result
        }

    递归思路

    1. 电影院例子中的方法f就是用来求当前位置处于哪一排 -->知道方法f的作用

    2. 实现的公式是f(n)=f(n-1)+1 其中,f(1)=1

    3. 放手让它自己运行吧

    解答问题2

    1.reverseList函数是用来反转链表的
    2.实现公式:

    newHead = reverseList(head.next)
    head.next.next = head
    head = nil

    3.递归下去

    编码实现

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     public var val: Int
     *     public var next: ListNode?
     *     public init(_ val: Int) {
     *         self.val = val
     *         self.next = nil
     *     }
     * }
     */
     
    class Solution {
      func reverseList(_ head: ListNode?) -> ListNode? {
            if head == nil || head?.next == nil { return head }
            let newHead = reverseList(head?.next)
            head?.next?.next = head
            head?.next = nil
            return newHead
        }
    }

    如何简单正确的理解了?

      咱们一起来撸下代码,跟着代码流程走一遍。

    1. 我们输入5->4->3->2->1->NULL

    2. head=5 不为空,然后执行reverseList(head?.next)即reverseList(4)

    3. 到了第2步,reverseList(4)的最后的得到的结果是1->2->3->4->NULL【因为reverseList的作用就是反转】

    4. head.next.next = head即head.next.next = 5

    5. 最后head=nil

    6. done
      需要把reverseList(head)当作一个整体。事实上,程序也是这样运行的。

    总结

      今天我们理解了快慢指针的原理,通过类比的方式我们可以很好的理解并且可以很久的记住它的原理。然后我们分析了递归的实现思路以及递归内部的调用栈。最后我们通过编码实现了链表算法题的解答。在文章最后,我想说的是,递归确实很难理解,如果你真的很想掌握它,那就像我一样,写一个test(intdx:int)函数来测试一下,走一遍递归流程,知道是怎么递的也知道是怎么归的,动手操作了,相信你一定会有惊喜。

    欢迎关注【无量测试之道】公众号,回复【领取资源】,
    Python编程学习资源干货、
    Python+Appium框架APP的UI自动化、
    Python+Selenium框架Web的UI自动化、
    Python+Unittest框架API自动化、

    资源和代码 免费送啦~
    文章下方有公众号二维码,可直接微信扫一扫关注即可。

    备注:我的个人公众号已正式开通,致力于测试技术的分享,包含:大数据测试、功能测试,测试开发,API接口自动化、测试运维、UI自动化测试等,微信搜索公众号:“无量测试之道”,或扫描下方二维码:

     添加关注,让我们一起共同成长!

  • 相关阅读:
    【LeetCode】46. 全排列(回溯)
    [P2894][USACO08FEB] 酒店Hotel (线段树+懒标记下传)
    [P2680][NOIP2015T6] 运输计划 (LCA+树上差分+二分)
    静态主席树学习笔记
    [P1941][NOIP2014T3] 飞扬的小鸟 (0/1背包+完全背包)
    [P1084][NOIP2012T6] 疫情控制 (二分+贪心+LCA)
    [P3959][NOIP2017T5] 宝藏 (状压DP+DFS)
    [P2679][NOIP2015T5] 子串 (DP+滚动数组)
    [P1314][NOIP2011T5] 聪明的质检员 (二分+前缀和)
    [P1966][NOIP2013T2] 火柴排队 (求逆序对+归并排序/树状数组)
  • 原文地址:https://www.cnblogs.com/Wu13241454771/p/13944774.html
Copyright © 2011-2022 走看看