LeetCode 第199场周赛题解报告

5472. 重新排列字符串

知识点:模拟
先创建一个等长的字符串,然后按顺序对其更新即可。

class Solution {
public:
    string restoreString(string s, vector<int>& indices) {
        string str = s;
        for(int i = 0; i < s.size(); i++) {
            str[indices[i]] = s[i];
        }
        return str;
    }
};

5473. 灯泡开关 IV

知识点:思维题
首先找到第一个和目标状态不一样的灯泡,然后改变其和其后所有灯泡的状态。然后重复前述步骤,直到所有灯泡都到达目标状态。

接下来用我拙劣的语言来说明一下该算法是收敛的:
虽然每次操作可能会让后面的灯泡不符合目标状态,但是可以保证当前被选中的灯泡变成目标状态的且不会反复。所以可以保证最终所有灯泡都会变成合法的。

再用我拙劣的的语言来说明下这样做是最优的:
设第一个不合法的灯泡的下标为 i,那么必须对 i 进行操作。原因如下:

  • 原因一:对 j (j>i)进行操作,不会影响 i。
  • 原因二:对 j (j < i) 进行操作,会使得 j 变为不合法,那就意味着要对 j 操作两次。但对 j 操作两次意味着 i 的状态不会改变。

综上,必须要操作 i。当对 i 操作之后,要么全都达到了目标状态,要么产生了一个新的 i。也就是说对于每一种初始状态,其中要被操作的那些灯泡(姑且称之为关键灯泡)就已经被确定了。无论你按什么样的次序操作,这些关键灯泡都是要操作的,而对其他灯泡的操作都是冗余的(如原因二所述),所以这样操作是最优的。

class Solution {
public:
    int minFlips(string target) {
        int anw = 0;
        bool status = false;
        for(int i = 0; i < target.size(); i++) {
            if(target[i] == '0') {
                if(status == false) {
                    continue;
                } else {
                    anw ++;
                    status = false;
                }
            } else {
                if(status == true) {
                    continue;
                } else {
                    anw ++;
                    status = true;
                }
            }
        }
        return anw;
    }
};

5474. 好叶子节点对的数量

知识点:深度优先遍历
先来说个前提:二叉树中,两个节点的最近路径是唯一的,且必然会经过两个节点的最近公共祖先。

最近公共祖先(LCA):设有节点F,A,B,如果F即是A的祖先,又是B的祖先,那么F是A,B的公共祖先。在所有符合上述要求的F中,深度最大的那个即为A,B的最近公共祖先。

设以节点 F 为LCA的好叶子节点对为 C(F)。那么最终的答案即为所有C(F)的累加和。那如何求解C(F)呢?
首先分别左右子树中到F的距离为 x(1 <= x <= distance) 的叶子节点的数量。
然后将枚举距离将左右子树的叶子节点配对,统计其中符合距离要求的点对即可。
拙劣的 证明:
因为每对叶子节点的LCA是唯一的,且LCA必定存在,所以通过LCA来区分点对既不会重复也不会漏掉。

/**
 * 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:
	// cntDict::key 是节点的内存地址,用作一个节点的id。
	// cntDict::value 也是一个 map, 其 value 为到 cntDict::key 距离为 key 的数量。
    map<ptrdiff_t, map<int, int>> cntDict;
    int dfs(TreeNode *root, int distance) {
        if(root == nullptr) {
            return 0;
        }
        int anw = 0;
        
        anw += dfs(root->left, distance); // 累加右子树中点对的数量
        anw += dfs(root->right, distance); // 累加左子树中点对的数量
        
        //左右节点都存在,那么必有以 root 为LCA 的点对,统计其中符合要求的叶子点对数量。
        if(root->left != nullptr && root->right != nullptr) {
            const map<int, int> &left = cntDict[ptrdiff_t(root->left)];
            const map<int, int> &right = cntDict[ptrdiff_t(root->right)];
            
            for(int i = 0; i < distance; i++) {
                auto lit = left.find(i);
                if(lit == left.end()) { continue; }
                for(int j = 0; j < distance; j++) {
                    if(i+j+2 > distance) {
                        continue;
                    }
                    auto rit = right.find(j);
                    if(rit == right.end()) {
                        continue;
                    }
                    anw += lit->second * rit->second;
                }
            }
        }
        
        map<int, int> cnt;
        if(root->left != nullptr) {
            const map<int, int> &left = cntDict[ptrdiff_t(root->left)];
            for(auto it = left.begin(); it != left.end(); ++it) {
                if(it->first + 1 >= distance) { continue; }
                cnt[it->first+1] += it->second;
            }
        }
        if(root->right != nullptr) {
            const map<int, int> &left = cntDict[ptrdiff_t(root->right)];
            for(auto it = left.begin(); it != left.end(); ++it) {
                if(it->first + 1 >= distance) { continue; }
                cnt[it->first+1] += it->second;
            }
        }
        
        if(root->left == nullptr && root->right == nullptr) {
            cnt[0] = 1;
        }
        cntDict.insert(make_pair(ptrdiff_t(root), cnt));
        return anw;
    }
    
    int countPairs(TreeNode* root, int distance) {
        return dfs(root, distance);
    }
};

5462. 压缩字符串 II

知识点:动态规划

这个题写的相当挫。。

既然是动态规划,那么先来确定状态:
设 s 的长度为n,下标从 1 开始。
f(i,j,d) 为以 s[i] 结尾,且末尾有 j 个连续的 s[i],且删了 d 个字符的最小长度。
当我们向 s 末尾追加一个 s 中不存在的字符(比如 #),那么答案就是 f(n+1, 1, k) - 1。减一是为了移除追加的特殊字符。

状态确定了,再来看如何转移:
先把状态摆在这里:

  • i: s[i]为最后一个字符
  • j:末尾是 j 个连续的 s[i]。
  • d: 总共删除了 d 个字符。
    当 s[i] 为最后一个字符时,意味了其后的所有字符都被删除了,设其后字符数量为 deleted。
    然后枚举s[i]之前删除字符的数量为 part,part 和 deleted 满足 0 <= part 且 part + deleted <= k。
    这样就得到了可以转移到f(i,j,k)的状态 f(i-part-1, con, k - part-deleted)。con 需满足 0 <= con 且 con <= i-part-1。
    然后通过讨论 s[i] 和 s[i-part-1] 是否相同,可得到从 f(i-part-1, con, k-part-deleted) 转移到 f(i,j,d)的计算公式。
class Solution {
public:
    int dp[102][101][101];
    int get(int con) {
        // con == 1, 说明要从 x 变为 x2
        // con == 9, 说明要从 x9 变为 x10
        // con == 9, 说明要从 x99 变为 x100
        return (con == 1 || con == 9 || con == 99) ? 1 : 0;
    }
    int getLengthOfOptimalCompression(string s, int k) {
        if(k >= s.size()) {
            return 0;
        }
        s += "#";

        memset(dp, 0x7f, sizeof(dp));

        dp[0][0][0] = 0;
        
        // 枚举最后一个字符的下标。
        for(int now = 1; now <= s.size(); now++) {
        	// 枚举 now 之前保留的最后一个字符 pre。
            for(int pre = now-1; pre >= 0; pre--) {
                if(now-pre-1 > k) {
                    break;
                }
                // 枚举 pre 之前删除了 deleted 个字符。
                for(int deleted = 0; deleted <= k; deleted++) {
                    int allDeleted = deleted + now-pre-1;
                    if(allDeleted > k) {
                        break;
                    }
                    // 枚举连续的 s[pre] 的个数
                    for(int con = 0; con <= pre; con++) {
                        if(dp[pre][con][deleted] > 100) {
                        	// 大于 100 说明该状态不可达。
                            continue;
                        }
                        // 转移
                        if(pre != 0 && s[pre-1] == s[now-1]) {
                            dp[now][con+1][allDeleted] = min(dp[now][con+1][allDeleted], dp[pre][con][deleted] + get(con));
                        } else {
                            dp[now][1][allDeleted] = min(dp[now][1][allDeleted], dp[pre][con][deleted] + 1);
                        }
                    }
                }
            }
        }
        return dp[s.size()][1][k] - 1;
    }
};

LeetCode 第199场周赛题解报告_第1张图片

你可能感兴趣的:(题解给力)