每日刷题计划之LC每日一题Day[14, 25]

题源:LeetCode

Day14:1219. 黄金矿工

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。

为了使收益最大化,矿工需要按以下规则来开采黄金:

  • 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
  • 矿工每次可以从当前位置向上下左右四个方向走。
  • 每个单元格只能被开采(进入)一次。
  • 不得开采(进入)黄金数目为 0 的单元格。
  • 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。

示例 1:
输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
[5,8,7],
[0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。

示例 2:
输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释:
[[1,0,7],
[2,0,6],
[3,4,5],
[0,3,0],
[9,0,20]]
一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。

提示:
1 <= grid.length, grid[i].length <= 15
0 <= grid[i][j] <= 100
最多 25 个单元格中有黄金。

思路:
深搜。
边界条件:上下左右,要么走过,要么是0,要么越界。(可以把走过的设定为0,就把条件要么走过要么是0合并了)
归时比较最大值。如果大于最大值,则更新。

class Solution {
public:
    int maxRes = 0;
    void dfs(int x, int y,int res, vector<vector<int>>& grid){       
        int t = grid[x][y];
        res += grid[x][y];     
        if(x - 1 >= 0 && grid[x - 1][y]){ 
            grid[x][y] = 0;
            dfs(x - 1, y, res, grid);
            grid[x][y] = t;
        }
        if(x + 1 < grid.size() && grid[x + 1][y] ){
            grid[x][y] = 0;
            dfs(x + 1, y, res , grid);
            grid[x][y] = t;
        }
        if(y + 1 < grid[0].size() && grid[x][y + 1] ){
            grid[x][y] = 0;
            dfs(x, y + 1, res , grid);
            grid[x][y] = t;

        }
        if(y - 1 >= 0 && grid[x][y - 1] ){
            grid[x][y] = 0;
            dfs(x, y - 1, res , grid);
            grid[x][y] = t;
        }
       
        if(res > maxRes) maxRes = res;
        return ;
        
    }
    int getMaximumGold(vector<vector<int>>& grid) {
        for(int i = 0; i < grid.size(); i++){
            for(int j = 0; j < grid[i].size(); j++){
                if(grid[i][j])
                    dfs(i, j, 0, grid);
            }
        }
        return maxRes;
    }
};

Day15:1748. 唯一元素的和

给你一个整数数组 nums 。数组中唯一元素是那些只出现 恰好一次 的元素。

请你返回 nums 中唯一元素的 和 。

示例 1:
输入:nums = [1,2,3,2]
输出:4
解释:唯一元素为 [1,3] ,和为 4 。

示例 2:
输入:nums = [1,1,1,1,1]
输出:0
解释:没有唯一元素,和为 0 。

示例 3 :
输入:nums = [1,2,3,4,5]
输出:15
解释:唯一元素为 [1,2,3,4,5] ,和为 15 。

提示:
1 <= nums.length <= 100
1 <= nums[i] <= 100

思路:哈希表。遍历。累加恰好出现一次的元素值。

class Solution {
public:
    
    int sumOfUnique(vector<int>& nums) {
        unordered_map<int, int> cnt;
        for(int num : nums){
            cnt[num]++;
        }
        int ans = 0;
        for(auto &[num, c] : cnt){
            if(c == 1)
                ans += num;
        }
        return ans;
    }
};

Day16:1405. 最长快乐字符串

如果字符串中不含有任何 ‘aaa’,‘bbb’ 或 ‘ccc’ 这样的字符串作为子串,那么该字符串就是一个「快乐字符串」。

给你三个整数 a,b ,c,请你返回 任意一个 满足下列全部条件的字符串 s:

  • s 是一个尽可能长的快乐字符串。
  • s 中 最多 有a 个字母 ‘a’、b 个字母 ‘b’、c 个字母 ‘c’ 。
  • s 中只含有 ‘a’、‘b’ 、‘c’ 三种字母。
    如果不存在这样的字符串 s ,请返回一个空字符串 “”。

示例 1:
输入:a = 1, b = 1, c = 7
输出:“ccaccbcc”
解释:“ccbccacc” 也是一种正确答案。

示例 2:
输入:a = 2, b = 2, c = 1
输出:“aabbc”

示例 3:
输入:a = 7, b = 1, c = 0
输出:“aabaa”
解释:这是该测试用例的唯一正确答案。

提示:
0 <= a, b, c <= 100
a + b + c > 0

思路:贪心
题目要求找到最长的快乐字符串,且快乐字符串中不含有三个连续相同的字母。
为了找到最长的字符串,我们可以使用如下贪心策略:

  • 尽可能有限使用当前数量最多的字母,因为最后同一种字母剩余的越多,越容易出现字母连续相同的情况。如果构建完成最长的快乐字符串后还存在剩余未选择的字母,则剩余的字母一定为同一种字母且该字母的总数量最多。
  • 依次从当前数量最多的字母开始尝试,如果发现加入当前字母会导致出现三个连续相同字母,则跳过当前字母,直到我们找到可以添加的字母为止。实际上每次只会在数量最多和次多的字母中选择一个。
  • 如果尝试所有的字母都无法添加,则直接退出,此时构成的字符串即为最长的快乐字符串。
class Solution {
public:
    string longestDiverseString(int a, int b, int c) {
        string res;
        vector<pair<int, char> > arr = {{a, 'a'}, {b, 'b'}, {c, 'c'}};

        while(true){
            sort(arr.begin(), arr.end(), [](const pair<int, char> & p1, const pair<int, char> & p2){
                return p1.first > p2.first;
            });
            bool hasNext = false;
            for(auto & [freq, ch] : arr){
                int m = res.size();//结果字符串的长度
                if(freq <= 0) break;
                if(m >= 2 && res[m - 2] == ch && res[m - 1] == ch) continue;//如果字符串的长度大于2 并且 前两个都是这个字符,那就不再安排这个字符
                hasNext = true;//可以安排下一个字符
                res.push_back(ch);
                freq--;
                break;
            }
            if(!hasNext) break;//如果不可以安排下一个字符,就退出
        }
        return res;
    }
};

Day17:1001. 网格照明

在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态。

给你一个由灯的位置组成的二维数组 lamps ,其中 lamps[i] = [rowi, coli] 表示 打开 位于 grid[rowi][coli] 的灯。即便同一盏灯可能在 lamps 中多次列出,不会影响这盏灯处于 打开 状态。

当一盏灯处于打开状态,它将会照亮 自身所在单元格 以及同一 行 、同一 列 和两条 对角线 上的 所有其他单元格 。

另给你一个二维数组 queries ,其中 queries[j] = [rowj, colj] 。对于第 j 个查询,如果单元格 [rowj, colj] 是被照亮的,则查询结果为 1 ,否则为 0 。在第 j 次查询之后 [按照查询的顺序] ,关闭 位于单元格 grid[rowj][colj] 上及相邻 8 个方向上(与单元格 grid[rowi][coli] 共享角或边)的任何灯。

返回一个整数数组 ans 作为答案, ans[j] 应等于第 j 次查询 queries[j] 的结果,1 表示照亮,0 表示未照亮。

请添加图片描述

示例 2:
输入:n = 5, lamps = [[0,0],[4,4]], queries = [[1,1],[1,1]]
输出:[1,1]

示例 3:
输入:n = 5, lamps = [[0,0],[0,4]], queries = [[0,4],[0,1],[1,4]]
输出:[1,1,0]

提示:
1 <= n <= 109
0 <= lamps.length <= 20000
0 <= queries.length <= 20000
lamps[i].length == 2
0 <= rowi, coli < n
queries[j].length == 2
0 <= rowj, colj < n

试了一下并查集,不太行。因为并查集无法简单表示这个集合成员出现的次数。这种情况,用哈希表比较好。

using LL = long long;
int dir[][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,0},{0,1},{1,-1},{1,0},{1,1}};
class Solution {
public:
    vector<int> gridIllumination(int n, vector<vector<int>>& lamps, vector<vector<int>>& queries) {
        // 存储灯所在行、列、主对角线、副对角线的光的数量
        unordered_map<int,int> row,col,left,right;
        // 用来存储灯的坐标点的,便于在询问的时候,删除光的八个方向包括该光本身是否存在灯,然后将存在的灯熄灭
        set<LL> point; 
        LL N=n;
        auto change=[&](int x,int y,int c){
            row[x]+=c,col[y]+=c,right[x-y]+=c,left[x+y]+=c;
        };
        // 遍历灯:存储以上数据结构
        for(vector<int>& l:lamps){
            int x=l[0],y=l[1];
            // 重复点直接跳过
            if(point.count(x*N+y))continue;
            point.insert(x*N+y);
            change(x,y,1);
        }
        vector<int> res;
        for(vector<int>& q:queries){
            int x=q[0],y=q[1];
            // 判断(x,y)所在行、列、对角线是否存在光
            if(row[x]||col[y]||right[x-y]||left[x+y])res.push_back(1);
            else {res.push_back(0);continue;}
            // 然后将光所在点的8个方向包括该点本身的所有灯进行关闭
            for(int i=0;i<9;++i){
                int nx=x+dir[i][0],ny=y+dir[i][1];
                if(nx<0||ny<0||nx>=n||ny>=n)continue;
                // 灯存在,则进行删除灯,并关闭行列对角线上的光
                if(point.count(nx*N+ny)){
                    point.erase(nx*N+ny);
                    change(nx,ny,-1);
                }
            }
        }
        return res;
    }
};

Day18:2006. 差的绝对值为 K 的数对数目

给你一个整数数组 nums 和一个整数 k ,请你返回数对 (i, j) 的数目,满足 i < j 且 |nums[i] - nums[j]| == k 。

|x| 的值定义为:

  • 如果 x >= 0 ,那么值为 x 。
  • 如果 x < 0 ,那么值为 -x 。

示例 1:
输入:nums = [1,2,2,1], k = 1
输出:4
解释:差的绝对值为 1 的数对为:

  • [1,2,2,1]
  • [1,2,2,1]
  • [1,2,2,1]
  • [1,2,2,1]

示例 2:
输入:nums = [1,3], k = 3
输出:0
解释:没有任何数对差的绝对值为 3 。

示例 3:
输入:nums = [3,2,1,5,4], k = 2
输出:3
解释:差的绝对值为 2 的数对为:

  • [3,2,1,5,4]
  • [3,2,1,5,4]
  • [3,2,1,5,4]

提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
1 <= k <= 99

思路:边遍历边记录某个数出现次数。
进行一次遍历,遍历时下标代表j。对每一个j,我们需要知道在这个j之前的符合条件的i的个数,即满足| nums[i] - nums[j] | = k 的 i 的个数,亦满足nums[i] = nums[j] + k,或nums[i] = nums[j] - k的i的个数。然后将nums[j]插入到哈希表中。

使用哈希表可以在O(1)的时间内统计出这样的个数,因此在遍历时可以使用一个哈希表来维护不同数值的频率,并统计符合条件的数对总数。

感觉没太懂。

class Solution {
public:
    int countKDifference(vector<int>& nums, int k) {
        int res = 0, n = nums.size();
        unordered_map<int, int> cnt;
        for(int j = 0; j < n; j++){
            res += (cnt.count(nums[j] - k) ? cnt[nums[j] - k] : 0);
            res += (cnt.count(nums[j] + k) ? cnt[nums[j] + k] : 0);
            cnt[nums[j]]++;
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n 为数组 nums 的长度。我们仅使用了一次遍历来寻找所有符合条件的数对。
  • 空间复杂度:O(n)。哈希表消耗了 O(n) 的空间。

Day19:1447. 最简分数

给你一个整数 n ,请你返回所有 0 到 1 之间(不包括 0 和 1)满足分母小于等于 n 的 最简 分数 。分数可以以 任意 顺序返回。

示例 1:
输入:n = 2
输出:[“1/2”]
解释:“1/2” 是唯一一个分母小于等于 2 的最简分数。

示例 2:
输入:n = 3
输出:[“1/2”,“1/3”,“2/3”]

示例 3:
输入:n = 4
输出:[“1/2”,“1/3”,“1/4”,“2/3”,“3/4”]
解释:“2/4” 不是最简分数,因为它可以化简为 “1/2” 。

示例 4:
输入:n = 1
输出:[]

提示:1 <= n <= 100
由于要保证分数在 (0,1)范围内,我们可以枚举分母 denominator∈[2,n] 和分子 numerator∈[1,denominator),若分子分母的最大公约数为 1,则我们找到了一个最简分数。
__gcd(x,y)函数
用于求x,y的最大公约数。x,y不能是浮点数
头文件:#include< algorithm>

class Solution {
public:
    vector<string> simplifiedFractions(int n) {
        vector<string> ans;
        for (int denominator = 2; denominator <= n; ++denominator) {
            for (int numerator = 1; numerator < denominator; ++numerator) {
                if (__gcd(numerator, denominator) == 1) {
                    ans.emplace_back(to_string(numerator) + "/" + to_string(denominator));
                }
            }
        }
        return ans;
    }
};

求最大公约数
辗转相除法:

int gcd(int x,int y){
    int r;
	while (a%b!=0){
        r=a%b;
        a=b;
        b=r;    
    }
    return b; 
}

int gcd(int a,int b) {
    return b>0 ? gcd(b,a%b):a;
}

Day20:1984. 学生分数的最小差值

给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。

从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。

返回可能的 最小差值 。

示例 1:
输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:

  • [90] 最高分和最低分之间的差值是 90 - 90 = 0
    可能的最小差值是 0

示例 2:
输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:

  • [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
  • [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
  • [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
  • [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
  • [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
  • [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
    可能的最小差值是 2

提示:
1 <= k <= nums.length <= 1000
0 <= nums[i] <= 105

思路:排序,滑动窗口。

排序后任选k个,最小值和最大值差最小值被取到的时候,k个数一定是在排序后数组中连续排列的。
如果不是;假设最小数是x 最大数是y;在x到y中一定有数没有被取,则将y和没取的数交换,最大最小值的差会变小(或保持不变)。

因而,我们升序排序后遍历每个位置i,计算 nums[i+k-1]-nums[i] ,求其中的最小值极客。

题解看了wfnuser的懂了。

class Solution {
public:
    int minimumDifference(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());//从小到大(升序)排列
        int n = nums.size();
        int ans = INT_MAX;

        for(int i = 0; i < n - k + 1; i++){
            ans = min(ans, nums[i + k - 1] - nums[i]);//i到i+k-1有k个数
        }
        return ans;
    }
};

Day21:1020. 飞地的数量

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

示例 1:
请添加图片描述

输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。

示例 2:
请添加图片描述

输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。

提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 500
grid[i][j] 的值为 0 或 1

思路:并查集+虚拟节点

class UnionFind{
    
public:
    vector<int> parent;
    UnionFind(int _n):parent(_n){
        iota(parent.begin(), parent.end(), 0);
    }

    int find(int x){
        if(x == parent[x]) return x;
        return parent[x] = find(parent[x]);
    }

    void merge(int x, int y){
        /*  易错
            int rootx = parent[x];是错误写法
        */
        int rootx = find(parent[x]);
        int rooty = find(parent[y]);
        if(rootx != rooty){
            parent[rooty] = rootx;//把y集合合并到x集合
        }
    }

    bool isConnected(int x, int y){
        return find(x) == find(y);
    }
};
class Solution {
public:
    int numEnclaves(vector<vector<int>>& grid) {
        int m = grid.size();//行
        if(!m) return 0;
        int n = grid[0].size();//列
        if(!n) return 0;

        UnionFind uf(m * n + 1);
        int dummyNode = m * n;
        int cnt = 0;//记录有多少个陆地格子
        int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1){
                    if(i == 0 || j == 0 || i == m - 1 || j == n - 1){
                        uf.merge(dummyNode, i * n + j);
                    }
                    for(int k = 0; k < 4; k++){
                        int x = dx[k] + i, y = dy[k] +j;
                        if(x >= 0 && y >= 0 && x < m  && y < n && grid[x][y] == 1)
                        uf.merge(i * n + j, x * n + y); 
                    }
                    cnt++;
                }
            }
        }


        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1 && uf.isConnected(dummyNode, i * n + j)){
                    cnt --;
                }
            }
        }
        return cnt;
    }
};

Day22:1189. “气球” 的最大数量

给你一个字符串 text,你需要使用 text 中的字母来拼凑尽可能多的单词 “balloon”(气球)。

字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 “balloon”。

多个数比较最小值可以用min({});最大值同理。

class Solution {
public:
    int maxNumberOfBalloons(string text) {
        int balloon[5]={0};
        for(auto c : text){
            if(c == 'b')
                balloon[0]++;
            else if(c == 'a')
                balloon[1]++;
            else if(c == 'l')
                balloon[2]++;
            else if(c == 'o')
                balloon[3]++;
            else if(c == 'n')
                balloon[4]++;
        }
        
        int res =min({balloon[0], balloon[1], balloon[2]/2, balloon[3]/2, balloon[4]});
        return res;
    }
};

周赛28:6005. 使数组变成交替数组的最少操作数

给你一个下标从 0 开始的数组 nums ,该数组由 n 个正整数组成。

如果满足下述条件,则数组 nums 是一个 交替数组 :

nums[i - 2] == nums[i] ,其中 2 <= i <= n - 1 。
nums[i - 1] != nums[i] ,其中 1 <= i <= n - 1 。
在一步 操作 中,你可以选择下标 i 并将 nums[i] 更改 为 任一 正整数。

返回使数组变成交替数组的 最少操作数 。

示例 1:
输入:nums = [3,1,3,2,4,3]
输出:3
解释:
使数组变成交替数组的方法之一是将该数组转换为 [3,1,3,1,3,1] 。
在这种情况下,操作数为 3 。
可以证明,操作数少于 3 的情况下,无法使数组变成交替数组。

示例 2:
输入:nums = [1,2,2,2,2]
输出:2
解释:
使数组变成交替数组的方法之一是将该数组转换为 [1,2,1,2,1].
在这种情况下,操作数为 2 。
注意,数组不能转换成 [2,2,2,2,2] 。因为在这种情况下,nums[0] == nums[1],不满足交替数组的条件。

提示:
1 <= nums.length <= 105
1 <= nums[i] <= 105

反省错误:对x2和y2的赋值考虑不周。
一开始是这样的:

 int x1 = nums[1], y1 = nums[0], x2 = 0, y2 = 0, cntx = 0, cnty = 0;
 if(nums.size() > 2 && nums[2] != y1) y2 = nums[2];
 if(nums.size() > 3 && nums[1] != x1) x2 = nums[3];

过不了测试:[4,4,4,4,3,4]。
因为改变x2 || y2 的值不是线性的。
初始化x2不是遇到一个比现在x1大的值才把x2赋值x1,x1赋值新值。

class Solution {
public:
    int minimumOperations(vector<int>& nums) {
        if(nums.size() <2) return 0;
        //记录奇数位置和偶数位置出现次数最多的数字x和y,(注意处理相等的情况),答案就是奇数个数n-x的个数+偶数个数m-y的个数。
        unordered_map<int, int> numsMapX, numsMapY;
        int x1 = nums[1], y1 = nums[0], x2 = 0, y2 = 0, cntx = 0, cnty = 0;
        int flag1 = 1, flag2 = 1;
        for(int i = 0; i < nums.size(); i++){
            if(i%2 == 0){
                //偶数
                cnty++;
                numsMapY[nums[i]]++;
                if(flag1 && nums[i]!= y1){
                    //cout<<"y1: "<< y1<<endl;
                     y2 = nums[i];
                     flag1 = 0;
                }
                if(numsMapY[nums[i]] > numsMapY[y1] && nums[i] != y1){
                    //cout<<"y1: "<<y1<<"  nums[i]: " <<nums[i]<<endl;
                    y2 = y1;
                    y1 = nums[i];
                }
            }else{
                //奇数
                cntx++;
                numsMapX[nums[i]]++;
                if(flag2 && nums[i] != x1){
                    x2 = nums[i];
                    flag2 = 0;
                }
                if(numsMapX[nums[i]] > numsMapX[x1] && nums[i] != x1){
                    x2 = x1;
                    x1 = nums[i];
                }
            }
        }
        //cout<<x1<<" "<<x2<<" "<<y1<<" "<<y2<<endl;
        int ans = 0;
        if(x1 == y1){
            if(x2 == 0 && y2 != 0){
                ans = min(cntx, cnty - numsMapY[y2]);
            }else if(x2 != 0 && y2 == 0){
                ans = min(cntx - numsMapX[x2], cnty);
            }else if(x2 == 0 && y2 == 0){
                ans = min(cntx, cnty);
            }else{
                ans = min(cntx - numsMapX[x1] + cnty - numsMapY[y2], cntx - numsMapX[x2] + cnty - numsMapY[y1] );
            }
            
        }else{
            ans = cntx - numsMapX[x1] + cnty - numsMapY[y1];
        }
        
        return ans;
    }
};

Day23:540. 有序数组中的单一元素

给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。

请你找出并返回只出现一次的那个数。

你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。

示例 1:
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:
输入: nums = [3,3,7,7,10,11,11]
输出: 10

提示:
1 <= nums.length <= 105
0 <= nums[i] <= 105

二分好难
思路:由于只出现一次的元素所在下标 x 的左边有偶数个元素,因此下标 x 一定是偶数,可以在偶数下标范围内二分查找。二分查找的目标是找到满足 nums[x]=nums[x+1] 的最小的偶数下标 x,则下标 x 处的元素是只出现一次的元素。

初始时,二分查找的左边界是 0,右边界是数组的最大偶数下标,由于数组的长度是奇数,因此数组的最大偶数下标等于数组的长度减 1。每次取左右边界的平均值 mid作为待判断的下标,如果mid是奇数则将mid-1,确保mid是偶数。

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int low = 0, high = nums.size() - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            mid -= mid & 1;
            if (nums[mid] == nums[mid + 1]) {
                low = mid + 2;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
};

Day24: 1380. 矩阵中的幸运数

给你一个 m * n 的矩阵,矩阵中的数字 各不相同 。请你按 任意 顺序返回矩阵中的所有幸运数。

幸运数是指矩阵中满足同时下列两个条件的元素:

  • 在同一行的所有元素中最小
  • 在同一列的所有元素中最大

示例 1:
输入:matrix = [[3,7,8],[9,11,13],[15,16,17]]
输出:[15]
解释:15 是唯一的幸运数,因为它是其所在行中的最小值,也是所在列中的最大值。

示例 2:
输入:matrix = [[1,10,4,2],[9,3,8,7],[15,16,17,12]]
输出:[12]
解释:12 是唯一的幸运数,因为它是其所在行中的最小值,也是所在列中的最大值。

示例 3:
输入:matrix = [[7,8],[1,2]]
输出:[7]

提示:
m == mat.length
n == mat[i].length
1 <= n, m <= 50
1 <= matrix[i][j] <= 10^5
矩阵中的所有元素都是不同的

class Solution {
public:
    vector<int> luckyNumbers (vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<int> minRow(m, INT_MAX), maxCol(n);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                minRow[i] = min(minRow[i], matrix[i][j]);
                maxCol[j] = max(maxCol[j], matrix[i][j]);
            }
        }
        vector<int> ret;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == minRow[i] && matrix[i][j] == maxCol[j]) {
                    ret.push_back(matrix[i][j]);
                }
            }
        }
        return ret;
    }
};

Day25: 969. 煎饼排序

给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。

一次煎饼翻转的执行过程如下:

选择一个整数 k ,1 <= k <= arr.length
反转子数组 arr[0…k-1](下标从 0 开始)
例如,arr = [3,2,1,4] ,选择 k = 3 进行一次煎饼翻转,反转子数组 [3,2,1] ,得到 arr = [1,2,3,4] 。

以数组形式返回能使 arr 有序的煎饼翻转操作所对应的 k 值序列。任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确。

示例 1:
输入:[3,2,4,1]
输出:[4,2,4,3]
解释:
我们执行 4 次煎饼翻转,k 值分别为 4,2,4,和 3。
初始状态 arr = [3, 2, 4, 1]
第一次翻转后(k = 4):arr = [1, 4, 2, 3]
第二次翻转后(k = 2):arr = [4, 1, 2, 3]
第三次翻转后(k = 4):arr = [3, 2, 1, 4]
第四次翻转后(k = 3):arr = [1, 2, 3, 4],此时已完成排序。

提示:
1 <= arr.length <= 100
1 <= arr[i] <= arr.length
arr 中的所有整数互不相同(即,arr 是从 1 到 arr.length 整数的一个排列)

思路与算法

设一个元素的下标是 index,我们可以通过两次煎饼排序将它放到尾部:

第一步选择 k = index+1,然后反转子数组 arr[0…k−1],此时该元素已经被放到首部。

第二步选择 k =n,其中 n 是数组 arr 的长度,然后反转整个数组,此时该元素已经被放到尾部。

通过以上两步操作,我们可以将当前数组的最大值放到尾部,然后将去掉尾部元素的数组作为新的处理对象,重复以上操作,直到处理对象的长度等于一,此时原数组已经完成排序,且需要的总操作数是 2×(n−1),符合题目要求。如果最大值已经在尾部,我们可以省略对应的操作。

分解出子问题就好做了。

class Solution {
public:
    vector<int> pancakeSort(vector<int>& arr) {
        vector<int> ret;
        for(int n = arr.size(); n > 1; n--){
            int index = max_element(arr.begin(), arr.begin() + n) - arr.begin();
            //返回的是地址,减去数组名是获得下标

            if(index == n - 1)   continue;
            reverse(arr.begin(), arr.begin() + index + 1);
            reverse(arr.begin(), arr.begin() + n);
            ret.push_back(index + 1);
            ret.push_back(n);
        }
        return ret;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值