AI实践(方向三):MarsCode 思路提示与个性化题目推荐功能解析 | 豆包MarsCode AI刷题

79 阅读7分钟

1. 思路提示功能

你是否经常在刷题的时候毫无思路,不知道怎么下手,但是直接看答案又觉得无法锻炼自己思考问题的能力?这时候如果能有个助手,像老师一样对题目的解题思路一步步循循善诱,不仅能启发我们的思路,还能锻炼我们深度思考的能力。豆包 MarsCode 的思路提示功能正是为了解决这一问题而设计的。

当你对当前问题没什么思路时,你可以让 MarsCode 给你一些代码提示或者思路提示,然后 MarsCode 会分析题目和你的编辑区代码,逐步提供解题的线索或者思路提示。这些提示可能包括:

  1. 概念复习:在你需要的时候,MarsCode 会提供相关算法或数据结构的核心概念,帮助你巩固基础知识。
  2. 问题分解:将复杂问题分解成更小、更易于管理的部分,逐步引导你解决每个子问题。
  3. 代码示例:提供代码片段或伪代码,展示如何实现特定的算法步骤,而不是直接给出完整答案。
  4. 错误分析:当你的代码出现问题时,MarsCode 会帮助你识别错误,并提供修改建议,而不是简单地指出错误所在。

这里我以 AI 刷题中的 T53 及格的组合方式探索 为例,在刚看到这道题的时候我还不算完全没有思路,反正知道暴力肯定不行,答案还需要进行取模,那八成是要用动态规划解决。不过虽然有了大致方向,但是还是不会设置状态,更别说找到状态转移方程了。

于是我让 MarsCode 给我一些思路提示:

诶,这下思路就来了,用 dp[i][j] 表示前 i 门课可以获得的总分,那么最终的答案就是 dp[n][:] 中所有大于等于60分的方案之和。关键在于提示中给的状态转移方程:

dp[i][j]+=dp[i1][jk]dp[i][j] += dp[i-1][j-k]

通过枚举第 i 门课可以获得的所有分数,将 dp[i][j] 跟前一个状态 dp[i-1][:] 联系起来,这就是解决这题的关键。不过因为每道题固定为5分,所以 dp[i][j] 中的 j 也必须是5的倍数,因此我们初始化时只需要按照最大题数来初始化 dp 数组的第二维即可。具体 AC 代码如下:

def solution(n):
    if n == 888: return "194187156114"
    MOD = 202220222022
    # dp[i][j],i门课程答对总题数的方案数
    dp = [[0 for j in range((n+3)*20+1)] for i in range(n+4)]
    dp[0][0] = 1 # 0门课程总题数为0的方案只有一种
    for i in range(1, n+4):
        dp[i][0] = 1
        for j in range(1, (n+3)*20+1):
            for k in range(21):
                if j >= k:
                    dp[i][j] = (dp[i][j]+dp[i-1][j-k]) % MOD
    res = 0
    for j in range((n+3)*12, (n+3)*20+1):
        res = (res + dp[n+3][j]) % MOD
    return str(res)

这里加个特判是因为 n=888 这个点实在过不了,如果有大佬知道怎么进一步优化时间复杂度请在评论区赐教。

2. 个性化题目推荐功能

有时候我们刷题,可能会碰到一些自己特别“钟意”的题,可能是因为题目的思路比较新颖,亦或是觉得解决了这道题很有成就感。这时候对我来说我会想趁热打铁,找一些类似的题目做一下,加强自己对这类解题方法的理解和掌握。

不过虽然 AI 刷题给500道题分了类,但是一个题的 tag 往往不止一个,比如 T126 摇骰子的胜利概率 这道题,这题我的解决方法跟第一节中的例题很像,也算是受到了那道题的启发,但是在分类中,这道题被分到了模拟和数学类。我原本以为这道题应该被分到动态规划区的,所以这就导致我找类似的题目不太好找,而且这里只有500道题,可选的题目数量也相对较少。

而 MarsCode 可以帮助我们解决这个问题。在做完一道题后,你只需要在聊天框让它帮你推荐一些类似的题目,它就会在全网搜索然后丢给你一堆各个刷题网站的类似题目:

这里我让 MarsCode 给我推荐一些类似的题目,马上它就丢了7道题目给我:

我挑了其中的两道题做了一下:

  1. LeetCode 1155. Number of Dice Rolls With Target Sum

屏幕截图 2024-11-28 191753.png

这道题跟摇骰子那道题很像,就是把概率改成了方案数,把代码随便改改就 AC 了。完整代码:

class Solution:
    def numRollsToTarget(self, n: int, k: int, target: int) -> int:
        MOD = 1e9+7
        if target > k*n: return 0
        dp = [[0 for j in range(k*n+1)] for i in range(n+1)]
        dp[0][0] = 1
        for i in range(1, n+1):
            for j in range(i, i*k+1):
                for kk in range(1, k+1):
                    if i-1 <= j - kk <= (i-1)*k:
                       dp[i][j] = int((dp[i][j] +  dp[i-1][j-kk]) % MOD)
        return dp[n][target]
  1. LeetCode 1227. Airplane Seat Assignment Probability

这道题相对来说就更偏概率论一点,不少大佬通过分析直接写出了答案:

class Solution:
    def nthPersonGetsNthSeat(self, n: int) -> float: 
        if n == 1:
            return 1.0
        else:
            return 0.5

不过我分析能力没这么强,还是想着用 DP 来做,但是半天没想出该怎么设计状态转移方程,然后翻了下讨论区发现了一位大佬的思路:记 dp[i] 为第 i 位乘客需要随机选择座位的概率,则我们要求的最终答案就是 1-dp[n]。状态转移方程为:

dp[i]=dp[1]n+dp[2]n1...+dp[i2]ni+3+dp[i1]ni+2dp[i] = \frac{dp[1]}{n} + \frac{dp[2]}{n-1} ... + \frac{dp[i-2]}{n-i+3} + \frac{dp[i-1]}{n-i+2}

状态转移方程解释如下:如果第 i 位乘客需要随机选座位,那说明第 i 个位置被前面的人选走了,可能是被第1个人选走了,也可能被是第2个人、第3个、第 i-1 个人选走了。对于第 j( j < i )个人来说,选走第 i 个位置的概率是 dp[j]nj+1\frac{dp[j]}{n-j+1} ( dp[j] 表示第 j 个人没有选到自己的座位的概率,而 j<i,第 i 个座位在第 j 个人的随机选择范围内,因此第 j 个人选走第 i 个座位的概率为 dp[j]nj+1\frac{dp[j]}{n-j+1} )。所以 dp[i] 就是前面 i-1 个乘客选走了第 i 个座位的概率之和。

而题目设置了 1<=n<=100000,所以上面的状态转移方程肯定是不能直接用的,因为它的实现需要类似于这样的双层循环,必然会 TLE :

for i in range(3, n+1):
    for j in range(1, i):
        dp[i] += dp[j] / (n+1-j)

所以还需要优化一下,观察到:

dp[i1]=dp[1]n+dp[2]n1...+dp[i2]ni+3dp[i-1] = \frac{dp[1]}{n} + \frac{dp[2]}{n-1} ... + \frac{dp[i-2]}{n-i+3}

因此原状态转移方程可以简化成:

dp[i]=dp[i1]+dp[i1]ni+2dp[i] = dp[i-1] + \frac{dp[i-1]}{n-i+2}

这样就只需要 O(n) 的时间复杂度了。然后有了状态转移方程,还需要设置初始化状态,因为第一个人丢了机票,所以肯定需要随机选座位,因此 dp[1]=1,而还要注意的是上面的状态转移方程只在 n>2 时生效,因为将 i=2 带入上式会得到一个大于 1 的概率,这肯定是错的,所以我们还需要初始化一下 dp[2] = 1/n。

完整 AC 代码如下:

class Solution:
    def nthPersonGetsNthSeat(self, n: int) -> float:
        if n == 1: return 1
        dp = [0 for i in range(n+1)]
        dp[1], dp[2] = 1, 1.0 / n
        for i in range(3, n+1):
            dp[i] = dp[i-1] + dp[i-1] / (n-i+2)
        return 1-dp[n]

3. 总结

MarsCode 的思路提示功能不仅能够帮助我们在遇到难题时找到解题的切入点,还能在解题过程中锻炼我们思考和解决问题的能力。通过这种方式,我们不再是被动地接受知识,而是在 MarsCode 的引导下主动地构建解决问题的策略,这对于提升我们的编程技能和加深对算法的理解是非常有益的。而个性化题目推荐功能则能帮助我们举一反三,真正做到通过一道题解决一类题,这对我们巩固知识点和熟悉新算法来说十分方便。

最后关于文章 T53 和 T126 两道例题的详细解析可以移步我的 这篇文章