LeetCode第26场双周赛(Biweekly Contest 26)解题报告

双周赛补题,总体不难,最后一题的 DP,暴力DP也是可以卡着时间过的,不过还是有优化的DP计算。

第一题:模拟。

第二题:模拟 + 最大公约数 gcd。

第三题:树的 DFS。

第四题:暴力DP 或者 优化后DP。

详细题解如下。


1. 连续字符

         AC代码(C++)

2. 最简分数

         AC代码(C++)

3.统计二叉树中好节点的数目

         AC代码(C++)

4.数位成本和为目标值的最大数字

         AC代码(方法一、暴力DP  C++)

         AC代码(方法二、优化DP  C++)


LeetCode第26场双周赛地址:

https://leetcode-cn.com/contest/biweekly-contest-26/


1. 连续字符

题目链接

https://leetcode-cn.com/problems/consecutive-characters/

题意

给你一个字符串 s ,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。

请你返回字符串的能量。

示例 1:

输入:s = "leetcode"
输出:2
解释:子字符串 "ee" 长度为 2 ,只包含字符 'e' 。

提示:

  • 1 <= s.length <= 500
  • s 只包含小写英文字母。

解题思路

很简单的题目,其实就是遍历一次字符串,判断,如果当前 字符 == 上一个字符,那么长度 + 1。否则,是新的字符了,那么重置 长度 = 1,然后继续判断。

每一次都取 长度 的最大,那么就可以得到答案了。

AC代码(C++)

class Solution {
public:
    int maxPower(string s) {
        int n = s.size();
        int ans = 1;
        int cnt = 1;
        for(int i = 1;i < n; ++i)
        {
            if(s[i] == s[i - 1])
            {
                ++cnt;
            }
            else
            {
                cnt = 1;
            }
            ans = max(ans, cnt);
        }
        return ans;
    }
};

2. 最简分数

题目链接

https://leetcode-cn.com/problems/simplified-fractions/

题意

给你一个整数 n ,请你返回所有 0 到 1 之间(不包括 0 和 1)满足分母小于等于  n 的 最简 分数 。分数可以以 任意 顺序返回。

示例 1:

输入:n = 2
输出:["1/2"]
解释:"1/2" 是唯一一个分母小于等于 2 的最简分数。

示例 3:

输入:n = 4
输出:["1/2","1/3","1/4","2/3","3/4"]
解释:"2/4" 不是最简分数,因为它可以化简为 "1/2" 。

提示:

  • 1 <= n <= 100

解题思路

一开始拿到题目,想到就是,如果我们可以枚举所有情况,也就是枚举 分子 和 分母,这样子时间复杂度是 O(n ^ 2)。然后判断其中那些情况是满足要求的。

满足要求,不能再化简,其实就是,分子和分母的最大公约数是 1,那么我们有计算最大公约数的,gcd,其中可以使用 C++ 自带的 __gcd 函数,也可以自己写,其实很简单的。

gcd 的时间复杂度大概是 O(log max(a, b) ,所以总时间复杂度是 O(n ^ 2 logn),不会超时。

AC代码(C++)

class Solution {
public:
    int gcd(int a, int b)
    {
        if(b == 0) return a;
        return gcd(b, a % b);
    }
    
    vector<string> simplifiedFractions(int n) {
        vector<string> res;
        for(int i = 1;i <= n - 1; ++i)  // 枚举分子
        {
            for(int j = i + 1;j <= n; ++j)  // 枚举分母
            {
                if(gcd(i, j) == 1)
                {
                    string tep = to_string(i) + "/" + to_string(j);
                    res.push_back(tep);
                }
            }
        }
        return res;
    }
};

3.统计二叉树中好节点的数目

题目链接

https://leetcode-cn.com/problems/count-good-nodes-in-binary-tree/

题意

给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。

「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。

示例 1:

【示例有图,具体看链接】
输入:root = [3,1,4,3,null,1,5]
输出:4
解释:图中蓝色节点为好节点。
根节点 (3) 永远是个好节点。
节点 4 -> (3,4) 是路径中的最大值。
节点 5 -> (3,4,5) 是路径中的最大值。
节点 3 -> (3,1,3) 是路径中的最大值。

示例 2:

【示例有图,具体看链接】
输入:root = [3,3,null,4,2]
输出:3
解释:节点 2 -> (3, 3, 2) 不是好节点,因为 "3" 比它大。

示例 3:

输入:root = [1]
输出:1
解释:根节点是好节点。

提示:

  • 二叉树中节点数目范围是 [1, 10^5] 。
  • 每个节点权值的范围是 [-10^4, 10^4] 。

解题分析

根据题目,是一个 树的操作题。

从题目可以分析,如果我们到了某个节点,如果能知道 其上面的 所有节点的 最大值,那么就可以利用 这个最大值 和当前节点的值进行比较,从而判断是否满足要求。然后,对于下一个节点 的最大值,应该是,最大值 和 当前节点的值,取最大。

因此,这么看的话,其实就是一个 DFS 的题目,从根节点,一直往 叶节点 走下去。同时,我们还需要额外记录一个信息,即 最大值。

AC代码(C++)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int ans;
    
    void dfs(TreeNode* root, int mx)
    {
        if(root == nullptr) return;
        if(root->val >= mx)
        {
            mx = root->val;
            ++ans;
        }
        dfs(root->left, mx);
        dfs(root->right, mx);
    }
    
    int goodNodes(TreeNode* root) {
        ans = 0;
        dfs(root, -100000);  // 一开始,对于根节点而言,它 上面的最大值,本来是没有的,但是根节点一定满足,所以设的一个最小值。
        return ans;
    }
};

4.数位成本和为目标值的最大数字

题目链接

https://leetcode-cn.com/problems/form-largest-integer-with-digits-that-add-up-to-target/

题意

给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:

  • 给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。
  • 总成本必须恰好等于 target 。
  • 添加的数位中没有数字 0 。

由于答案可能会很大,请你以字符串形式返回。

如果按照上述要求无法得到任何整数,请你返回 "0" 。

示例 1:

输入:cost = [4,3,2,5,6,7,2,5,5], target = 9
输出:"7772"
解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "997" 也是满足要求的数字,但 "7772" 是较大的数字。
 数字     成本
  1  ->   4
  2  ->   3
  3  ->   2
  4  ->   5
  5  ->   6
  6  ->   7
  7  ->   2
  8  ->   5
  9  ->   5

示例 2:

输入:cost = [7,6,5,5,5,6,8,7,8], target = 12
输出:"85"
解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12 。

示例 3:

输入:cost = [2,4,6,2,4,6,4,4,4], target = 5
输出:"0"
解释:总成本是 target 的条件下,无法生成任何整数。

提示:

  • cost.length == 9
  • 1 <= cost[i] <= 5000
  • 1 <= target <= 5000

解题分析

看到题目,就会想到是 动态规划 DP 的问题。

方法一、暴力 DP

一开始,假设 dp[ i ] 是 消耗了 i 情况下的,最大字符串(长度最长,相同长度,则比较各位大小)

那么这样子的话,时间复杂度大概是 O(target * 9),但是在每一次我们还需要比较,新的字符串和当前保存的字符的大小,那么这里比较,只能逐位比较,那么字符串的最大长度,应该是 target(假设 cost 都是 1 的时候),那么总的时间复杂度是 O(target * 9 * target)。刚好可以时间复杂度卡过。

因此,我们就只要 对于新的 i ,我们找到 dp[ i - cost[ j ] ],然后加上新的位,得到新的字符串 tep,然后和 dp[ i ] 进行比较,取出最大的字符串。

那么最后,如果 dp[ target ] 还是 空字符串,那么说明,无法构建得到相应的字符串,那么 答案返回的是 "0"。否则直接返回  dp[ target ] 即可。

方法二、优化 DP

上面的时间复杂度,高,主要是我们在 得到新的字符串后,都要和当前字符串进行比较,因此这提高了复杂度。

那么我们考虑,为了使得 最后结果字符串 最大,我们希望的是,字符串的长度,尽可能长,同时相同长度的情况下,保证 取得数字 尽可能大。

所以,我们可以先 DP 得到 最大长度,那么得到最大长度后,我们只需要每次,对每一位都取 最大数 即可。

这样子,我们将 字符串比较 分开两部分,这样子,就可以降低复杂度。

首先,我们先计算 DP 得到 最大长度,其实也就是 f[ i ] 是消耗 i 情况下得最大长度。

那么转移方程就是 f[ i ] = max( f[ i - cost[ j ] ] + 1 )

初始化,因为是取最大值,所以一开始都设为 最小值。而 f[ 0 ] = 0,因为没有消耗,长度 == 0。

那么如何判断,是否存在答案呢?

其实,最后 f[ target ] 就是,我们消耗了那么多,得到得 最大长度,那么如果这个长度,<= 0,说明就 不存在 对应得情况,那么此时答案就可以直接返回 "0"

接着,我们已经知道了最大长度,也就是 f [ target ],那么我们每一次,去 取 数得时候,会有对应得 消耗 c,那么我们要求 f[ target] == f[ target - c ] + 1

因为我们要保证每一次,都是按之前 长度慢慢累加得情况转移过来得。因此,需要满足上面得情况。

在满足上面的情况下(也就是长度大家都一样),那么我们只要取当前位的最大数字即可,那么从 9 到 1,如果能取,相当于跳出。

那么此时 target = target - c,我们要一直 tareget 到 0 才结束。

时间复杂度而言,第一步的DP,为 O(target * 9), 第二步,其实也是 O(target * 9),所以总的时间复杂度是 O(target * 9)。

这个就比方法一的时间复杂度降低了。

AC代码(方法一、暴力DP  C++)

const int MAXN = 5e3 + 5;
class Solution {
public:
    // string dp[i]
    string dp[MAXN];
    bool check(string& a, string& b)
    {
        if(a.size() > b.size()) return true;
        if(a.size() < b.size()) return false;
        for(int i = 0;i < a.size(); ++i)
        {
            if(a[i] == b[i]) continue;
            if(a[i] > b[i]) return true;
            else 
                return false;
        }
        return false;
    }

    string largestNumber(vector<int>& cost, int target) {
        for(int i = 0;i <= target; ++i) dp[i] = "";
        for(int i = 1;i <= target; ++i)
        {
            for(int j = 0;j < 9; ++j)
            {
                if(i - cost[j] < 0) continue;
                
                string tep = dp[i - cost[j]];
                if(i - cost[j] > 0 && tep == "") continue;  // 注意,如果从这个转移来 i 的,原来的字符串是空串,说明这个情况之前不可能有,那么也就不可能从这个状态转移到 i(除了 0 的情况下)

                tep += (j + 1 + '0');
                if(check(tep, dp[i])) dp[i] = tep;  // 比较两个字符串的大小
            }
        }
        if(dp[target] == "") return "0";
        return dp[target];
    }
};

AC代码(方法二、优化DP  C++)

class Solution {
public:
    string largestNumber(vector<int>& cost, int target) {
        vector<int> f(target + 5, -1e9);
        f[0] = 0;
        for(int i = 1;i <= target; ++i)
        {
            for(int j = 0;j < 9; ++j)
            {
                if(i - cost[j] < 0) continue;
                f[i] = max(f[i], f[i - cost[j]] + 1);
            }
        }

        if(f[target] <= 0) return "0";
        
        string ans = "";
        int now = target;
        while(now)
        {
            for(int j = 9;j >= 1; --j)
            {
                int c = cost[j - 1];
                if(now - c < 0) continue;
                if(f[now] == f[now - c] + 1)
                {
                    ans += (j + '0');
                    now -= c;
                    break;
                }
            }
        }
        return ans;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值