一周Hard (2021.11.29-2021.12.05)

这篇博客探讨了几种常见的数组处理问题,包括寻找和至少为K的最短子数组、串联所有单词的子串、计算右侧小于当前元素的个数以及寻找丑数III。博主通过讲解算法思路,如单调队列、双指针和二分查找,展示了如何高效地解决这些问题。此外,还涉及到了公交路线的最短路径计算,展示了图的搜索算法应用。
摘要由CSDN通过智能技术生成

862. 和至少为 K 的最短子数组
先从朴素的思想去考:
枚举当前的前缀和 k k k,那么我们需要找到当前位置之前的满足差大于等于 k k k的最大位置。

i < j i<j i<j,且 p r e [ i ] ≥ p r e [ j ] pre[i]\geq pre[j] pre[i]pre[j],那么 p r e [ c u r ] − p r e [ i ] ≥ k pre[cur]-pre[i]\geq k pre[cur]pre[i]k,必然有 p r e [ c u r ] − p r e [ j ] ≥ k pre[cur]-pre[j] \geq k pre[cur]pre[j]k i i i就没有存在的必要了。

就是说,如果一个人比你小(更靠近当前枚举的位置),还比你强(与当前前缀和作差得到更大的值),那你就没存在的必要了(doge)

维护一个递增的单调队列,每次从队头遍历所有 p r e [ c u r ] − p r e [ j ] ≥ k pre[cur]-pre[j]\geq k pre[cur]pre[j]k j j j,并且同时弹出队头,因为与 p r e [ c u r ] − p r e [ j ] ≥ k pre[cur]-pre[j]\geq k pre[cur]pre[j]k j j j,可以构成符合条件的最小子数组的右边界就是 c u r cur cur

class Solution {
public:
    int shortestSubarray(vector<int>& nums, int k) {
        int n = nums.size();
        vector<long long> pre(n, -(1ll << 60));
        vector<int> q(n);
        int hh = 0, tt = -1;
        int res = 0x3f3f3f3f;
        
        for(int i = 0; i < n; ++i) {
            pre[i] = nums[i];
            if(i > 0) pre[i] += pre[i - 1];
			if(pre[i] >= k) res = min(res, i + 1);
            
            while(hh <= tt && pre[q[tt]] >= pre[i]) tt -= 1;
            
			while(hh <= tt && pre[i] - pre[q[hh]] >= k) { 
	            res = min(res, i - q[hh]);
                hh += 1;
            } 
            
            q[++tt] = i;
        }
        return res == 0x3f3f3f3f ? -1 : res;
    }
};

30. 串联所有单词的子串
本题需要找到所有覆盖掉 w o r d s words words数组的 s s s中的子串
可以考虑枚举起点,因为 w o r d s words words数组中每个元素的长度一样,考虑枚举起点为 [ 0 , w o r d s [ 0 ] . s i z e ( ) ) [0,words[0].size()) [0,words[0].size())
这样可以有双指针去操作,当每次双指针判断后长度恰好为 w o r d s [ 0 ] . s i z e ( ) ∗ w o r d s . s i z e ( ) words[0].size()*words.size() words[0].size()words.size(),那么就找到了这样一个子串,记录即可。

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        int len = words[0].size();
        int cnt = words.size();
        int total = cnt * len;
        int n = s.size();
        
        vector<int> ans;
        unordered_map<string, int> act;
        for(auto& u : words) act[u] += 1;
        
        for(int i = 0; i < len; ++i) {
            unordered_map<string, int> cur; 
            int l = i, r = i + len;
            while(r <= n) {
                string tword = s.substr(r - len, len);
                if(act.count(tword)) {
                    int max_tword = act[tword];
                    auto& cur_tword = cur[tword];
                    if(cur_tword < max_tword) cur_tword += 1;
                    else {
                        while(cur_tword >= max_tword && l < r) {
                            cur[s.substr(l, len)] -= 1;
                            l += len;
                        } 
                        cur_tword += 1;
                    }
                } else {
                    cur.clear();
                    l = r;
                }
                
                if((r - l) / len == cnt) ans.push_back(l);
                
                r += len;
            }
        }
        
        return ans;
    }
};

315. 计算右侧小于当前元素的个数
树状数组求逆序对或者归并求逆序对两种解法都可,下面是树状数组。
如果是用树状数组,可以采用对应的离散化操作进行即可。

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        int base = 10001;
        int maxv = base << 1 | 1;
        vector<int> tr(maxv, 0);
        vector<int> res(n, 0);
        
        auto get = [&tr](int p) -> int {
            int sum = 0;
            while(p > 0) {
                sum += tr[p];
                p -= p & -p;
            }
            return sum;
        };
        
        auto add = [=, &tr](int p) {
            while(p < maxv) {
                tr[p] += 1;
                p += p & -p;
            }
        };
        
        for(int i = n - 1; i >= 0; --i) {
            res[i] = get(nums[i] + base - 1);
            add(nums[i] + base);
        }
        
        return res;
    }
};

1201. 丑数III
标着是mid,但是思维难度应该是hard,但是实际做法难度也就是mid难度,与丑数I 和丑数II并不同。
因为 n ≤ 2 × 1 0 9 n\leq 2\times 10^9 n2×109,所以线性做法也不可以。
考虑二分答案,每次 c h e c k check check当前值之前是否有 n n n个即可。

class Solution {
public:
    
    long long gcd(long long a, long long b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    
    int nthUglyNumber(int n, int _a, int _b, int _c) {
        long long a = _a;
        long long b = _b;
        long long c = _c;
        long long ab = a / gcd(a, b) * b;
        long long bc = b / gcd(b, c) * c;
        long long ac = a / gcd(a, c) * c;
        long long abc = ab / gcd(ab, c) * c;
        
        long long l = min({a, b, c}), r = 2e9;
        while(l < r) {
            long long mid = l + r >> 1;
            if(mid / a + mid / b + mid / c - mid / ab - mid / ac - mid / bc + mid / abc >= n) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

815. 公交路线

#define sz(x) (int)x.size()
class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& r, int s, int t) {
        if(s == t) return 0;
        int n = r.size();
        vector<vector<bool>> g(n, vector<bool>(n, false));
        vector<int> dist(n, 0x3f3f3f3f);

        for(auto &u : r) sort(u.begin(), u.end());

        auto check = [](vector<int> &A, vector<int> &B) {
            int i = 0, j = 0;
            while(i < sz(A) && j < sz(B)) {
                if(A[i] == B[j]) return true;
                if(A[i] < B[j]) ++i; else ++j;
            }
            return false;
        };

        for(int i = 0; i < n; ++i) 
            for(int j = i + 1; j < n; ++j)
                if(check(r[i], r[j])) g[i][j] = g[j][i] = 1;

        queue<int> q;
        for(int i = 0; i < n; ++i) {
            auto it = lower_bound(r[i].begin(), r[i].end(), s);
            if(it != r[i].end() && *it == s) q.push(i), dist[i] = 1;
        }

        while(!q.empty()) {
            int u = q.front(); q.pop();
            for(int i = 0; i < n; ++i) {
                int v = i;
                if(g[u][v] && dist[v] > dist[u] + 1) {
                    dist[v] = dist[u] + 1;
                    q.push(v);
                }
            }
        }

        int res = 0x3f3f3f3f;
        for(int i = 0; i < n; ++i) {
            auto it = lower_bound(r[i].begin(), r[i].end(), t);
            if(it != r[i].end() && *it == t) res = min(res, dist[i]);
        }

        if(res == 0x3f3f3f3f) res = -1;
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值