【每日一题】23年4月

C++ 技术点

1. string类型使用find函数。
int index = s.find("@");
if (inde != string:npos){
xx
}

2. transform函数,将整个字符串做整体改变。
transform(s.begin(), s.end(), s.begin(), ::tolower);

多边三角形剖分的最低得分(dp思路,选不选问题)

在这里插入图片描述
在这里插入图片描述

  • dp定义:dp[i][j]表示点 i 到点 j 的多边形的最小值。
  • dp转移:转移方程的思路两种。选不选、选哪个。现在本题只有选哪个这个思路。因此dp[i][j] = min(dp[i][j], dp[i,k]+dp[k,j]+cal(i,j,k))
  • 枚举次序:求解dp[i][j]的时候是需要提前知道dp[i][k](k<j)这个更小的问题的,因此此时 j 是正序枚举;同样的对于dp[k][j]则是需要知道 k 才能知道 i 的,因此 i 是倒序枚举
  • dp初始值:在枚举次序分析以后,我们知道dp[i][i],dp[i][i+1] = 0, 其他的初始化为INT_MAX;
class Solution {
public:
    int minScoreTriangulation(vector<int> &v) {
        int n = v.size(), f[n][n];
        memset(f, 0, sizeof(f));
        for (int i = n - 3; i >= 0; --i)
            for (int j = i + 2; j < n; ++j) {
                f[i][j] = INT_MAX;
                for (int k = i + 1; k < j; ++k)
                    f[i][j] = min(f[i][j], f[i][k] + f[k][j] + v[i] * v[j] * v[k]);
            }
        return f[0][n - 1];
    }
};


移动石子到连续(思路)

在这里插入图片描述

比较有思维量的题目。首先我们需要思考下最终的结果是处于什么区间上的,也就是我们需要选在一个长度为 k 的区间最后将所有的石子搬运到这个区间上。

另外我们还需要思考,如何实现最大和最小的移动。

  • 最大:以左右端点作为其中一个端点进行移动。这两个点之间的所有端点都是需要一次操作的。
  • 最小:首先设计一个滑动窗,窗的两侧为实际存在的石子,并且左右两侧的石子的距离是小于 n 的最大距离。此时我们可以分情况讨论:
    • 如果左右端点差为n,完全不需要动,返回0;
    • 两个端点的距离差[stone[l], stone[r] ]恰好为n-1,石子数量也为n-1,也就是n-1个全部都是连续的。此时外面还剩一个,无论在什么位置,最多两次就可以连续。(认为右侧不动)
    • 否则,填上中间的空挡即可。
class Solution {
public:
    vector<int> numMovesStonesII(vector<int>& stones) {
        int n = stones.size();
        sort(stones.begin(), stones.end());
        // max = max(stones[n-2] - stones[0] -1, stones[n-1]-stones[1]-1)
        // min = 在一个滑动窗内 k个,因此需要n-k次 
        // 如果有n-1个是紧密相连的。那么 需要两次

        if(stones[n-1]- stones[0]+1 == n){
            return {0, 0};
        }

        int max_v = max(stones[n-2] - stones[0] +1, stones[n-1]-stones[1]+1)-(n-1);
        int min_v = max_v;
        int r = 1;
        for (int l = 0;l<n;l++){
            while(r < n && stones[r]-stones[l]+1 <= n){
                r++;
            }
            r--;
            // 左端点l到右端点r是连续的
            if (r-l+1 == n-1 && stones[r]-stones[l]+1 == n-1){
                min_v = min(min_v, 2);
            }else{
                min_v = min(min_v, n-(r-l+1));
            }
        }
        return {min_v, max_v};
    }
};

1027. 最长等差数列(动态规划)

在这里插入图片描述

最长子序列的问题很容易想到dp问题。选哪个问题

  • dp定义:dp[i][j] 长度为 i 时,差值为 j 时的最大长度
  • dp转移:dp[i][j] = max(dp[k][j]+(k,i)*maxValue, dp[i][j])
  • dp初始值:0
  • dp转移顺序:正序遍历 i ,反向遍历 k
class Solution {
public:
    int longestArithSeqLength(vector<int> &a) {
        int ans = 0, n = a.size(), f[n][1001];
        memset(f, 0, sizeof(f));
        for (int i = 1; i < n; ++i)
            for (int j = i - 1; j >= 0; --j) {
                int d = a[i] - a[j] + 500; // +500 防止出现负数
                if (f[i][d] == 0) {
                    f[i][d] = f[j][d] + 1; // 默认的 1 在下面返回时加上
                    ans = max(ans, f[i][d]);
                }
            }
        return ans + 1;
    }
};

1105. 填充书架(动态规划)

在这里插入图片描述
选不选?选哪个? 选哪个问题

  • dp定义:dp[i]表示前 i 本 书的最小高度。
  • dp转移:dp[i] = min(dp[i], dp[j]+cal(j,i))。对于后 j 到 i 的书我们放在一个全新的书架上。
  • dp状态:初始化为INT_MAX,但是dp[0] = 0;
class Solution {
public:
    int minHeightShelves(vector<vector<int>>& books, int shelfWidth) {
        // dp[i] 表示前i本书,总的高度
        // dp[i] 选什么?当前层还是下一层? X 
        // 选哪个?从哪个开始放置在新的一层? ✅
        int n = books.size();
        vector<int> dp(n+1, INT_MAX);
        dp[0] = 0; // 初始化为0
        for (int i = 1;i<=n;i++){
            int curmax = 0;
            int curlength = 0;
            for (int j = i;j>=1;j--){ // 选择某一个点,这个点之后都放在某一层
                curlength += books[j-1][0];
                // 超出了就停止,此时无法都放在一层
                if(curlength > shelfWidth) break;
                curmax = max(curmax, books[j-1][1]);
                dp[i] = min(dp[i], dp[j-1]+curmax);
            }
        }
        return dp[n];
    }
};

1031 两个非重叠子数组的最大和

在这里插入图片描述
依然会有一点类似滑动窗口的思路,我们枚举second的起始位置 i ,可以表示firstLen的选取在[0,i]之间实现,second就是[i+1, i+secondLen]。 对于firsr 和 second可以交换前后的情况,我们可以分两种情况讨论即可。

class Solution {
public:
    int help(vector<int>& nums, int firstLen, int secondLen) {

        // dp[i] 表示前i个元素时,first数组的最大值,
        // ans = dp[i]+sum(nums(i+1, i+secondLen))
        int suml = accumulate(nums.begin(), nums.begin() + firstLen, 0);
        int maxSumL = suml;
        int sumr = accumulate(nums.begin() + firstLen, nums.begin() + firstLen + secondLen, 0);
        int res = maxSumL + sumr;
        // i表示second的结尾位置,j表示first的结尾位置
        for (int i = firstLen + secondLen, j = firstLen; i < nums.size(); ++i, ++j) {
            suml += nums[j] - nums[j - firstLen];
            maxSumL = max(maxSumL, suml);
            sumr += nums[i] - nums[i - secondLen];
            res = max(res, maxSumL + sumr);
        }
        return res;
    }

    int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
    // 分两种情况分别讨论
        return max(help(nums, firstLen, secondLen), help(nums, secondLen, firstLen));
    }
};

1163.按字典序排在最后的子串(双指针,反证法)

在这里插入图片描述

这个题目的思路很独特,不知道更深层的思想是什么。首先有一个证明,答案一定是后缀字符串。只是我们要去选择这个开头的位置。因此我们可以比较不同的开头。

我们维护一个双指针,假设 i 是最佳的答案,我们尝试在 i 的后面寻找到一个 j 去证明 i 不是最好。


class Solution {
public:
    string lastSubstring(string s) {
        int n = s.size();
        int i = 0;
        int j = 1;
        int k = 0;
        // 假设 i 是最好的点,我们尝试找一个点j去推翻,如果找不到,就是最好的点。
        // i表示当前最大的字串开始的位置,j是当前考虑的字串的位置,k是当前比较的位置
        while( j + k < n) {
            if (s[i + k] == s[j + k]) {
                ++k;
            } else if (s[i + k] < s[j + k]) {
                //因为i+k这个点不如j+k这个点,因此此时s[i+1,..,i+k]开头的的字串都不如s[j+1,..,j+k]开头的字串更好。都不考虑了,直接跳到新的点去论证。
                i += k + 1;
                k = 0;
                // 如果i在j的位置以后了,我们更新j比较。前面的点都被推翻了。
                if (i >= j) {
                    j = i + 1;
                }
            } else {
                // s[i + k] > s[j + k] 我们需要找新的点去推翻,这个点就是j+k+1才可能
                j += k + 1;
                k = 0;
            }
        }
        return s.substr(i);
    }
};

1187 使数组严格递增(dp)

**在这里插入图片描述**

  • dp[i][j] 表示对于前 i 个数字时候,替换了 j 次时的最后一个数字。这个定义比较奇特,没有直接定义所求内容。
  • 转移方程:
    • 如果arr[i] > dp[i-1][j] 那可以直接,dp[i][[j] = arr[i];
    • 对于无法选择arr[i]或者不选择arr[i]的情况, 在arr2[j, end]里面寻找到第一个大于(upperbound)的 dp[i-1][j-1]的
  • 初始:对于i= 0这个位置,dp[0][0] = arr[0], dp[0][1] = arr2[0], 其余的都初始化为int_MAX;
class Solution {
public:
    int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
        // dp[i][j] 表示对于对于前i个arr1,替换j次时候的最小后缀;
        // dp[i][j] = arr[i] 满足arr[i] > dp[i-1][j] 不进行替换
        // dp[i][j] = min(dp[i][j], cal) 其中 cal是arr2中 index大于等于j,val严格大于dp[i-1][j-1]的最小值 j>0
        // 初始化:dp[i][j] = INF dp[0][0] = arr[0] dp[0][1] =  arr2[0]
        sort(arr2.begin(), arr2.end());
        arr2.erase(unique(arr2.begin(), arr2.end()), arr2.end());// 去重
        int n = arr1.size();
        int m = min((int)arr2.size(), n);
        vector<vector<int>> dp(n, vector<int>(m+1, INT_MAX));

        dp[0][0] = arr1[0];
        dp[0][1] = arr2[0];
        for(int i = 1;i<n;i++){
            for(int j = 0;j<=min(i+1,m);j++){
                if(arr1[i] > dp[i-1][j]) {
                    dp[i][j] = arr1[i];
                }
                if(j>0){
                    auto it = upper_bound(arr2.begin()+j-1, arr2.end(), dp[i-1][j-1]);
                    if(it != arr2.end()){
                        dp[i][j] = min(dp[i][j], *it);
                    }
                    
                }
                if(dp[n-1][j] != INT_MAX) return j;
            }
        }
        return -1;
    }
};

1172. 餐盘栈 H (数据结构)

在这里插入图片描述

class DinnerPlates {
public:
	// list 维护所有的stack,pq维护没有放满stack的index
    vector<stack<int>> list;
    priority_queue<int, vector<int>, greater<>> pq;
    int cap = 0;
    DinnerPlates(int capacity) {
        cap = capacity;
    }
    
    void push(int val) {
    // 首先删除可能已经空的尾部栈
        while(!pq.empty() && pq.top() >= list.size()) pq.pop();
        if (!pq.empty() ){
            int stackIndex = pq.top();
            pq.pop();
            auto &stack = list[stackIndex];
            stack.push(val);
        }else{
        // 如果所有的栈都满了,就新建一个栈
            stack<int> newStack;
            newStack.push(val);
            if(newStack.size()!=cap){
            // 有空间的栈,可以压入。
                pq.push(list.size());
            }
            list.push_back(newStack);
        }
    }
    
    int pop() {
        if(list.size() == 0) return -1;
        return popAtStack(list.size()-1);
    }
    
    int popAtStack(int index) {
        if (index > list.size() || index<0 || list[index].empty()) return -1;
        auto &stack = list[index];
        int cur = stack.top();
        stack.pop();
		// 如果stack是最后一个,并且空了,开始清理队尾
        if(index == list.size()-1 && stack.empty()){
            while(!list.empty() && list[list.size()-1].empty()) list.pop_back();
        }else{
        // 空出来了空间,因此可以压入。
            pq.push(index);
        }
        return cur;
    }
};

1157. 子数组中占绝大多数的元素 (线段树)

在这里插入图片描述

class Node {
public:
// l,r维护了区间的两个端点,x是当前的众数,cnt是摩尔投票的结果
    int l = 0, r = 0;
    int x = 0, cnt = 0;
};

using pii = pair<int, int>;

class SegmentTree {
public:
    SegmentTree(vector<int>& nums) {
        this->nums = nums;
        int n = nums.size();
        tr.resize(n << 2);
        for (int i = 0; i < tr.size(); ++i) {
            tr[i] = new Node();
        }
        build(1, 1, n);
    }

    pii query(int u, int l, int r) {
        // 当前点的区间小于查询区间, 直接返回
        if (tr[u]->l >= l && tr[u]->r <= r) {
            return {tr[u]->x, tr[u]->cnt};
        }
        int mid = (tr[u]->l + tr[u]->r) >> 1;
        // 查询区间完整在左侧,直接查询左侧
        if (r <= mid) {
            return query(u << 1, l, r);
        }
        // 查询区间完整在右侧,直接查询右侧
        if (l > mid) {
            return query(u << 1 | 1, l, r);
        }
        // 分居两边
        auto left = query(u << 1, l, r);
        auto right = query(u << 1 | 1, l, r);
        // 众数一样,直接合并
        if (left.first == right.first) {
            left.second += right.second;
        // 否则按照摩尔投票,得到最多的
        } else if (left.second >= right.second) {
            left.second -= right.second;
        } else {
            right.second -= left.second;
            left = right;
        }
        return left;
    }

private:
    vector<Node*> tr;
    vector<int> nums;

    void build(int u, int l, int r) {
        tr[u]->l = l;
        tr[u]->r = r;
        if (l == r) {
            tr[u]->x = nums[l - 1];
            tr[u]->cnt = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }

    void pushup(int u){
        int l = u<<1;
        int r = u<<1|1;
        if(tr[l]->x == tr[r]->x){
            tr[u]->x = tr[r]->x;
            tr[u]->cnt = tr[l]->cnt + tr[r]->cnt;
        }else if (tr[l]->cnt >= tr[r]->cnt){
            tr[u]->x = tr[l]->x;
            tr[u]->cnt = tr[l]->cnt - tr[r]->cnt;
        }else{
            tr[u]->x = tr[r]->x;
            tr[u]->cnt = tr[r]->cnt - tr[l]->cnt;
        }
    }
};

class MajorityChecker {
public:
    MajorityChecker(vector<int>& arr) {
        tree = new SegmentTree(arr);
        for (int i = 0; i < arr.size(); ++i) {
            d[arr[i]].push_back(i);
        }
    }

    int query(int left, int right, int threshold) {
        // 得到区间内的众数
        int x = tree->query(1, left + 1, right + 1).first;
        // 二分查找,index 大于等于left的
        auto l = lower_bound(d[x].begin(), d[x].end(), left);
        // 二分查找,index大于等于riht的
        auto r = upper_bound(d[x].begin(), d[x].end(), right);
        return r - l >= threshold ? x : -1;
    }

private:
    unordered_map<int, vector<int>> d;
    // 这里一定是一个地址
    SegmentTree* tree;
};

/**
 * Your MajorityChecker object will be instantiated and called as such:
 * MajorityChecker* obj = new MajorityChecker(arr);
 * int param_1 = obj->query(left,right,threshold);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值