回溯

    技术2024-11-10  5

    今天理解了一点回溯的相关知识,记录一下。

    链接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?answerType=1&f=discussion 来源:牛客网 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],...,k[m]。请问k[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

    看到这道题最开始想到的是,穷举出所有的可能情况,选择最大的,自然而然穷举,最简单的实现还是递归,我们假设,长度为7的绳子,剪一刀,出现的所有可能,分别是(1*6)、(2*5)、(3*4)、(4*3)、(5*2)、(6*1),观察一下,一组值,第一个用for实现,第二个值为总长减去第一个值,问题来了,那么剪完一刀后剩余的部分又该怎么剪,其实很简单,再按照上面的套路继续穷举呗,所以递归初步成型,就差退出条件,我们发现分到绳子还剩4的时候,绳子再剪的话就亏了,但是退出不应该==4的时候退出,因为我们发现绳子长度为5的时候,其实1*4 小于2*3。(通过特例去验证边界条件是最好的方法)。

    class Solution { public: int back_track(int n) { // n <= 4, 表明不分,长度是最大的 if (n <= 4) { return n; } int ret = 0; for (int i = 1; i < n; ++i) { ret = max(ret, i * back_track(n - i)); } return ret; } int cutRope(int number) { // number = 2 和 3 时,分 2 段和分 1 段的结果是不一样的,所以需要特判一下 if (number == 2) { return 1; } else if (number == 3) { return 2; } return back_track(number); } };

    上面的代码超时了,确实,时间复杂度是n的阶乘。

    我们发现,上面有好多重复计算,我们写一个备忘录,免去重复计算。

    class Solution { public: int back_track(int n, vector<int> &mark) { if (n <= 4) { return n; } // 在方法一的基础上添加 if (mark[n] != -1) { return mark[n]; } int ret = 0; for (int i = 1; i < n; ++i) { ret = max(ret, i * back_track(n - i, mark)); } // 添加部分 return mark[n] = ret; } int cutRope(int number) { if (number == 2) { return 1; } else if (number == 3) { return 2; } // 添加部分 vector<int> mark(number+1, -1); return back_track(number, mark); } };

     在这里说一下,递归从函数调用点入栈之后,出栈的时候,基于本层栈的所有变量的值,从调用点之后,执行到函数大括号完,才能完整出栈。发现上面的代码和DFS很相似,按照上面画的递归图人肉执行一次就会发现回溯的奥秘。

    Processed: 0.025, SQL: 9