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

依旧晚上没做,后面的才来补题的,听说很简单。

第一题:模拟。

第二题:贪心 + 暴力枚举。

第三题:思维 + 倒退。

第四题:DP。

详细题解如下。


1. 逐步求和得到正数的最小值(Minimum Value to Get Positive Step by Step Sum)

        AC代码(C++)

2. 和为 K 的最少斐波那契数字数目(Find the Minimum Number of Fibonacci Numbers Whose Sum is K)

        AC代码(C++)

3.长度为 n 的开心字符串中字典序第 k 小的字符串(the K th Lexicographical String of All Happy Strings of Length N)

        AC代码(C++)

4.恢复数组(Restore the Array)

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

 


LeetCode第24场双周赛地址:

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


1. 逐步求和得到正数的最小值(Minimum Value to Get Positive Step by Step Sum)

题目链接

https://leetcode-cn.com/problems/minimum-value-to-get-positive-step-by-step-sum/

题意

给你一个整数数组 nums 。你可以选定任意的 正数 startValue 作为初始值。

你需要从左到右遍历 nums 数组,并将 startValue 依次累加上 nums 数组中的值。

请你在确保累加和始终大于等于 1 的前提下,选出一个最小的 正数 作为 startValue 。

示例 1:

输入:nums = [-3,2,-3,4,2]
输出:5
解释:如果你选择 startValue = 4,在第三次累加时,和小于 1 。
                累加求和
                startValue = 4 | startValue = 5 | nums
                  (4 -3 ) = 1  | (5 -3 ) = 2    |  -3
                  (1 +2 ) = 3  | (2 +2 ) = 4    |   2
                  (3 -3 ) = 0  | (4 -3 ) = 1    |  -3
                  (0 +4 ) = 4  | (1 +4 ) = 5    |   4
                  (4 +2 ) = 6  | (5 +2 ) = 7    |   2

提示:

  • 1 <= nums.length <= 100
  • -100 <= nums[i] <= 100

解题思路

根据题目意思,我们需要知道一个最小数字 ,使得对整个数组累加的过程中,都满足 >= 1

根据这个数字是多少,我们可以假设一开始是 1

那么在遍历数组的过程中,发现累加 < 1,说明初始数不够大,那么我们要增大初始数,使得这个累加 = 1 (使得初始数增加的值是最少的)

这是因为,我们一开始就设初始值,和在过程中,发现 < 1 了再去增大初始值是一样的。都是保证每次累加 >= 1。

AC代码(C++)

class Solution {
public:
    int minStartValue(vector<int>& nums) {
        int res = 1;  // 一开始初始值
        int cur = 1;  // 目前的累加值
        for(auto num : nums)
        {
            cur += num;
            if(cur < 1)  // 发现累加值 < 1,说明初始值要增大,要使得 cur -> 1。
            {
                res += (1 - cur);
                cur = 1;
            }
        }
        return res;
    }
};

2. 和为 K 的最少斐波那契数字数目(Find the Minimum Number of Fibonacci Numbers Whose Sum is K)

题目链接

https://leetcode-cn.com/problems/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k/

题意

给你数字 k ,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。

斐波那契数字定义为:

  • F1 = 1
  • F2 = 1
  • Fn = Fn-1 + Fn-2 , 其中 n > 2 。

数据保证对于给定的 k ,一定能找到可行解。

示例 1:

输入:k = 7
输出:2 
解释:斐波那契数字为:1,1,2,3,5,8,13,……
对于 k = 7 ,我们可以得到 2 + 5 = 7 。

示例 2:

输入:k = 19
输出:3 
解释:对于 k = 19 ,我们可以得到 1 + 5 + 13 = 19 。

提示:

  • 1 <= k <= 10^9

解题思路

根据题意,先要知道 整个 斐波那契数列。

然后利用 k ,不断的去找,最接近 k 的最大值,然后 减去。这样子,每加一个数字(同样的开销),得到的收益是最大的。

AC代码(C++)

class Solution {
public:
    int findMinFibonacciNumbers(int k) {
        vector<int> nums;
        nums.push_back(1);
        nums.push_back(1);
        int a = 1, b = 1, c = a + b;
        while(c <= k)
        {
            nums.push_back(c);
            a = b;
            b = c;
            c = a + b;
        }
        int res = 0;
        for(int i = nums.size() - 1;i >= 0; --i)
        {
            if(k == 0) break;
            if(k >= nums[i])
            {
                k -= nums[i];
                ++res;
            }
        }
        return res;
    }
};

3.长度为 n 的开心字符串中字典序第 k 小的字符串(the K th Lexicographical String of All Happy Strings of Length N)

题目链接

https://leetcode-cn.com/problems/the-k-th-lexicographical-string-of-all-happy-strings-of-length-n/

题意

一个 「开心字符串」定义为:

  • 仅包含小写字母 ['a', 'b', 'c'].
  • 对所有在 1 到 s.length - 1 之间的 i ,满足 s[i] != s[i + 1] (字符串的下标从 1 开始)。

比方说,字符串 "abc","ac","b" 和 "abcbabcbcb" 都是开心字符串,但是 "aa","baa" 和 "ababbc" 都不是开心字符串。

给你两个整数 n 和 k ,你需要将长度为 n 的所有开心字符串按字典序排序。

请你返回排序后的第 k 个开心字符串,如果长度为 n 的开心字符串少于 k 个,那么请你返回 空字符串 。

示例 1:

输入:n = 1, k = 3
输出:"c"
解释:列表 ["a", "b", "c"] 包含了所有长度为 1 的开心字符串。按照字典序排序后第三个字符串为 "c" 。

示例 2:

输入:n = 1, k = 4
输出:""
解释:长度为 1 的开心字符串只有 3 个。

示例 3:

输入:n = 3, k = 9
输出:"cab"
解释:长度为 3 的开心字符串总共有 12 个 ["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"] 。第 9 个字符串为 "cab"

提示:

  • 1 <= n <= 10
  • 1 <= k <= 100

解题分析

我们根据题目分析,可以知道

n = 1 时,有三个

n = 2时,在上一个基础上,每个扩展出 2 个。

那么反过来,

比如 我们要得到 最后中的 第 k 个(n),那么根据 n -  1 扩展 到 n 的时候,每一次都有两个,那么从 n 回到 n - 1,那就是 n - 1 中 的 第 (k + 1) / 2。

所以我们根据 n ,计算 n 轮 k 的值

然后从一开始往后推

比如 一开始  = 2,说明是 b,到后面的时候,要注意,因为 b 是对应两个,那么需要把后面的值,变成 0 或者 1。

也就是 (v + 1) % 2。

然后可能是 0 或者 1。那么此时本来应该要取 a,b,c 中第 0 个或第 1 个。(这里是按顺序,但是不一定 a 就是第 0 个。)因为要求是和上一个不同,才开始计数。

那么当计数 ==  (v + 1) % 2,所以对应的字母,接在后面。

这样子,时间复杂度是 O(n)。

AC代码(C++)

class Solution {
public:
    string getHappyString(int n, int k) {
        if(3 * (int)pow(2, n - 1) < k) return "";

        vector<int> v;
        for(int i = 0;i < n; ++i)
        {
            v.push_back(k);
            k = (k + 1) / 2;
        }
        int i = v.size() - 1;
        string ans = "";
        ans += char(v[i] - 1 + 'a');
        for(i--; i >= 0; --i)
        {
            int cur = (v[i] + 1) % 2;
            int cnt = -1;
            for(int j = 0;j < 3; ++j)
            {
                if(j + 'a' == ans.back()) continue;
                else ++cnt;
                if(cnt == cur)
                {
                    ans += char(j + 'a');
                    break;
                }
            }
        }
        return ans;
    }
};

4.恢复数组(Restore the Array)

题目链接

https://leetcode-cn.com/problems/restore-the-array/

题意

某个程序本来应该输出一个整数数组。但是这个程序忘记输出空格了以致输出了一个数字字符串,我们所知道的信息只有:数组中所有整数都在 [1, k] 之间,且数组中的数字都没有前导 0 。

给你字符串 s 和整数 k 。可能会有多种不同的数组恢复结果。

按照上述程序,请你返回所有可能输出字符串 s 的数组方案数。

由于数组方案数可能会很大,请你返回它对 10^9 + 7 取余 后的结果。

示例 1:

输入:s = "1000", k = 10000
输出:1
解释:唯一一种可能的数组方案是 [1000]

示例 2:

输入:s = "1317", k = 2000
输出:8
解释:可行的数组方案为 [1317],[131,7],[13,17],[1,317],[13,1,7],[1,31,7],[1,3,17],[1,3,1,7]

示例 3:

输入:s = "1234567890", k = 90
输出:34

提示:

  • 1 <= s.length <= 10^5.
  • s 只包含数字且不包含前导 0 。
  • 1 <= k <= 10^9.

解题分析

看了题目之后,第一想到的就是动态规划。

1)那么就要设状态,根据题目,状态应该有 第 i 个数(字符)的操作,是应该单独自己,还是连接到前面,那么要连接到前面几个,要记录下来,这是因为要知道链接上去得数,会不会超过 k 得范围。

因此,有两个状态要记录,dp[ i ][ j ],即在 第 i 个字符时,单独自己(自己作为一个新得,1),或者和前面链接(记录前面的链接可能 all j,那么当前就为 j + 1)

2)状态转移

有两种情况

  • 第一种情况,自己作为一个新数,那么这样子,所有 i - 1 的所有 j 都可以转移过来,因为是成立的。dp[ i ][ 1 ] += dp[ i - 1 ][ j ]
    • 注意了,这种情况有一个前提,当前这个数不能是 0,因为 0 不能作为一个新数 的开始
  • 第二种情况,链接到前面,那么就遍历 i - 1 的所有可能的  j,如果可以,那么就是 dp[ i ][ j + 1 ] += dp[ i - 1][ j ]。

那么判断 j 的长度,最多 10,因为组成新的 -> 11,就会超过 k 的范围 (10 ^ 9)

那么现在就要判断,第二种情况,什么时候的 j 是可以的

  • 其实就是      x .... i - 1  i,[x, i - 1] 总长度 j
  • 如果 x 这个位置是 0,那么说明是不可能从这个组成一个数的,不可以
  • 从 x 一直到 i 位置,组成的数,要在 k 的范围内。满足 可以,不满足则不可以。

3)初始值,dp[ 1 ][ 1 ] = 1,其他都是 0。一开始,只有一个数的时候,只能自己组成一个新数。

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

const int MAXN = 1e5 + 50;
const int MOD = 1e9 + 7;

typedef long long LL;

class Solution {
public:
    int dp[MAXN][15];

    bool check(string& s, int k, int ed, int len)
    {
        int st = ed + 1 - len;
        if(s[st] == '0') return false;
        
        long long val = 0;
        for(int i = st;i < st + len + 1; ++i)
        {
            val = val * 10 + (s[i] - '0');
        }
        if(val > k) return false;
        return true;
    }

    int numberOfArrays(string s, int k) {
        memset(dp, 0, sizeof(dp));
        
        dp[1][0] = 0;
        if(s[0] - '0' > k) return 0;  // 因为一开始 第一个数,肯定组成一个新数,不能是 0
        dp[1][1] = 1;

        int n = s.size();

        for(int i = 2;i <= n; ++i)
        {
            if(s[i - 1] - '0' <= k && s[i - 1] - '0' != 0)  // 自己组成新数的前提
            {
                for(int j = 1;j <= 10; ++j)
                {
                    dp[i][1] = (dp[i][1] + dp[i - 1][j]) % MOD;
                }
            }
            
            for(int j = 1;j <= 10; ++j)  // 和前面链接,枚举所有 可能 的 j ,看看那种情况可以转移过来
            {
                if(i - 1 < j) break;
                if(!check(s, k, i - 2, j)) continue;
                dp[i][j + 1] = (dp[i][j + 1] + dp[i - 1][j]) % MOD;
            }
        }

        // 最后答案,因为我们要 n 长度,但是最后一个位置,我们不知道他到底是自己,还是和前面链接
        // 但是这些都符合要求,所以最后答案,是全部加起来。
        int ans = 0;
        for(int j = 1;j <= 10; ++j) ans = (ans + dp[n][j]) % MOD;

        return ans;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值