leetcode刷题记录28(2023-12-30)【递增的三元子序列(最小前缀、最大后缀) | 反转字符串(双指针) | 两个数组的交集 II(哈希表、双指针、排序) | 有序矩阵中第K小的元素】

334. 递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

提示:

1 < = n u m s . l e n g t h < = 5 ∗ 1 0 5 1 <= nums.length <= 5 * 10^5 1<=nums.length<=5105
− 2 31 < = n u m s [ i ] < = 2 31 − 1 -2^{31} <= nums[i] <= 2^{31} - 1 231<=nums[i]<=2311

进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?

主要思路就是记录一个leftMin数组,和rightMax数组,分别表示,从左边开始最小的数,和从右边开始最大的数,这样,如果当前位大于左边最小的,小于右边最大的,就找到了这样一个序列。

#include <vector>

using namespace std;

class Solution
{
public:
    bool increasingTriplet(vector<int> &nums)
    {
        if (nums.size() < 3)
            return false;
        vector<int> leftMin(nums.size());
        vector<int> rightMax(nums.size());

        leftMin[1] = nums[0];
        for (int i = 2; i < nums.size(); i++)
        {
            leftMin[i] = min(leftMin[i - 1], nums[i - 1]);
        }

        rightMax[nums.size() - 2] = nums[nums.size() - 1];
        for (int i = nums.size() - 3; i >= 0; i--)
        {
            rightMax[i] = max(rightMax[i + 1], nums[i + 1]);
        }

        for (int i = 1; i < nums.size() - 1; i++)
        {
            if (nums[i] > leftMin[i] && nums[i] < rightMax[i])
            {
                return true;
            }
        }

        return false;
    }
};

题解1给出了另外一种空间复杂度为O(1)的做法,但是有点抽象,主要利用了贪心的思想:我们希望使得first和second尽量接近,同时二者尽可能的小,这样我们才更容易找到third。

放一个评论区老哥对该方法的解释:

第二种方法用人话说就是:
赋初始值的时候,已经满足second > first了,现在找第三个数third
(1) 如果third比second大,那就是找到了,直接返回true
(2) 如果third比second小,但是比first大,那就把second指向third,然后继续遍历找third
(3) 如果third比first还小,那就把first指向third,然后继续遍历找third(这样的话first会跑到second的后边,但是不要紧,因为在second的前边,老first还是满足的)

#include <vector>
#include <climits>
using namespace std;

class Solution
{
public:
    bool increasingTriplet(vector<int> &nums)
    {
        if (nums.size() < 3)
            return false;
        int first = nums[0];
        int second = INT_MAX;
        for (int i = 1; i < nums.size(); i++)
        {
            if (nums[i] > second)
            {
                return true;
            }
            else if (nums[i] > first)
            {
                second = nums[i];
            }
            else
            {
                first = nums[i];
            }
        }
        return false;
    }
};

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

示例 2:

输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

提示:

1 < = s . l e n g t h < = 1 0 5 1 <= s.length <= 10^5 1<=s.length<=105
s[i] 都是 ASCII 码表中的可打印字符

这是一道easy的题目,并不难,采用双指针方法,从两侧向中间遍历。

#include <vector>

using namespace std;

class Solution
{
public:
    void reverseString(vector<char> &s)
    {
        int i = 0, j = s.size() - 1;
        while (i < j)
        {
            swap(s[i], s[j]);
            i++;
            j--;
        }
    }
};

350. 两个数组的交集 II

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

提示:

1 < = n u m s 1. l e n g t h , n u m s 2. l e n g t h < = 1000 1 <= nums1.length, nums2.length <= 1000 1<=nums1.length,nums2.length<=1000
0 < = n u m s 1 [ i ] , n u m s 2 [ i ] < = 1000 0 <= nums1[i], nums2[i] <= 1000 0<=nums1[i],nums2[i]<=1000

进阶:

如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

这道题目很简单,但是题目中有一些可以优化的点,进阶题目很有意思。

自己写的代码如下:

class Solution
{
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2)
    {
        map<int, int> mp1;
        for (int i = 0; i < nums1.size(); i++)
        {
            mp1[nums1[i]]++;
        }
        map<int, int> mp2;
        for (int i = 0; i < nums2.size(); i++)
        {
            mp2[nums2[i]]++;
        }
        vector<int> res;
        for (int i = 0; i < nums1.size(); i++)
        {
            int num = min(mp1[nums1[i]], mp2[nums1[i]]);
            for (int j = 0; j < num; j++)
            {
                res.push_back(nums1[i]);
            }
            mp1[nums1[i]] = 0;
        }
        return res;
    }
};

看了题解2之后,感觉有些笨拙了,遍历了三次数组,时间、空间开销都比较大:

class Solution
{
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2)
    {
        map<int, int> mp1;
        for (int i = 0; i < nums1.size(); i++)
        {
            mp1[nums1[i]]++;
        }

        vector<int> res;
        for (int i = 0; i < nums2.size(); i++)
        {
            auto it = mp1.find(nums2[i]);
            if (it != mp1.end() && it->second > 0)
            {
                res.push_back(nums2[i]);
                it->second--;
            }
        }
        return res;
    }
};

同时,由于用到了哈希表,因此,哈希表所占用的内存越小越好,因此,可以把小的数组存放到map中:

class Solution
{
public:
    vector<int> intersect(vector<int> &nums1, vector<int> &nums2)
    {
        if (nums1.size() > nums2.size())
        { // 如果nums1长度更大,调换参数位置
            return intersect(nums2, nums1);
        }
        unordered_map<int, int> mp1; // 使用unordered_map会更好一些
        for (int i = 0; i < nums1.size(); i++)
        {
            mp1[nums1[i]]++;
        }

        vector<int> res;
        for (int i = 0; i < nums2.size(); i++)
        {
            auto it = mp1.find(nums2[i]);
            if (it != mp1.end() && it->second > 0)
            {
                res.push_back(nums2[i]);
                it->second--;
            }
        }
        return res;
    }
};

然后来看一下进阶问题:
如果给定的数组已经排好序呢?你将如何优化你的算法?

排序号的数组可以采用双指针法,更加节约内存,同时避免了查找map的过程,节约了时间

如果 nums1 的大小比 nums2 小,哪种方法更优?

使用map的方法。

如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

使用map方法,把nums1存进哈希表,分块顺序访问nums2。

371. 两整数之和

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。

示例 1:

输入:a = 1, b = 2
输出:3

示例 2:

输入:a = 2, b = 3
输出:5

提示:

-1000 <= a, b <= 1000

这道题目题解写的很简洁,但是和自己的思维方式不太一样,没有想到。自己的代码如下:

#include <iostream>

using namespace std;

class Solution
{
public:
    int getSum(int a, int b)
    {
        int mask = 1;		// 掩膜,用于获取特定的位
        int res = 0;		// 表示最终的结果
        int promotion = 0; 	// 表示32位,每一位对应的进位
        for (int i = 0; i < 32; i++)
        {
            res = res | ((mask & a) ^ (mask & b) ^ (mask & promotion));
            // cout << (((mask & a) & (mask & b)) << 1) <<endl;
            promotion = promotion | ((((mask & a) & (mask & b)) | ((mask & a) & (mask & promotion)) | ((mask & b) & (mask & promotion))) << 1);
            mask = mask << 1;
        }
        return res;
    }
};

int main()
{
    Solution sol;
    int res = sol.getSum(2, 3);
    cout << res << endl;
}

题解代码如下3

class Solution {
public:
    int getSum(int a, int b) {
        while (b != 0) {
            unsigned int carry = (unsigned int)(a & b) << 1;
            a = a ^ b;
            b = carry;
        }
        return a;
    }
};

378. 有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。

你必须找到一个内存复杂度优于 O(n2) 的解决方案。

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

提示:

n = = m a t r i x . l e n g t h n == matrix.length n==matrix.length
n = = m a t r i x [ i ] . l e n g t h n == matrix[i].length n==matrix[i].length
1 < = n < = 300 1 <= n <= 300 1<=n<=300
− 1 0 9 < = m a t r i x [ i ] [ j ] < = 1 0 9 -10^9 <= matrix[i][j] <= 10^9 109<=matrix[i][j]<=109
题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
1 < = k < = n 2 1 <= k <= n^2 1<=k<=n2

这道题目一开始采用贪心的做法来做,结果贪心策略选择错误了,不再介绍。参考了题解的做法,第一种就是归并排序的做法,比较好实现,唯一需要注意的就是小顶堆的使用:

// 方法1
class Solution
{
    struct Point
    {
        int val;
        int x;
        int y;
        Point(int val, int x, int y) : val(val), x(x), y(y) {}
        bool operator>(const Point &point) const
        {
            return this->val > point.val;
        }
    };

public:
    int kthSmallest(vector<vector<int>> &matrix, int k)
    {
        priority_queue<Point, vector<Point>, greater<Point>> que;
        for (int i = 0; i < matrix.size(); i++)
        {
            que.emplace(matrix[i][0], i, 0);
        }
        for (int i = 0; i < k - 1; i++)
        {
            Point now = que.top();
            que.pop();
            if (now.y < matrix[0].size() - 1)
            {
                que.push({matrix[now.x][now.y + 1], now.x, now.y + 1});
            }
        }
        return que.top().val;
    }
};

第2种做法采用了二分查找的方法,思路不难理解,但是实现过程中有许多小细节需要注意。

// 方法2
class Solution
{
    int getNumLessThanMid(vector<vector<int>> &matrix, int mid)
    {
        int res = 0;
        int frontier = 0;
        for (int i = matrix.size() - 1; i >= 0; i--)
        {
            for (int j = frontier; j < matrix[0].size(); j++)
            {
                if (matrix[i][j] > mid)
                {
                    break;
                }
                else
                {
                    frontier++;
                }
            }
            res += frontier;
        }
        return res;
    }

public:
    int kthSmallest(vector<vector<int>> &matrix, int k)
    {
        int l = matrix[0][0], r = matrix[matrix.size() - 1][matrix[0].size() - 1];
        while (l < r)
        {
            // 如果出现复数,则与 (l+r)/2 不同,int取整,默认是向0取整,所以中间结果中不要出现负数
            int mid = l + (r - l) / 2;
            int numLessThanMid = getNumLessThanMid(matrix, mid);
            // >和=的情况,都不要移动r,而是一点一点的移动l,
            // 这里不同于一般的二分查找,我们想要的元素需要在矩阵内部
            if (numLessThanMid >= k)
            {
                r = mid;
            }
            else
            {
                l = mid + 1;
            }
        }
        return l;
    }
};

  1. https://leetcode.cn/problems/increasing-triplet-subsequence/solutions/1204375/di-zeng-de-san-yuan-zi-xu-lie-by-leetcod-dp2r/ ↩︎

  2. https://leetcode.cn/problems/intersection-of-two-arrays-ii/solutions/327356/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution/ ↩︎

  3. https://leetcode.cn/problems/sum-of-two-integers/solutions/1017617/liang-zheng-shu-zhi-he-by-leetcode-solut-c1s3/ ↩︎

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherries Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值