三、重排元素
1.套路
2.题目描述
3.学习经验
1.重排元素要任意两个相邻的数不相等,可以利用贪心加奇偶位置构造思想,先取数量最多的数,然后把它排到答案的偶数位置,其余数填补剩下的偶数位置,若偶数位置填完,则填奇数位置。
1. 984. 不含AAA或BBB的字符串(中等)
984. 不含 AAA 或 BBB 的字符串 - 力扣(LeetCode)
思想
1.给定两个整数 a
和 b
,返回 任意 字符串 s
,要求满足:
s
的长度为a + b
,且正好包含a
个'a'
字母与b
个'b'
字母;- 子串
'aaa'
没有出现在s
中; - 子串
'bbb'
没有出现在s
中。
2.贪心思想,优先选择数量多的放进去,可以把代码再优化一下,变成以下逻辑: - (1)a>b,则放入"aab"
- (2)a=b,则放入"ab"
- (3)a<b,则放入"bba"
- (4)最后单独放剩下元素,题目保证一定有答案
代码
class Solution {
public:
string strWithout3a3b(int a, int b) {
string res = "";
int maxa = 0;
int maxb = 0;
bool tag = true;
if (a < b)
tag = false;
while (a || b) {
if (a >= b) {
maxa = 2;
maxb = 1;
} else {
maxa = 1;
maxb = 2;
}
int lena = min(maxa, a);
int lenb = min(maxb, b);
if (tag) {
if (lena == 2) {
res += "aa";
} else if (lena == 1) {
res += "a";
}
if (lenb == 2) {
res += "bb";
} else if (lenb == 1) {
res += "b";
}
} else {
if (lenb == 2) {
res += "bb";
} else if (lenb == 1) {
res += "b";
}
if (lena == 2) {
res += "aa";
} else if (lena == 1) {
res += "a";
}
}
a -= lena;
b -= lenb;
}
return res;
}
};
优化代码:
class Solution {
public:
string strWithout3a3b(int a, int b) {
string res = "";
while (a && b) {
if (a > b) {
res += "aab";
a -= 2;
--b;
} else if (a == b) {
res += "ab";
--a;
--b;
} else {
res += "bba";
b -= 2;
--a;
}
}
while (a--) {
res += "a";
}
while (b--) {
res += "b";
}
return res;
}
};
2. 767. 重构字符串(中等,学习构造思想)
思想
1.给定一个字符串 s
,检查是否能重新排布其中的字母,使得两相邻的字符不同。
返回 s
的任意可能的重新排列。若不可行,返回空字符串 ""
。
2.首先肯定能想到求出现次数最多的字母的次数maxn
,然后其他字符就插入,要大于等吗maxn-1
,否则不可行。
我的想法是优先选择次数最多的,但是为了不想邻,记录下标,如果等于之前下标,则选次次数最多的,想到最大堆。
但是这题只要两两不相邻,故可考虑奇偶,即次数最多的排偶数,然后其余的接着偶数排,偶数排完排奇数
代码
class Solution {
public:
struct Node {
int cnt;
char c;
int lastIdx;
Node(int _cnt, char _c, int _lastIdx)
: cnt(_cnt), c(_c), lastIdx(_lastIdx) {}
};
struct cmp {
bool operator()(const Node& a, const Node& b) { return a.cnt < b.cnt; }
};
string reorganizeString(string s) {
int n = s.size();
map<char, int> mp;
int maxn = 0;
string res = "";
for (auto& c : s) {
++mp[c];
maxn = max(maxn, mp[c]);
}
if (maxn - 1 > n - maxn)
return "";
priority_queue<Node, vector<Node>, cmp> pq;
for (auto t : mp) {
pq.push(Node(t.second, t.first, -5));
}
int id = 0;
while (!pq.empty()) {
auto tmp = pq.top();
pq.pop();
if (tmp.lastIdx != id - 1) {
res.push_back(tmp.c);
--tmp.cnt;
if (tmp.cnt > 0)
pq.push(Node(tmp.cnt, tmp.c, id));
} else {
auto tmp2 = pq.top();
pq.pop();
res.push_back(tmp2.c);
--tmp2.cnt;
if (tmp2.cnt > 0)
pq.push(Node(tmp2.cnt, tmp2.c, id));
pq.push(Node(tmp));
}
++id;
}
return res;
}
};
构造优化:
typedef pair<char, int> PCI;
bool cmp(const PCI& a, const PCI& b) { // 写在类外
return a.second > b.second;
}
class Solution {
public:
string reorganizeString(string s) {
int n = s.size();
map<char, int> mp;
for (auto& c : s)
++mp[c];
vector<PCI> vec(mp.begin(), mp.end());
sort(vec.begin(), vec.end(), cmp);
int maxn = vec[0].second;
if (maxn - 1 > n - maxn)
return "";
string res(n, '0');
int id = 0;
for (auto tmp : vec) {
char c = tmp.first;
int cnt = tmp.second;
while (cnt--) {
res[id] = c;
id += 2;
if (id >= n)
id = 1;
}
}
return res;
}
};
3. 1054. 距离相等的条形码(中等)
思想
1.在一个仓库里,有一排条形码,其中第 i
个条形码为 barcodes[i]
。
请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。
2.跟[[九.堆(优先队列)#2. 767. 重构字符串(中等,学习构造思想)]]一模一样
代码
typedef pair<int, int> PII;
bool cmp(const PII& a, const PII& b) { return a.second > b.second; }
class Solution {
public:
vector<int> rearrangeBarcodes(vector<int>& barcodes) {
int n = barcodes.size();
map<int, int> mp;
for (auto& x : barcodes)
++mp[x];
vector<PII> vec(mp.begin(), mp.end());
sort(vec.begin(), vec.end(), cmp);
vector<int> res(n, 0);
int id = 0;
for (auto& tmp : vec) {
int val = tmp.first;
int cnt = tmp.second;
while (cnt--) {
res[id] = val;
id += 2;
if (id >= n)
id = 1;
}
}
return res;
}
};
四、第K小/大
1.套路
1.部分题目也可以用二分解决。
2.类似于top-k,但是能用二分的肯定二分快
3.堆中先存初始元素,然后进行弹出再插入的动态更新,直到弹出第K个,即为答案。
但是插入会产生重复,可以利用哈希去重[[九.堆(优先队列)#1. 264. 丑数II(中等,去重想哈希)]]或者预先插入一部分,规定更新顺序[[九.堆(优先队列)#3. 373. 查找和最小的K对数字(中等,学习)]]
2.题目描述
3.学习经验
1. 264. 丑数II(中等,去重想哈希)
思想
1.给你一个整数 n
,请你找出并返回第 n
个 丑数 。
丑数 就是质因子只包含 2
、3
和 5
的正整数。
2.等价于取第n个最小的丑数,所以小顶堆,然后栈顶元素乘2,3,5再放入堆,但是会产生重复,利用哈希去重(这里集合就行)
代码
class Solution {
public:
typedef long long ll;
int nthUglyNumber(int n) {
int id = 0;
priority_queue<ll, vector<ll>, greater<ll>> pq;
set<ll> st;
pq.push(1);
ll res = 0;
while (id < n) {
res = pq.top();
pq.pop();
++id;
if (!st.count(res * 2)) {
st.insert(res * 2);
pq.push(res * 2);
}
if (!st.count(res * 3)) {
st.insert(res * 3);
pq.push(res * 3);
}
if (!st.count(res * 5)) {
st.insert(res * 5);
pq.push(res * 5);
}
}
return res;
}
};
2. 378. 有序矩阵中第K小的元素(中等)
378. 有序矩阵中第 K 小的元素 - 力扣(LeetCode)
思想
1.给你一个 n x n
矩阵 matrix
,其中每行和每列元素均按升序排序,找到矩阵中第 k
小的元素。
请注意,它是 排序后 的第 k
小元素,而不是第 k
个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2)
的解决方案。
2.用top-k小最大堆时间复杂度是O(n2logk)
,比二分慢很多
代码
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
int n = matrix.size();
priority_queue<int> pq;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
pq.push(matrix[i][j]);
if (pq.size() > k)
pq.pop();
}
}
return pq.top();
}
};
3. 373. 查找和最小的K对数字(中等,学习)
373. 查找和最小的 K 对数字 - 力扣(LeetCode)
思想
1.给定两个以 非递减顺序排列 的整数数组 nums1
和 nums2
, 以及一个整数 k
。
定义一对值 (u,v)
,其中第一个元素来自 nums1
,第二个元素来自 nums2
。
请找到和最小的 k
个数对 (u1,v1)
, (u2,v2)
... (uk,vk)
。
2.因为此题两个数组都是有序的,所以当前最小的数对(i,j)
,那么下一个次小的一定是(i+1,j)
或者(i,j+1)
,很符合堆拿出来一个,然后利用它再放进去,然后动态弹出和插入,但是会产生重复问题 ,即当前的(i,j)
会由(i-1,j)
和(i,j-1)
两次插入,所以我们人为规定只插入(i,j+1)
,那么i
不能动了,就要预先把所有的(i,0)
都插入最小堆。
代码
class Solution {
public:
struct Node {
int sum;
int id1;
int id2;
Node(int _sum, int _id1, int _id2) : sum(_sum), id1(_id1), id2(_id2) {}
};
struct cmp {
bool operator()(const Node& a, const Node& b) { return a.sum > b.sum; }
};
vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2,
int k) {
int n1 = nums1.size(), n2 = nums2.size();
priority_queue<Node, vector<Node>, cmp> pq;
for (int i = 0; i < n1; ++i) {
pq.push(Node(nums1[i] + nums2[0], i, 0));
}
int id = 0;
vector<vector<int>> res(k);
while (id < k) {
auto tmp = pq.top();
pq.pop();
res[id] = vector<int>{nums1[tmp.id1], nums2[tmp.id2]};
if (tmp.id2 + 1 < n2) {
pq.push(Node(nums1[tmp.id1] + nums2[tmp.id2 + 1], tmp.id1,
tmp.id2 + 1));
}
++id;
}
return res;
}
};