1177. Can Make Palindrome from Substring

在这里插入图片描述

前缀和的题目。
先吐槽一下自己,之前刷题就是喜欢做会的,不会的就喜欢略过。但是这样基本提高不了啊。这几天基本只做不会的,结果提高了很多。以后还是得这样。基础题需要掌握,但不需要反复做。

这一题需要解决两个事情:
1.给一段子串,在题目允许范围内需要换多少次?这个不难,统计出各个字符的个数,偶数的消掉。留下单个的,统计有多少个。结果是cnt/2.因为奇数可以把一个放中间,就不用换了。
2.解决这一题频繁查询的问题:因为这一题需要反复查询不同的区间段。这很浪费时间,如果查询一次就统计一次这段之内的字符数的话。这时候就需要想到前缀和。

class Solution {
public:
    vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {
        int sz = s.size();
        vector<vector<int>> cnts(sz+1);
        cnts[0] = vector<int>(26, 0);
        for (int i = 0; i < sz; ++i) {
            cnts[i+1] = cnts[i];
            cnts[i+1][s[i] - 'a']++;
        }
        int left, right, k;
        vector<bool> ret;
        for (auto& query : queries) {
            left = query[0];
            right = query[1];
            k = query[2];
            int cnt = 0;
            for (int i = 0; i < 26; ++i) {
                if ((cnts[right+1][i] - cnts[left][i]) & 0x1)
                    ++cnt;
            }
            ret.push_back(k >= (cnt / 2));
        }
        return ret;
    }
};

前缀和:
想到它的时候应该是:需要频繁计算一个数组中某一段连续区间中的某种信息。如果只计算一次或者几次,直接计算就行了。但是如果需要计算很多次不同的区间的话,用前缀和是最好的,一次计算,然后每次区间都用末尾减去开头就行了。

需要记住这种思考路线。

ok,上面是基本思路,做到这就差不多了,但是还有一个骚方法。

我们思考一下,我们看一段区间内的不同字符的个数就是为了得到区间中字符个数为奇数的个数。我们可以直接使用bool来指示从开头到这儿这个字符出现了奇数个还是偶数个。如果一段区间的开头和结尾处这个字符的出现属性是相同的(都为奇数或者都为偶数),说明区间内出现了偶数个;否则为奇数个。
再激进一些,甚至不要bool的数组,因为说了都是小写字母,最多26个,所以可以用数字来代替bool的数组,用1和0代表奇偶性。

class Solution {
public:
    vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {
        int mask = 0;
        vector<int> cnts{0};
        for (char c : s) {
            mask ^= (1 << (c - 'a'));
            cnts.push_back(mask);
        }
        int left, right, k;
        vector<bool> ret;
        for (auto& query : queries) {
            left = query[0];
            right = query[1];
            k = query[2];
            int cnt = helper(cnts, left, right);
            ret.push_back(k >= (cnt/2));
        }
        return ret;
    }
private:
    int helper(vector<int>& cnts, int left, int right) {
        int ln = cnts[left];
        int rn = cnts[right+1];
        int ret = 0;
        while (ln || rn) {
            if ((ln ^ rn) & 0x1)
                ++ret;
            ln >>= 1;
            rn >>= 1;
        }
        return ret;
    }
};

虽然不是很必要,但是在面试时候说出这种想法的话,应该很加分。

类似其他思考路线:
把具有相同或者类似属性或者有关联关系的元素放到一块:并查集。
字符串的前缀相关:Trie
待补充

posted @ 2019-09-11 11:40  于老师的父亲王老爷子  阅读(20)  评论(0)    收藏  举报