LeetCode -- 第9场双周赛&&第155场周赛

第9场双周赛

5072. 最多可以买到的苹果数量

题目描述

楼下水果店正在促销,你打算买些苹果,arr[i] 表示第 i 个苹果的单位重量。
你有一个购物袋,最多可以装 5000 单位重量的东西,算一算,最多可以往购物袋里装入多少苹果。
示例 1:
输入:arr = [100,200,150,1000]
输出:4
解释:所有 4 个苹果都可以装进去,因为它们的重量之和为 1450。

示例 2:
输入:arr = [900,950,800,1000,700,800]
输出:5
解释:6 个苹果的总重量超过了 5000,所以我们只能从中任选 5 个。

题解

贪心法,每次选择重量最小的苹果入袋,当一个苹果的重量超过购物袋的承受能力,则表示不能再放了。
当然,为了方便每次选择重量最小的,需要先排序。
时间复杂度为 o ( n l o g n ) o(nlogn) o(nlogn),空间复杂度为 o ( 1 ) o(1) o(1)

代码

class Solution {
public:
    int maxNumberOfApples(vector<int>& arr) {
        sort(arr.begin(), arr.end());//从小到大排序
        int cnt = 0, sum = 0;//cnt:装入购物袋的苹果数;sum:当前购物袋中苹果的总重量。
        for(int i = 0; i < arr.size(); ++i){
            sum += arr[i];
            if(sum <= 5000){
                ++cnt;
            }
            else{
                break;
            }
        }
        return cnt;
    }
};

5073. 进击的骑士

题目描述

一个坐标可以从 -infinity 延伸到 +infinity 的 无限大的 棋盘上,你的 骑士 驻扎在坐标为 [0, 0] 的方格里。
骑士的走法和中国象棋中的马相似,走 “日” 字:即先向左(或右)走 1 格,再向上(或下)走 2 格;或先向左(或右)走 2 格,再向上(或下)走 1 格。
每次移动,他都可以按图示八个方向之一前进。
LeetCode -- 第9场双周赛&&第155场周赛_第1张图片
现在,骑士需要前去征服坐标为 [x, y] 的部落,请你为他规划路线。
最后返回所需的最小移动次数即可。本题确保答案是一定存在的。
示例 1:
输入:x = 2, y = 1
输出:1
解释:[0, 0] → [2, 1]

示例 2:
输入:x = 5, y = 5
输出:4
解释:[0, 0] → [2, 1] → [4, 2] → [3, 4] → [5, 5]

提示:
|x| + |y| <= 300

题解

题目中就是要求解象棋中马到达某个位置所需的最小步数。这是bfs的一个简单的应用。与其它描述不同的是棋盘可以无限延伸,这个约束可有可无,通过处理可以把所有的坐标约束在第一象限或者将棋盘变成有限的。
考试中没有使用标记数组,导致运行时间一直超时,结束之后看了其他人的代码,才发现问题所在,当时脑子真的是瓦特了。
标记处理看了其他人的解法,主要有三种方式:

  1. 使用二维数组标记,因为x与y可能会为负,所以结合|x| + |y| <= 300,标记的时候mark[now_x + 400][now_y + 400],横纵坐标都加上400,将其转成正的。
  2. 使用二维数组标记,与法一不同的是,结合对称性,将所有的坐标转成正的。具体来说从(0, 0)到(x, y)与(0, 0)到(|x|, |y|)的最小步数是相同的。
  3. set标记,使用哈希函数idx = now_x * 1000 + now_y,然后节点遍历后将idx插入set中,通过set来判断是否重复访问。

代码

bool mark[800][800];
class Solution {
public:
    struct Node{
        int x;
        int y;
        int cnt;
    };
    int minKnightMoves(int x, int y) {
        int dir[8][2] = {{-2, 1}, {-1, 2}, {1, 2}, {2, 1}, 
                         {2, -1}, {1, -2}, {-1, -2}, {-2, -1}};//定义8个方向
        for(int i = -350; i <= 350; ++i){
            for(int j = -350; j <= 350; ++j){//初始化mark
                mark[i +400][j +400] = false;
            }
        }
        int res = 0, flag = false;
        queue<Node>que;
        Node frt, next;
        que.push(Node({0, 0, 0}));
        mark[400][400] = true;
        while(!que.empty() && !flag){//bfs
            frt = que.front();
            if(frt.x == x && frt.y == y){
                res = frt.cnt;
                break;
            }
            que.pop();
            for(int i = 0; i < 8; ++i){//8个方向遍历
                    next.x = frt.x + dir[i][0];
                    next.y = frt.y + dir[i][1];
                    if(next.x == x && next.y == y){
                        flag = true;
                        res = frt.cnt + 1;
                        break;
                    }
                    if(!mark[next.x +400][next.y + 400]){//将未访问过的节点放入队列中
                        mark[next.x+400][next.y+400] = true;
                        que.push({next.x, next.y, frt.cnt + 1});
                    }
                }
        }
        return res;
    }
};

5071. 找出所有行中最小公共元素

题目描述

给你一个矩阵 mat,其中每一行的元素都已经按 递增 顺序排好了。请你帮忙找出在所有这些行中 最小的公共元素。
如果矩阵中没有这样的公共元素,就请返回 -1。
示例:
输入:mat = [[1,2,3,4,5],[2,4,5,8,10],[3,5,7,9,11],[1,3,5,7,9]]
输出:5

提示:
1 <= mat.length, mat[i].length <= 500
1 <= mat[i][j] <= 10^4
mat[i] 已按递增顺序排列。

题解(暴力法)

比赛时最直接最暴力的想法,遍历矩阵mat第一行的元素,判断该元素是否存在其他所有行中,如果是则返回该元素,否则返回-1。
因为矩阵的每一行都是有序的,所以查找使用的是二分查找
时间复杂度为 o ( n ∗ m ∗ l o g m ) o(n * m * logm) o(nmlogm), 空间复杂度为 o ( 1 ) o(1) o(1)

代码

class Solution {
public:
    int smallestCommonElement(vector<vector<int>>& mat) {
        if(mat.size() == 1){//只有一行时,直接返回mat[0][0]
            return mat[0][0];
        }
        bool flag = true;
        int res = -1;
        for(int i = 0; i < mat[0].size(); ++i){
            flag = true;
            for(int j = 1; j < mat.size(); ++j){
                if(!binary_search(mat[j].begin(), mat[j].end(), mat[0][i])){//二分查找使用的是stl中的binary_search(beg, end, target),返回值是布尔值。 
                    flag = false;
                    break;
                }
            }
            if(flag){
                res = mat[0][i];
                break;
            }
        }
        return res;
    }
};

题解(标记法)

因为题目中提到每个数最大只能为 1 0 4 10^4 104,所以可以统计mat中每个数出现的次数,如果一行中一个数出现二次及以上,则只统计一次,最后出现n次中最小的数即为所求,如果不存在,则返回-1。
时间复杂度为 o ( n ∗ m ) o(n * m) o(nm),但空间上需要开辟一个数组来标记。

代码

class Solution {
public:
    int smallestCommonElement(vector<vector<int>>& a) {
        int n = a.size();
        int m = a[0].size();
        vector<int> v(10010);
        for (int i = 0; i < n; ++ i)
        {
            for (int j = 0; j < m; ++ j)
                if (j == 0 || a[i][j] != a[i][j-1]) v[a[i][j]] ++;
        }
        for (int i = 1; i <= 10000; ++ i)
            if (v[i] == n) return i;
        return -1;
    }
};

5091. 建造街区的最短时间

题目描述

题解

代码

第155场周赛

5197. 最小绝对差

题目描述

给你个整数数组 arr,其中每个元素都 不相同。
请你找到所有具有最小绝对差的元素对,并且按升序的顺序返回。
示例 1:
输入:arr = [4,2,1,3]
输出:[[1,2],[2,3],[3,4]]

示例 2:
输入:arr = [1,3,6,10,15]
输出:[[1,3]]

示例 3:
输入:arr = [3,8,-10,23,19,-4,-14,27]
输出:[[-14,-10],[19,23],[23,27]]

提示:
2 < = a r r . l e n g t h < = 1 0 5 2 <= arr.length <= 10^5 2<=arr.length<=105
− 1 0 6 < = a r r [ i ] < = 1 0 6 -10^6 <= arr[i] <= 10^6 106<=arr[i]<=106

题解

先排序,先从左到右扫描一遍找到最小绝对差,然后再从左到右扫描一遍将等于最小绝对差的元素对添加到结果集中。这两步可以结合成一步。

代码

class Solution {
public:
    vector<vector<int>> minimumAbsDifference(vector<int>& arr) {
        sort(arr.begin(), arr.end());
        vector<vector<int>>res;
        int min_val = 100000000;
        for(int i = 1; i < arr.size(); ++i){//找到最小绝对差
            if(arr[i] - arr[i - 1] < min_val){
                min_val = arr[i] - arr[i - 1];
            }
        }
        for(int i = 1; i < arr.size(); ++i){//将值等于最小绝对差的添加到vector中。
            if(arr[i] - arr[i - 1] == min_val){
                res.push_back(vector<int>({arr[i - 1], arr[i]}));
            }
        }
        return res;
    }
};

将两步合二为一

class Solution {
public:
    vector<vector<int>> minimumAbsDifference(vector<int>& arr) {
        sort(arr.begin(), arr.end());
        vector<vector<int>>res;
        int min_val = 100000000;
        for(int i = 1; i < arr.size(); ++i){//找到最小绝对差
            if(arr[i] - arr[i - 1] < min_val){
                min_val = arr[i] - arr[i - 1];
                res.clear();
                res.push_back({arr[i-1], arr[i]});
            }
            else if(arr[i] - arr[i - 1] == min_val){
            	res.push_back({arr[i-1], arr[i]});
            }
        }
        return res;
    }
};

5198. 丑数 III

题目描述

请你帮忙设计一个程序,用来找出第 n 个丑数。
丑数是可以被 a 或 b 或 c 整除的 正整数。
示例 1:
输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10… 其中第 3 个是 4。

示例 2:
输入:n = 4, a = 2, b = 3, c = 4
输出:6
解释:丑数序列为 2, 3, 4, 6, 8, 9, 12… 其中第 4 个是 6。

示例 3:
输入:n = 5, a = 2, b = 11, c = 13
输出:10
解释:丑数序列为 2, 4, 6, 8, 10, 11, 12, 13… 其中第 5 个是 10。

示例 4:
输入:n = 1000000000, a = 2, b = 217983653, c = 336916467
输出:1999999984

提示:
1 < = n , a , b , c < = 1 0 9 1 <= n, a, b, c <= 10^9 1<=n,a,b,c<=109
1 < = a ∗ b ∗ c < = 1 0 18 1 <= a * b * c <= 10^{18} 1<=abc<=1018
本题结果在 [1, 2 * 10^9] 的范围内

题解

这题直接暴力会超时,是过不了的,后来比赛中采用的是容斥原理+二分查找通过的。
具体思路在[beg, end]中二分判断mid = (beg + end) / 2处1-mid中可以被 a 或 b 或 c 整除的 正整数的个数是否等于n,大于则调小end;小于则调大beg。等于的话可以停止二分,说明[1, mid]间的丑数的个数刚好等于n,但不一定第n个丑数就是mid,我们还需要逐渐减小mid,找到的第一个丑数即为所求。
注意 :此题的数据类型最好用long

代码

/*
容斥原理+二分法
*/
class Solution {
public:
    long long int gcd(long long int a, long long int b){//求最大公约数
        return b == 0?a:gcd(b, a % b);
    }
    long long int lcm(long long int a, long long int b){//求最小公倍数
        return a / gcd(a, b) * b;
    }
    long long int getNum(long long int end, long long int a, long long int b, long long int c){
    //用容斥原理求某一范围内的丑数的个数
        long long int num1 = end / a + end / b + end / c;
        long long int tmp1 = lcm(a, b), tmp2 = lcm(a, c), tmp3 = lcm(b, c), tmp4 = lcm(a, lcm(b, c)), tmp5 = end;
        long long int num2 = tmp5 / tmp1 + tmp5 / tmp2 + tmp5 / tmp3;
        long long int num3 = tmp5 / tmp4;
        return num1 - num2 + num3;
    }
    int nthUglyNumber(int n, int a, int b, int c) {
        long long int min_num = a;
        long long int beg = 1, end = min_num * n, mid, bound;
        while(beg <= end){//二分法
            mid = (beg + end) >> 1;
            int cnt = getNum(mid, a, b, c);
            if(cnt > n){
                end = mid - 1;
            }
            else if(cnt < n){
                beg = mid + 1;
            }
            else{
                break;
            }
        }
        while(mid % a != 0 && mid % b != 0 && mid % c != 0) --mid;
        return mid;
    }
};

5199. 交换字符串中的元素

题目描述

给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。
你可以 任意多次交换 在 pairs 中任意一对索引处的字符。
返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。
示例 1:
输入:s = “dcab”, pairs = [[0,3],[1,2]]
输出:“bacd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[1] 和 s[2], s = “bacd”

示例 2:
输入:s = “dcab”, pairs = [[0,3],[1,2],[0,2]]
输出:“abcd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[0] 和 s[2], s = “acbd”
交换 s[1] 和 s[2], s = “abcd”

示例 3:
输入:s = “cba”, pairs = [[0,1],[1,2]]
输出:“abc”
解释:
交换 s[0] 和 s[1], s = “bca”
交换 s[1] 和 s[2], s = “bac”
交换 s[0] 和 s[1], s = “abc”

提示:
1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] < s.length
s 中只含有小写英文字母

题解

若a与b能交换,b与c能交换。则a,b,c三者能两两交换。则交换后的最小字典序,实际上对应的是abc组成的字符串排序。所以我们首先需要合并能交换索引放在一起,这里使用的是并查集。然后排序,最后按下标组成新的字符串。
注意:代码中的vector换成string,会在最后一个测试用例超时。

代码

/*
并查集
*/
class Solution {
public:
    int father[100005];
    int find(int num){//查找
        return father[num] == num?num:father[num] = find(father[num]);
    }
    void unions(int num1, int num2){//合并
        int root1 = find(num1), root2 = find(num2);
        father[root1] = root2;
    }
    string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
        int len = s.size();
        string res;
        vector<vector<char>>vec(len);
        for(int i = 0; i < 100005; ++i){//并查集初始化
            father[i] = i;
        }
        for(int i = 0; i < pairs.size(); ++i){//并查集合并
            unions(pairs[i][0], pairs[i][1]);
        }
        for(int i = 0; i < len; ++i){//将同一集合的下标对应的字符放到同一vector中
            vec[find(i)].push_back(s[i]);
        }
        for(int i = 0; i < len; ++i){//排序+逆序
            sort(vec[i].begin(), vec[i].end());
            reverse(vec[i].begin(), vec[i].end());
        }
        for(int i = 0; i < len; ++i){//重组字符串,比较巧妙
            res.push_back(vec[find(i)].back());
            vec[find(i)].pop_back();
        }
        return res;
    }
};

你可能感兴趣的:(LeetCode)