LeetCode 力扣周赛 203

88 阅读2分钟

知识点:计数;排序; 因为数据量较小,可以直接暴力统计经过每个扇区的次数。然后选取此时最大的扇区即可。 (数据量较大时,可用线段树等区间查询技术来优化~)

class Solution {
public:
    vector<int> mostVisited(int n, vector<int>& rounds) {
        int cnt[101] = {0};
        cnt[rounds[0]]++; // 因为只有round[0] 的起点需要计数,所以单独统计一下。
        
        for(int i = 0, pos = 0; i+1 < rounds.size(); i++) {
            int b = rounds[i];
            int e = rounds[i+1];
            while(b != e) {
                if(++b > n) {
                    b = 1;
                }
                cnt[b]++;
            }
        }
        
        vector<int> vec;
        for(int i = 1; i <= n; i++) {
            vec.push_back(i);
        }
        
        stable_sort(vec.begin(), vec.end(), [cnt](int l, int r) -> bool {
            return cnt[l] > cnt[r];
        });
        
        
        for(int i = 1; i < n; i++) {
            if(cnt[vec[i]] != cnt[vec[0]]) {
                vec.resize(i);
                break;
            }
        }
        return vec;
    }
};

知识点:贪心;排序 因为每轮选择中,Bob 总是选择最少的一堆。所以 Bob 得到最少的 n 堆是对其他两个人最有利的局面。

剩下的 2n 堆该如何分配呢? 每次选择最大的两堆,Alice 获得其中较大的,我获得其中较小的。 虽然这样选择,Alice 还是能获得剩余的里面最大的一堆,但是可以让剩余堆中的最大值最小

class Solution {
public:
    int maxCoins(vector<int>& piles) {
        sort(piles.begin(), piles.end(), [](int l, int r) -> bool {
            return l > r;
        });
        
        int anw = 0;
        
        for(int i = 0; i < piles.size()/3; i++) {
            anw += piles[i*2+1];
        }
        return anw;
    }
};

知识点:思维题;瞎搞;

每操作一次,新增的 1 可能会有如下三种情况:

  • 左右都是 0。此时该位置作为新增段独立存在
  • 仅有左边或者右边。此时该位置会将某个旧段的长度加 1
  • 左右都是 1。此时该位置会将两个旧段合并成一个新段

我们现在维护一个字典 M,M 的 key 表示段的长度,value 表示在字符串中,长度为 key 的段的数量。初始时,M 中无记录。接下来,让我们看下上述三种情况分别对 M 造成了哪些变化。

  • 情况一:新增了一个长度为 1 的段。M[1] += 1。
  • 情况二:删除一个长度为 L 的段,增加一个长度为 L+1的段。M[L] -= 1;M[L+1] += 1;
  • 情况三:删除两个长度分别为 X,Y的段,增加一个长度为 X+Y+1 的段。 M[X] -= 1;M[Y] -= 1;M[X+Y+1] += 1;

然后记录一下最后一次使 M[m] 不为零的操作即可。

另外,还有一个重要问题,如何确定被删除段的长度呢

设有一个数组 link,当 arr[i] 为某个段的端点时,link[i] 才有意义,其值代表另一个端点的位置。

接下来,上述三种情况如何更新 link。

  • 情况一:因为长度为 1,所以 link[i] = i;

  • 情况二:加入新增点成为某个旧段的右端点; 则被删段长度为 (i-1) - link[i-1] + 1;link[link[i-1]] = i,link[i] = link[i-1]。 为左端点时同理,机智的老铁们可自行推导。

  • 情况三:左删除段长度为:(i-1) - link[i-1] + 1; 有删除段长度为:link(i+1) - (i+1) + 1; 更新:link[link[i-1]], link[link[i+1]] = link[i+1], link[i-1]。

另外,实现时根本不需要M,因为我们只关心长度为 m 的段的数量~。

int link[100001] = {0};

class Solution {
public:
    int findLatestStep(vector<int>& arr, int m) {
        int cnt = 0;
        memset(link, -1, sizeof(link));
        int anw = -1;
        for(int i = 0; i < arr.size(); i++) {
            int pos = arr[i] - 1;
            link[pos] = pos;
            int L = pos, R = pos;
            if(0 < pos && link[pos-1] != -1) {
                if(pos-1 - link[pos-1] + 1 == m) {
                    cnt--;
                }
                L = link[pos-1];
            }
            if(pos+1 < arr.size() && link[pos+1] != -1) {
                if(link[pos+1] - (pos+1) + 1 == m) {
                    cnt--;
                }
                R = link[pos+1];
            }
            
            link[L] = R;
            link[R] = L;
            
            if(R-L+1 == m) {
                cnt++;
            }
            if(cnt > 0) {
                anw = i+1;
            }
        }
        return anw;
    }
};

知识点:递归;记忆化;前缀和 设 f(L,R) 为在 stoneValue[L:R] 这一排石子上可获得最大分数; 显然,f(1,N) 即为答案。那么 f(1,N) 咋求呢?递归呀~

  • 终止条件:显然 L == R 时,可直接获得答案,为 0。那就把 L == R 作为终止条件。
  • 递进阶段:从 L 到 R 枚举分割策略,然后根据题意保留 f(L,i) + sum(L,i),或者 f(i+1,R) + sum(i+1, R)中的最大值。
  • 回归阶段:利用递进阶段获得的最优解,更新f(L,R)。

另外,因为求解过程中,会产生重复的子问题,所以需要通过记忆化的方法避免重复计算。

class Solution {
public:
    int64_t dp[501][501]; // 记忆化数组,用于避免重复计算
    int64_t sum[501];
    
    int64_t dfs(int L, int R) {
        if(dp[L][R] != -1) { //已经计算过该子问题了,直接范围答案
            return dp[L][R];
        }
        if(L == R) { // 终止条件,直接获得答案
            dp[L][R] = 0;
        } else {
            //递进阶段,根据题意,求解最大值;
            int64_t val = 0;
            for(int i = L; i< R; i++) {
                int64_t s1 = sum[i] - sum[L-1];
                int64_t s2 = sum[R] - sum[i];
                
                if(s1 < s2) { // 根据题意,只能取后半段
                    val = max(val, s1 + dfs(L, i));
                } else if(s1 > s2){ // 根据题意,只能取前半段
                    val = max(val, s2 + dfs(i+1, R));
                } else { // 相等时,可任意选择~
                    val = max(dfs(L, i), dfs(i+1, R)) + s1;
                }
            }
            //回归阶段,更新答案
            dp[L][R] = val;
        }
        return dp[L][R];
    }
    
    int stoneGameV(vector<int>& stoneValue) {
        memset(dp, -1, sizeof(dp));
        
        //出来一下前缀和
        sum[0] = 0;
        for(int i = 0; i < stoneValue.size(); i++) {
            sum[i+1] = sum[i] + stoneValue[i];
        }
        
        return dfs(1, stoneValue.size());    
    }
};