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<=5∗105
−
2
31
<
=
n
u
m
s
[
i
]
<
=
2
31
−
1
-2^{31} <= nums[i] <= 2^{31} - 1
−231<=nums[i]<=231−1
进阶:你能实现时间复杂度为 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;
}
};
https://leetcode.cn/problems/increasing-triplet-subsequence/solutions/1204375/di-zeng-de-san-yuan-zi-xu-lie-by-leetcod-dp2r/ ↩︎
https://leetcode.cn/problems/intersection-of-two-arrays-ii/solutions/327356/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution/ ↩︎
https://leetcode.cn/problems/sum-of-two-integers/solutions/1017617/liang-zheng-shu-zhi-he-by-leetcode-solut-c1s3/ ↩︎