leetcode解题思路分析(十八)120 - 126题

  1. 三角形最小路径和
    给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

采用动态规划求解,可以自顶向下也可以自底向上

方法一 递归&&备忘录递归 超时
递归

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        return helper(triangle,0,0);
    }
    int helper(vector<vector<int>> triangle,int i,int j){
        if(i==triangle.size()-1) return triangle[i][j];
        int lt = helper(triangle,i+1,j);
        int rt = helper(triangle,i+1,j+1);
        return min(lt, rt) + triangle[i][j];
    }
};

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<vector<int>> dp(triangle);
        for(int i=triangle.size()-2; i>=0; i--)
            for(int j=0; j<triangle[i].size(); j++) dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + dp[i][j];
        return dp[0][0];
    }
};
方法二 动态规划
自底而上,第i行的最小路径和 = 第i+1的最小路径和 + 第i行路径值

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<vector<int>> dp(triangle);
        for(int i=triangle.size()-2; i>=0; i--) 
            for(int j=0; j<triangle[i].size(); j++) dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + dp[i][j];
        return dp[0][0];
    }
};
优化,在原来数组基础上动作

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        for(int i=triangle.size()-2; i>=0; i--) 
            for(int j=0; j<triangle[i].size(); j++) 
                triangle[i][j] = min(triangle[i+1][j],triangle[i+1][j+1]) + triangle[i][j];  
        return triangle[0][0];
    }
空间压缩

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<int> dp(triangle.size()+1,0);
        for(int i=triangle.size()-1; i>=0; i--)
            for(int j=0; j<triangle[i].size(); j++) dp[j] = min(dp[j],dp[j+1]) + triangle[i][j];
        return dp[0];
    }
};
  1. 买股票的最佳时机
    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

因为只能买入后再卖,因此遍历一次即可,先更新最大收益,再更新最低价格,遍历完成即可

class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        int inf = 1e9;
        int minprice = inf, maxprofit = 0;
        for (int price: prices) 
        {
            maxprofit = max(maxprofit, price - minprice);
            minprice = min(price, minprice);
        }
        return maxprofit;
    }
};

  1. 买卖股票的最佳时机2
    本题和上题的区别在于可以多次买入,但是一次只能有一笔交易

做法其实大同小异,增加一个判断:如果价格会降就卖出再买入就好了

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ret = 0;
        int inf = 1e9;
        int minprice = inf, maxprofit = 0;
        for (int price: prices) 
        {
            if (price - minprice > maxprofit)
            {
                maxprofit = price - minprice;
            }
            else 
            {
                minprice = price;
                ret += maxprofit;
                maxprofit = 0;
            }
        }

        return ret + maxprofit;
    }
};

但是就本题而言,其实还有更简单的方法:直接把每次递增的收益加起来就OK了

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int maxprofit = 0;
        for (int i = 1; i < prices.size(); i++) 
        {
            if (prices[i] > prices[i - 1])
                maxprofit += prices[i] - prices[i - 1];
        }
        return maxprofit;
    }
};
  1. 买股票3
    和上题区别在于只可以买卖两次,所以需要考虑最大收益

状态定义
f[i][j][k]表示第i天,进行了j次交易,当前状态为k时的最大利润
该题中j<=2j<=2
k=0表示没有持有股票,k=1表示持有股票

状态转移方程
f[i][j][0] = max(f[i - 1][j][1] + prices[i - 1], f[i - 1][j][0])
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - prices[i - 1])
初始状态定义
f[i][0][0] = 0
f[i][0][1] = -inf
f[0][i][0] = -inf
f[0][i][1] = -inf

class Solution {
public:
 int maxProfit(vector<int> &prices)
    {
        const int inf = 1 << 30;
        const int n = prices.size();
        int f[30000 + 5][3][2];
        for (int i = 0; i <= n; i++)
        {
            f[i][0][0] = 0;
            f[i][0][1] = -inf;
        }
        for (int i = 1; i <= 2; i++)
        {
            f[0][i][0] = -inf;
            f[0][i][1] = -inf;
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = 0; j <= 2; j++)
            {
                f[i][j][0] = max(f[i - 1][j][1] + prices[i - 1], f[i - 1][j][0]);
                if (j)
                {
                    f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - prices[i - 1]);
                }
            }
        }
        return max(max(f[n][0][0], f[n][1][0]), f[n][2][0]);
    }
};
  1. 二叉树的最大路径和
    给定一个非空二叉树,返回其最大路径和。

b + a + c。
b + a + a 的父结点。
c + a + a 的父结点。
其中情况 1,表示如果不联络父结点的情况,或本身是根结点的情况。
这种情况是没法递归的,但是结果有可能是全局最大路径和。
情况 2 和 3,递归时计算 a+b 和 a+c,选择一个更优的方案返回,也就是上面说的递归后的最优解啦。

另外结点有可能是负值,最大和肯定就要想办法舍弃负值(max(0, x))(max(0,x))。
但是上面 3 种情况,无论哪种,a 作为联络点,都不能够舍弃。

代码中使用 val 来记录全局最大路径和。
ret 是情况 2 和 3。
lmr 是情况 1。

所要做的就是递归,递归时记录好全局最大和,返回联络最大和。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
 class Solution 
 {
    public:
    int maxPathSum(TreeNode* root, int &val)
    {
        if (root == nullptr) return 0;
        int left = maxPathSum(root->left, val);
        int right = maxPathSum(root->right, val);
        int lmr = root->val + max(0, left) + max(0, right);
        int ret = root->val + max(0, max(left, right));
        val = max(val, lmr);
        return ret;
    }

    int maxPathSum(TreeNode* root) 
    {
        int val = INT_MIN;
        maxPathSum(root, val);
        return val;
    } 
 };


  1. 验证回文串
    给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

本题难点在于需要排除空格等其他因素,外加忽略大小写,其他很容易

class Solution {
public:
    bool isPalindrome(string s) {
        int i = 0, j = s.size() - 1;
        while (i < j)
        {
            while(!isalnum(s[i]) && i < j)   
                i++;       //直到s[left]为字母或数字
            while(!isalnum(s[j]) && i < j)   
                j--;       //直到s[right]为字母或数字
          
            if (toupper(s[i]) != toupper(s[j]))
                return false;
            else 
            {
                i++;
                j--;
            }
        }
        return true;
    }
};
  1. 单词接龙2
    给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
    每次转换只能改变一个字母。
    转换过程中的中间单词必须是字典中的单词。

本题本质上是一个回溯法的过程,但是由于需要找到所有变换的序列,会较为复杂。主要重点在于如何优化回溯法的表现,过程较长,可以参考这篇文章

class Solution {
public:
    bool sim(const string& s1, const string& s2) {
        int diff = 0;
        for (int i = 0; i < s1.size(); ++i) {
            diff += s1[i] != s2[i];
        }
        return diff <= 1;
    }
    void dfs(const vector<vector<int> >& g, const vector<int>& dfn, const vector<string>& wordList,
            int i, vector<string>& path, vector<vector<string> >& paths) {
        if (dfn[i] == 0) {
            vector<string> v(path);
            reverse(v.begin(), v.end());
            paths.push_back(v);
            return;
        }
        for (auto j : g[i]) {
            if (dfn[j] == dfn[i] - 1) {
                path.push_back(wordList[j]);
                dfs(g, dfn, wordList, j, path, paths);
                path.pop_back();
            }
        }
    }
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        wordList.push_back(beginWord);
        int N = wordList.size();
        vector<vector<int> > g(N);
        int endi = -1;
        // 构图
        for (int i = 0; i < N; ++i) {
            if (wordList[i] == endWord) endi = i;
            for (int j = i + 1; j < N; ++j) {
                if (sim(wordList[i], wordList[j])) {
                    g[i].push_back(j);
                    g[j].push_back(i);
                }
            }
        }
        if (endi == -1) return {};
        // 层级编号
        vector<int> dfn(N, -1);
        queue<int> q;
        q.push(N - 1);
        dfn[N - 1] = 0;
        while (!q.empty()) {
            auto i = q.front();
            q.pop();
            for (auto j : g[i]) {
                if (dfn[j] == -1) {
                    dfn[j] = dfn[i] + 1;
                    q.push(j);
                }
            }
        }
        if (dfn[endi] == -1) return {};
        // 回溯路径
        vector<string> path{endWord};
        vector<vector<string> > paths;
        dfs(g, dfn, wordList, endi, path, paths);
        return paths;
    }
};

发布了129 篇原创文章 · 获赞 15 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u013354486/article/details/105031389
今日推荐