编程思想(技巧)---递归控制

最近在看谷歌面试官讲解的视频,特来分享一波~ 这个系列主要是讲编程思想(或者说技巧),主要包括:递归控制、循环控制、边界控制和一些数据结构的知识。这一节主要说下递归。

大佬们讲课很深奥,首先说了下数学归纳法,并用典型的求和的递推公式:首项加尾项乘以项数除以2,即n(n+1)/2讲解了数学归纳法,这也是递归思想的一部分理论基础。朋友们可以看看这些思想~ 下面说了递归的知识:

(1). 如何保证递归函数正确执行?

使用数学归纳法/自然语言,推纳出来之后转化为程序语言。

(2). 递归书写方法(套路)

  • 严格定义递归函数作用,包括参数,返回值,side-effect
  • 先一般,后特殊
  • 每次调用必须缩小规模
  • 每次问题规模缩小程度必须为1

根据上述套路我们就可以写出一个递归函数,下面举例说明:

例子1:链表创建,返回头节点,链表尾部指向null

描述:使用java创建链表。
ps:面试喜欢问链表是因为,链表容易理解但是代码难写。
下面java代码就用递归实现一个链表:
如图:
这里写图片描述
首先定义一个Node类,这个很好理解,每个Node包括节点值value,和下一个节点。

public class Node {
    private final int value;
    private Node next;

    public Node(int value){
        this.value=value;
        this.next=null;
    }
    public int getValue() {
        return value;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public static void printLinkedList(Node head){
        while (head!=null){
            System.out.print(head.getValue());
            System.out.print(" ");
            head=head.getNext();
        }
        System.out.println();
    }
}

下面使用递归思想实现链表创建:

public class LinkedListCreator {
    //1.严格定义递归函数作用,包括参数,返回值,side-effect
    /**
     * Creates a linked list.
     * @param data the data to create the list
     * @return head of the linked list. The returned linked list
     * ends with last node with getNext() == null.
     */
    public static Node createLinkedList(List<Integer> data){
        //2.先一般,后特殊。递归退出条件
        if (data.isEmpty()){
            return null;
        }
        Node firstNode = new Node(data.get(0));
        //每次调用必须缩小规模,每次问题规模缩小程度为1
        Node headOfSubList = createLinkedList(data.subList(1,data.size()));
        firstNode.setNext(headOfSubList);
        return firstNode;
    }
}

链表的创建比较简单,使用递归几行代码就搞定了。下面看下链表反转的例子。

例子2:链表反转,将上述链表反转

描述:将一个链表反转。
实现如图:
这里写图片描述

主要思想也是使用递归,不断缩小反转的链表长度,最后把第一个元素指向null,并把每次递归的第一个元素的next节点指向第一个节点就ok了
public class LinkedListReverse {
    /**
     * Reverses a linked list.
     * @param head the linked list to reverse
     * @return head of the reversed linked list
     */
    public Node reverseLinkedList(Node head){
        //size=0 or size=1 递归退出条件
        if (head==null||head.getNext()==null){
            return head;
        }
        //递归调用,每次都缩小规模
        Node newHead = reverseLinkedList(head.getNext());
        //把每次递归的第一个元素的next节点指向第一个节点
        head.getNext().setNext(head);
        //第一个元素指向null
        head.setNext(null);
        return newHead;
    }
}

递归反转使用递归也很容易理解啊!之前想破脑袋写了一大堆的代码还错了,使用递归确实代码异常简介,而且一目了然。

例子3:列出给定数列的所有组合

描述:给定一个任意长度的数列,求出该数列使用n个元素能组成的所有子集。
如图:第一个参数为数列的元素集合,第二个n为子集的长度。[1,2,3,4]中使用2个元素能组成的所有可能:
这里写图片描述

使用递归的实现思路如下:

combinations([1,2,3,4],2)
选1(即数组第0个元素)–>调用combinations([2,3,4],1)
不选1—>调用combinations([2,3,4],2)

代码实现:

/**
     * /**
     * Generates all combinations and output them,
     * selecting n elements from data.
     * @param data
     * @param n
     */
    public static void combinations(List<Integer> selected, List<Integer> data,int n){
        if (n==0){
            //output all selected elements
            for (Integer i : selected){
                System.out.print(i);
                System.out.println(" ");
            }
            System.out.println();
            return;
        }
        if (data.isEmpty()){
            return;
        }
        //select element 0
        selected.add(data.get(0));
        combinations(selected,data.subList(1,data.size()),n-1);

        //un-select element 0
        selected.remove(selected.size()-1);
        combinations(selected,data.subList(1,data.size()),n);
    }
}

使用递归的好处想必很明显了,代码简洁明了,方便理解,也方便编码,缺点就是stack的压力太大,效率很低,时间空间开销都很大。下一节使用循环解决这个问题~

以上递归算法小伙伴们可以自己调试,方便大家理解递归的运行过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值