LintCode 解题记录17.10.21

本文总结了三道经典动态规划问题:House Robber系列及Unique Binary Search Trees II的解题思路,介绍了如何运用动态规划思想解决这些最优化问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

刷题的进度还是非常慢的,照这个进度,115道题估计过年前都刷不完。。真的是怠惰啊。

House Robber

题目描述

给定一个数组,要求每次不能取相邻的两个数,求能取到所有数的最大和。

思路

初读一遍,发现是最优化问题(最大化),于是就思考最优化的两种思路 贪心和Dp。这里采用Dp的思想。我自己的思路就是采用 局部最优化与全局最优化的思想。设local[i]表示抢了第i家所能获得的最大利润,global[i]表示到第i家时所能获得的最大利润。那么递归关系如下:
local[i] = num[i] + global[i-2]; (1)
global[i] = max{local[i], global[i-1]}; (2)
写出这个递推式的思路如下:
1.针对local[i],假设我抢了第i家,那么第i-1家就不能抢,此时所能获得的最大利润就是抢劫前i-2家所能获得的最大利润加上第i家的利润,于是就得到式1
2.针对global[i],就可以分为两种情况,抢劫第i家和没有抢劫第i家,如果抢劫了第i家,所能获得的最大利润就是local[i],如果没有抢劫第i家,那么所能所得的最大利润就是抢劫了前i-1家所能获得的最大利润。
综合一下,得到下面的递推关系:
global[i] = max(global[i-2]+num[i], global[i-1]),我们只要初始化global[0],global[1]就好了,其中global[0] = num[0], global[1] = max(num[0], num[1]);

挑战

O(n)时间复杂度,O(1)空间复杂度。
显然上面的思路是O(n)的时间复杂度,O(n)的空间复杂度,接下来就是优化空间复杂度。于是我维护3个变量分别代表global[i],global[i-1],global[i-2]即可。然后只需要顺序遍历即可。

代码
    long long houseRobber(vector<int> &A) {
        // write your code here
        if (A.size() == 0) return 0;
        if (A.size() == 1) return A[0];
        long long res = 0, before = A[0], pre = max(A[0], A[1]);
        //res = global[i], pre = global[i-1], before = global[i-2];
        for (int i = 2; i < A.size(); i++) {
            res = max(before + A[i], pre);
            //i即将++,更新before与pre
            before = pre; 
            pre = res;
        }
        return res;
    }

House Robber II

题目描述

还是这个小偷,现在是这个数组首尾相连,问你最大的偷取价值。

思路

其实这题就是上一题稍微变形一下,即如果偷了第一家,那么就不能偷最后一家;如果偷了最后一家,那么就不能偷第一家,实际上这道题只需要用两次House Robber中的方法,最后返回两者中的较大值即可。

代码
    int houseRobber2(vector<int> nums) {
        // write your code here
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        //houseRobber即为上一道题的方法
        int a = houseRobber(vector<int>(nums.begin(), nums.end()-1));
        int b = houseRobber(vector<int>(nums.begin()+1, nums.end()));
        return max(a, b);
    }

House RobberIII

题目描述

现在这片房子既不是用数组表示,也不是用圆圈表示,而是用了一颗二叉树来表示。不能直接抢劫两个直接相连的房子,仍然返回这个最大的利润值。

思路

树的题目自然是递归来做。此题中,对于某一节点,显然是分是否抢劫该节点两种情况来进行讨论。如果是对于两种情况分别进行递归,那恐怕会获得超时错误。因为你相当于搜索了这棵树两遍。为了避免这种情况,用一个长度为2的数组来进行存储数据,res[0]表示没有抢劫该节点,而res[1]表示抢劫了该节点。那么对于某一节点,首先递归计算其左子树得到left数组,然后递归计算右子树得到right,那么于是有
res[0] = max(left[0], left[1]) + max(right[0], right[1]) ; //没有抢劫该节点
res[1] = left[0] + right[0] + node->val; //抢劫了该节点

代码
    int houseRobber3(TreeNode * root) {
        // write your code here
        vector<int> res = helper(root);
        return max(res[0], res[1]);
    }

    vector<int> helper(TreeNode *node) {
        if (node == NULL) return vector<int>(2, 0);
        vector<int> left = helper(node->left);
        vector<int> right = helper(node->right);
        vector<int> res(2, 0);
        res[0] = max(left[0], left[1]) + max(right[0], right[1]);
        res[1] = left[0] + right[0] + node->val;
        return res;
    }

Unique Binary Search Trees II

题目描述

给定一个n,产生所有不同的二叉搜索树,每一颗树都存储着1-n这n个数。

思路

递归。对于一个范围[start, end],从中任远一个作为根节点,然后把该范围划分成了左右两个数组。然后用左边的数组递归得到其左子树集合,以及同样的右子树集合。最后对于左子树集合中的每一个节点,与右子树集合中的每一个节点,拼接成一棵树,并把该数的根节点放入最终的答案中。

代码
    vector<TreeNode *> generateTrees(int n) {
        // write your code here
        if (n < 0) {
            return vector<TreeNode*> {NULL};
        }
        return helper(1, n);
    }

    vector<TreeNode *> helper(int start, int end) {
        vector<TreeNode *> res;
        if (start > end) {
            res.push_back(NULL);
            return res;
        }
        for (int i = start; i <= end; i++) {
            vector<TreeNode *> left = helper(start, i-1);
            vector<TreeNode *> right = helper(i+1, end);
            for (auto l: left) {
                for (auto r: right) {
                    TreeNode *root = new TreeNode(i);
                    root->right = r;
                    root->left = l;
                    res.push_back(root);
                }
            }
        }
        return res;
    }
注意

这道题可以说是非常能体现树、递归、以及二元查找树的紧密联系了。题目挺经典的。

K SumII

题目描述

给定了n个数,从中选取k个数,使他们的和为target。

思路

dfs。注意一下递归终止的条件。

代码
    vector<vector<int>> kSumII(vector<int> &A, int k, int target) {
        // write your code here
        vector<vector<int>> res;
        vector<int> tmp;
        dfs(res, tmp, A, k, target, 0);
        return res;

    }

    void dfs(vector<vector<int>> &res, vector<int> tmp, vector<int> A, int k, int target, int idx) {
        if (target <= 0 || k <= 0) {
            if (target == 0 && k == 0) {
                res.push_back(tmp);
            }
            return;
        }

        for (int i = idx; i < A.size(); i++) {
            tmp.push_back(A[i]);
            dfs(res, tmp, A, k-1, target-A[i], i+1);
            tmp.pop_back();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值