LeetCode 第201场周赛题解报告

5483. 整理字符串

知识点:栈
将待整理字符串 s 的字符一次放到栈顶,每放入一个字符后就对栈进行检查:

  • 栈是否有两个或更多元素
  • 栈顶的两个元素是否是对应的大小写字符。

如果栈满足上述两个条件,则弹出栈顶的两个元素。
最后栈内的字符即为答案~

class Solution {
public:
    string makeGood(string s) {
        string stack;
        for(int i = 0; i < s.size(); i++){
            stack += s[i];
            int n = stack.size();
            if(n >= 2 && (stack[n-1] + 32 == stack[n-2] || stack[n-1] - 32 == stack[n-2])) {
                stack.resize(n-2);
            }
        }
        return stack;
    }
};

5484. 找出第 N 个二进制字符串中的第 K 位

知识点:预处理
因为只会对前 20 个 S 进行询问,所以可以先预处理出S1,S2,S2 … S20。这样对于每次询问就可以O(1)的给出答案了~

// 全区变量,用于存储 S1,S2, ..., S20
std::vector<string> seq;

class Solution {
public:
    string reverse(const string &str) {
        string anw = str;
        for(int i = str.size()/2; i >= 0; --i) {
            std::swap(anw[i], anw[str.size()-i-1]);
        }
        return anw;
    }
    string invert(string s) {
        for(int i = 0; i < s.size(); i++) {
            if(s[i] == '1') {
                s[i] = '0';
            } else {
                s[i] = '1';
            }
        }
        return s;
    }
    Solution() {
        // 如果seq 非空,说明已经处理过了。
        if(seq.empty() == false) {
            return;
        }
        // 按题目描述初始化 seq
        seq.push_back("0");
        for(int i = 1; i < 20; i++) {
            seq.push_back(seq[i-1] + "1" + reverse(invert(seq[i-1])));
        }
    };
    char findKthBit(int n, int k) {
        // 以O(1)的时间复杂度给出答案
        return seq[n-1][k-1];
    }
};

5471. 和为目标值的最大数目不重叠非空子数组数目

知识点:动态规划

设 dp[i] 表示前 i 个数字中,满足要求的子数组个数。
当 i = 0 时,显然有 dp[0] = 0。
当 i > 0 时,有两种情况:

  • 如果存在 j 使得 sum(nums[j] … nums[i-1]) 等于 target,那么:
    • dp[i] 有可能为 dp[j-1] + 1。找到了一个以第 i 个数字为右端点的合法子数组。
    • dp[i] 有可能为 dp[i-1]。以第 i 个数字为右端点的子数组虽然累加和为 target,但它太长了,被最优解嫌弃了。
    • 综上,dp[i] = max(dp[i-1], dp[j-1] + 1)。
  • 如果不存在这样的 j,那么 dp[i] 只能是 dp[i-1],因为第 i 个数组没有做出任何贡献。

还有一种情况,如果存在多个满足条件的 j 呢?显然要选最大的,因为 dp 是单调非降的,j 越大,则 dp[j] 也可能越大,则 dp[j-1] + 1 更有可能大于 dp[i-1]。

class Solution {
public:
    unordered_map<int, int> pos_dict; // key 为 前缀和,value 为最大的位置
    int dp[100001]; // 前 i 个数字中,合法子数组的最大数量
    int maxNonOverlapping(vector<int>& nums, int target) {
        dp[0] = 0;
        pos_dict[0] = 0;
        for(int i = 1, sum = 0; i <= nums.size(); i++) {
            int data = nums[i-1];
            sum += data;
            // 寻找前缀和为 sum - target 的位置。
            auto it = pos_dict.find(sum - target);
            if(it == pos_dict.end()) {
                // 没找到,则只有一种策略
                dp[i] = dp[i-1];
            } else {
                // 找到了,选择一种更优的策略。
                dp[i] = max(dp[i-1], dp[it->second] + 1);
            }
            // 更新一下 pos_dict。因为 value 是最大的位置。
            pos_dict[sum] = i;
        }
        return dp[nums.size()];
    }
};

5486. 切棍子的最小成本

知识点:动态规划
设 dp[L][R] 为切割以L,R为左右端点的木棍的最小成本。显然 L,R 的取值只能是 0,n,或者 cuts中的某个数
如果不存在 i 满足 L < cuts[i] < R,那么dp[L][R] = 0,因为根本不用切了。
如果存在 i 满足上述条件,则 dp[L][R] = min(dp[L][cuts[i]] + dp[cuts[i]][R]) + R-L。

class Solution {
public:
    // 为了方便实现,
    // dp[l][r] 代表 切割以 cuts[l], cuts[r] 为左右端点的木棍的最小花费
    int dp[103][103];
    int dfs(int l, int r, int n, const vector<int>& cuts) {
        // 保存已经计算过的答案,避免子问题的重复计算
        if(dp[l][r] != -1) {
            return dp[l][r];
        }
        // l+1 == r 时,说明不用再切了。
        if(l+1 == r) {
            dp[l][r] = 0;
            return 0;
        }
        // 枚举切割的地方,记录最优解。
        for(int i = l+1; i < r; i++) {
            int cost = dfs(l, i, n, cuts) + dfs(i, r, n, cuts) + cuts[r] - cuts[l];;
            if(dp[l][r] == -1 || dp[l][r] > cost) {
                dp[l][r] = cost;
            }
        }
        return dp[l][r];
    }
    
    int minCost(int n, vector<int>& cuts) {
        memset(dp, -1, sizeof(dp));
        cuts.push_back(0);
        cuts.push_back(n);
        sort(cuts.begin(), cuts.end());
        // 向 cuts 中加入 0 和 n。
        // 这不会影响答案,因为我们不会从这两处切割。
        return dfs(0, cuts.size()-1, n, cuts);
    }
};

猜你喜欢

转载自blog.csdn.net/Time_Limit/article/details/107891480