D348周赛复盘:最小化字符串长度+半有序排列+查询矩阵和

6462.最小化字符串长度

给你一个下标从 0 开始的字符串 s ,重复执行下述操作 任意 次:

  • 在字符串中选出一个下标 i ,并使 c 为字符串下标 i 处的字符。并在 i 左侧(如果有)和 右侧(如果有)各 删除 一个距离 i 最近 的字符 c

请你通过执行上述操作任意次,使 s 的长度 最小化

返回一个表示 最小化 字符串的长度的整数。

示例 1:

输入:s = "aaabc"
输出:3
解释:在这个示例中,s 等于 "aaabc" 。我们可以选择位于下标 1 处的字符 'a' 开始。接着删除下标 1 左侧最近的那个 'a'(位于下标 0)以及下标 1 右侧最近的那个 'a'(位于下标 2)。执行操作后,字符串变为 "abc" 。继续对字符串执行任何操作都不会改变其长度。因此,最小化字符串的长度是 3
  • 这道题最简单的做法是直接用哈希表map把原字符串塞进去,由于unordered_map本身就有key去重的特性,因此map中的key就是最后的结果。用result存放map中的key即可。
class Solution {
public:
    int minimizedStringLength(std::string s) {
        unordered_map<char, int> map;
        for (int i = 0; i < s.size(); i++) {
            map[s[i]]++;
        }

        string result = "";
        for (auto it = map.begin(); it != map.end(); ++it) {
            result += it->first;
        }

        return result.size();
    }
};

6424.半有序排列

给你一个下标从 0 开始、长度为 n 的整数排列 nums 。

如果排列的第一个数字等于 1 且最后一个数字等于 n ,则称其为 半有序排列 。你可以执行多次下述操作,直到将 nums 变成一个 半有序排列 :

选择 nums 中相邻的两个元素,然后交换它们。
返回使 nums 变成 半有序排列 所需的最小操作次数。

排列 是一个长度为 n 的整数序列,其中包含从 1 到 n 的每个数字恰好一次。

输入:nums = [2,1,4,3]
输出:2
解释:可以依次执行下述操作得到半有序排列:
1 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3]2 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。
可以证明,要让 nums 成为半有序排列,不存在执行操作少于 2 次的方案。

示例 2:

输入:nums = [2,4,1,3]
输出:3
解释:
可以依次执行下述操作得到半有序排列:
1 - 交换下标 1 和下标 2 对应元素。排列变为 [2,1,4,3]2 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3]3 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。
可以证明,要让 nums 成为半有序排列,不存在执行操作少于 3 次的方案。

示例 3:

输入:nums = [1,3,4,2,5]
输出:0
解释:这个排列已经是一个半有序排列,无需执行任何操作。

思路

这道题主要是把确定的两个数字1和n分别移动到数组的两侧,假设1的下标是p,n的下标是q,有两种情况:

  • p<q,那么1和n只需要分别移动到数组两头即可,操作次数为n-1-q+p
  • 在这里插入图片描述
    p>q,1和n的移动次数为n-1-q+p-1,因为我们要计算的是移动元素的操作次数,而例子中1和n的大小交换本身就算一次操作次数
    在这里插入图片描述
class Solution {
public:
    int semiOrderedPermutation(vector<int> &nums){
        int n = nums.size();
        int flag=0;
        //在vector数组中找到1的元素下标
        auto p = find(nums.begin(),nums.end(),1);
        //vector数组中找到n的元素下标
        auto q = find(nums.begin(),nums.end(),n);
        if(p<q) flag = 0;
        if(p>q) flag = 1;
        //注意问题:p和q是迭代器类型!迭代器不能直接作为整型运算,需要用distance做转换!
         int result = n - 1 - distance(nums.begin(), q) + distance(nums.begin(), p) - flag;
        return result;
        
    }
}

补充1:vector_find()方法返回想要查找的元素下标

在哈希表中了解过find()方法可以用来查找set或者map中key指定的元素,例如:

int main() {
    std::unordered_set<int> set;
    set.insert(1);
    set.insert(2);
    set.insert(3);

    if(set.find(2) != set.end()) {
        std::cout << "2 is in the set" << std::endl;
    }
    return 0;
}
int main() {
    std::unordered_map<std::string, int> map;
    map["Alice"] = 18;
    map["Bob"] = 20;

    if(map.find("Alice") != map.end()) {
        std::cout << "Alice is " << map["Alice"] << " years old." << std::endl;
    }
    return 0;
}
//两个数组的交集题目
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int>result;
        unordered_set<int>nums_set(nums1.begin(),nums1.end());
        //可以使用数组的begin和end直接放进set容器里
        
        //遍历nums2
        for(int i=0;i<nums2.size();i++){
            if(nums_set.find(nums2[i])!=nums_set.end()){
                result.insert(nums2[i]); //set的insert用法
            }
        }
        //返回result,直接使用vector的容器转换
        return vector<int>(result.begin(),result.end());

    }
//两数之和题目
vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int>map;
        for(int i=0;i<nums.size();i++){
            int s=target-nums[i];
            auto iter = map.find(s); //注意auto这种方式
            if(map.find(s)!=map.end()){
                return {iter->value,i}}
            map.insert(nums[i],i);
        }
        return {};//找不到返回空集合
    }

find()是一个模板函数,用于在指定范围内查找和目标元素值相等的第一个元素。

find()属于头文件中的算法,可以作用于所有的序列式容器,例如数组,vector,list等。

容器是否可以直接用find(),这要看容器的元素是否支持运算符,因为find()的底层实现就是用运算符将目标元素和范围内的元素逐个进行比较。

需要注意的是,find() 返回的是一个迭代器,而不是元素的下标。在上面的代码中,我们使用 auto 关键字来推断 pq 的类型,它们实际上是迭代器类型。为了将迭代器转换为整型,在进行数值计算之前我们使用了 distance() 函数,该函数返回两个迭代器之间的距离(即它们之间相差的元素个数),从而可以将迭代器转换为整型。

STL算法

参考:STL 算法 - OI Wiki (oi-wiki.org)

STL 提供了大约 100 个实现算法的模版函数,基本都包含在 <algorithm> 之中,还有一部分包含在 <numeric><functional>。完备的函数列表请 参见参考手册,排序相关的可以参考 排序内容的对应页面

  • find:顺序查找。find(v.begin(), v.end(), value),其中 value 为需要查找的值。

    auto p = find(nums.begin(),nums.end(),1);
    iterator find(iterator first, iterator last, const T& value);
    
  • find_end:逆序查找。find_end(v.begin(), v.end(), value)

  • reverse:翻转数组、字符串。reverse(v.begin(), v.end())reverse(a + begin, a + end)

  • unique:去除容器中相邻的重复元素。unique(ForwardIterator first, ForwardIterator last),返回值为指向 去重后 容器结尾的迭代器,原容器大小不变。与 sort 结合使用可以实现完整容器去重。

  • random_shuffle:随机地打乱数组。random_shuffle(v.begin(), v.end())random_shuffle(v + begin, v + end)

    (random_shuffle 自 C++14 起被弃用,C++17 起被移除。

    在 C++11 以及更新的标准中,可以使用 shuffle 函数代替原来的 random_shuffle。使用方法为 shuffle(v.begin(), v.end(), rng)(最后一个参数传入的是使用的随机数生成器,一般情况使用以真随机数生成器 random_device 播种的梅森旋转伪随机数生成器 mt19937)。)

  • sort:排序。sort(v.begin(), v.end(), cmp)sort(a + begin, a + end, cmp),其中 end 是排序的数组最后一个元素的后一位,cmp 为自定义的比较函数。
  • stable_sort:稳定排序,用法同 sort()
  • nth_element:按指定范围进行分类,即找出序列中第 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MdB49Qg2-1685906051953)(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)] 大的元素,使其左边均为小于它的数,右边均为大于它的数。nth_element(v.begin(), v.begin() + mid, v.end(), cmp)nth_element(a + begin, a + begin + mid, a + end, cmp)
  • binary_search:二分查找。binary_search(v.begin(), v.end(), value),其中 value 为需要查找的值。

补充2:迭代器类型转化为整数

int result = n - 1 - q + p - flag,写法错误,因为p和q是迭代器不能直接投入运算,需要把迭代器转化为对应的整数下标,如:

int result = n - 1 - distance(nums.begin(), q) + distance(nums.begin(), p) - flag;

distance(nums.begin(), q) 返回的是 nums.begin()q 之间的距离,也就是 q 在向量 nums 中的位置(从 nums.begin() 开始算)。因此,它返回的是 q 的下标(整数值),而不是迭代器。在上面的代码中,我们使用 distance(nums.begin(), q) 来获取 q 在向量 nums 中的位置,并将其转换为整数类型,以便进行数值计算。

除了 distance() 函数外,还可以使用迭代器的 operator- 运算符来计算两个迭代器之间的距离。例如,对于迭代器 it1it2,可以使用 it2 - it1 来计算它们之间的距离,返回一个整型值。

std::vector<int> v = {1, 2, 3, 4, 5};
auto it1 = v.begin(); // 指向第一个元素的迭代器
auto it2 = v.end();   // 指向尾后元素的迭代器
int dist = it2 - it1; // 计算迭代器距离

需要注意的是,不是所有类型的迭代器都支持 operator- 运算符。只有随机访问迭代器(如 vectorarray 等,也就是序列式容器才支持该运算符。对于其他类型的迭代器(如 listset 等),只能使用 distance() 函数来计算距离。

6472.查询后矩阵的和

给你一个整数 n 和一个下标从 0 开始的 二维数组 queries ,其中 queries[i] = [typei, indexi, vali] 。

一开始,给你一个下标从 0 开始的 n x n 矩阵,所有元素均为 0 。每一个查询,你需要执行以下操作之一:

如果 typei == 0 ,将第 indexi 行的元素全部修改为 vali ,覆盖任何之前的值。
如果 typei == 1 ,将第 indexi 列的元素全部修改为 vali ,覆盖任何之前的值。
请你执行完所有查询以后,返回矩阵中所有整数的和。

在这里插入图片描述
输入:n = 3, queries = [[0,0,1],[1,2,2],[0,2,3],[1,0,4]]
输出:23
解释:上图展示了每个查询以后矩阵的值。所有操作执行完以后,矩阵元素之和为 23 。

思路

  • 这道题的思路一个很重要的点是,题目只需要求所有整数的和,不需要矩阵的覆盖是正确的!倒序遍历的做法会导致矩阵的覆盖是错误的,但是我们并不需要考虑最终的矩阵是不是覆盖正确的矩阵,我们只需要求和就可以了。

    • 这也是我看题解的时候迟迟不能理解倒序遍历的原因,我没有注意到,这个矩阵并不需要输出的本身是覆盖正确的结果,只需要和正确就可以
  • 只有后面的修改是生效的,前面的部分即使修改了,也会被覆盖掉,例如下图中[0][0]位置的元素,会被4覆盖掉。

  • 在这里插入图片描述

因此,我们可以考虑,如果先遍历queries中的4,也就是从后向前遍历,意味着直接就能得到一个位置上的最终结果。比起每一个位置遍历检查覆盖,时间复杂度大大优化。

  • 对于一个倒序遍历到的元素,需要知道:

    • 进行的是行操作还是列操作,queries[k][0]是0还是1

    • 这一行之前有没有被处理过(之前指的是大于i的操作),rowSet.find(queries[k][1])是否存在

    • 两个哈希表记录已经被处理过的行/列号

完整版

class Solution {
public:
    long long matrixSumQueries(int n, vector<vector<int>>& queries) {
         std::unordered_set<int> rowSet; // 记录操作过的行
        std::unordered_set<int> colSet; // 记录操作过的列
        int m = queries.size();
        long sum = 0; // 计算矩阵和
        for (int k = m - 1; k >= 0; k--) { // 倒序遍历
            // 当前处理行且行未被处理过
            if (queries[k][0] == 0 && rowSet.find(queries[k][1]) == rowSet.end()) {
                sum += (n - colSet.size()) * static_cast<long>(queries[k][2]); // 这一行的元素和等于未被处理的列数 * 赋予的值
                rowSet.insert(queries[k][1]); // 添加该行已处理
            }
            // 当前处理列且列未被处理过
            if (queries[k][0] == 1 && colSet.find(queries[k][1]) == colSet.end()) {
                sum += (n - rowSet.size()) * static_cast<long>(queries[k][2]); // 这一列的元素和等于未被处理的行数 * 赋予的值
                colSet.insert(queries[k][1]); // 添加该列已处理
            }
        }
        return sum;
        
    }
};
  • (n - colSet.size())代表的是该行中未经处理的元素个数。这些元素的值都是矩阵中原来就存在的元素,因此需要将它们乘以该行操作赋予的值 queries[k][2],然后将结果累加到 sum 中。
  • 同样地,如果当前处理的是列操作且该列未被处理过,那么 (n - rowSet.size()) 表示该列中未被查询操作覆盖的行数,即该列中未被处理的元素个数。

问题:如果对于同一个元素,查询操作是先对列进行操作,再对行进行操作,那么倒序遍历查询操作的时候,会先遍历到行操作,再遍历到列操作,覆盖错误?

例如下图的情况,对于左上角元素4,是先行操作再列操作,但是倒序遍历会使得4这个元素的位置覆盖是错误的。
在这里插入图片描述

覆盖错误是会出现的,但是,这道题目的含义是求解矩阵中所有矩阵的和,并不需要覆盖正确。当求和的时候,4所在的行已经被排除掉了,4的覆盖错误并不会影响最后的求和。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值