青训营AI刷题 回溯 二叉树 数组长度缩减到1问题 | 豆包MarsCode AI刷题

34 阅读3分钟

问题重述

你有一个长度为 N 的数组 A,和一个整数 X。你可以进行以下操作:选择两个相邻的元素 A[i]A[i+1],如果它们都小于 X,则可以将它们合并成它们的和,并替换原来的两个元素。

例如,如果数组是 [2, 6, 1, 9]X = 5,你可以选择 i = 1,合并 61,得到新数组 [2, 7, 9]

你的任务是判断是否可以通过多次这样的操作,将数组 A 缩减到只剩下一个元素。如果能,返回 1,否则返回 0

示例

样例1:

输入:N = 3, X = 5, A = [4, 3, 1]
输出:1
解释:可以先合并 31,得到 [4, 4],然后再合并 44,得到 [8]

样例2:

输入:N = 4, X = 10, A = [7, 2, 5, 1]
输出:1
解释:可以先合并 25,得到 [7, 7, 1],然后再合并 71,得到 [8, 7],最后合并 87,得到 [15]

样例3:

输入:N = 5, X = 8, A = [3, 3, 2, 2, 1]
输出:1
解释:可以先合并 33,得到 [6, 2, 2, 1],然后再合并 22,得到 [6, 4, 1],最后合并 41,得到 [6, 5],再合并 65,得到 [11]

问题分析

这个问题基于遍历的思想很好解决,这里包含两个要点:我们需要判断是否可以通过一系列操作将数组 A 缩减到长度为 1。其二,每次操作可以选择两个相邻都小于 X 的元素,将它们合并成它们的和。如果是非相邻的遍历,可能首先想到的是双指针法,但在相邻的遍历中,使用回溯构造遍历二叉树的方式似乎更合适(PS:其实和数组总和那道题目很像)。我们只需要按照这棵树尝试所有可能即可,即当前这一步能合并的基础上讨论下一步能不能继续合并。

示例代码

def solution(N: int, X: int, A: list) -> int:
    # write code here
    # 回溯法
    def dfs(_list, idx):
        # 合并到最后只剩俩
        if len(_list) == 2 and _list[0] < X and _list[1] < X:
            return 1

        # 坐标已经不能往右边移动了
        if idx >= len(_list)-1:
            return 0

        # 大于三的
        if _list[idx] < X and _list[idx+1] < X:
            # 如果能先合并,那就合并了再从头移动
            new_list = _list[:idx]+[_list[idx]+_list[idx+1]]
            if idx+2 < len(_list):
                new_list += _list[idx+2:]
            if dfs(new_list,0) == 0:
                return dfs(_list,idx+1)
            else:
                return 1

        else:

            return dfs(_list,idx+1)

    return dfs(A,0)


if __name__ == '__main__':
    print(solution(N=3, X=5, A=[4, 3, 1]) == 1)
    print(solution(N=4, X=10, A=[7, 2, 5, 1]) == 1)
    print(solution(N=5, X=8, A=[3, 3, 2, 2, 1]) == 1)

代码分析

定义一个递归函数 dfs,参数为当前数组 _list 和当前索引 idx。合并之后的list作为参数进一步传递到下一级处理中,同时idx用来标记当前的操作位置(如果合并成功了,则从头开始,否则每次都会移动至下一位)。如果当前元素和下一个元素都小于 X,则进行合并操作,并继续从当前索引开始递归。 否则,继续向右移动索引。在遇到两者情况之一终止递归:

  • 如果数组长度为 2 且两个元素都小于 X,返回 1
  • 如果索引 idx 已经不能往右边移动,返回 0