mirror of
https://gitlab.com/game-loader/hugo.git
synced 2025-04-20 05:52:07 +08:00
leetcode update
This commit is contained in:
parent
d85bfc486c
commit
3fe46dc882
@ -15634,25 +15634,30 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## day231 2024-10-15
|
## day231 2024-10-15
|
||||||
### 2938. Separate Black and White Balls
|
|
||||||
|
### 2938. Separate Black and White Balls
|
||||||
|
|
||||||
There are n balls on a table, each ball has a color black or white.
|
There are n balls on a table, each ball has a color black or white.
|
||||||
|
|
||||||
You are given a 0-indexed binary string s of length n, where 1 and 0 represent black and white balls, respectively.
|
You are given a 0-indexed binary string s of length n, where 1 and 0 represent black and white balls, respectively.
|
||||||
|
|
||||||
In each step, you can choose two adjacent balls and swap them.
|
In each step, you can choose two adjacent balls and swap them.
|
||||||
|
|
||||||
Return the minimum number of steps to group all the black balls to the right and all the white balls to the left.
|
Return the minimum number of steps to group all the black balls to the right and all the white balls to the left.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 题解
|
### 题解
|
||||||
|
|
||||||
本题起初想到可以遍历一遍数组统计0的个数,再遍历数组将前面的1和后面的0交换直到到达0的个数的位置。但这样好像麻烦了一些,统计0的个数其实没有必要,直接使用双指针,分别从首尾开始遍历数组,尾指针遇到的0和首指针遇到的1交换,直到两个指针相遇即可。原因在于尾指针每次遇到0就将其和1交换,则尾指针指向位置之后的数组可以确保是全1的,同理首指针之前的数组可以确保是全0的。二者相遇时相遇位置之后的数组为全1,之前的为全0,就已经满足题目条件了。
|
本题起初想到可以遍历一遍数组统计0的个数,再遍历数组将前面的1和后面的0交换直到到达0的个数的位置。但这样好像麻烦了一些,统计0的个数其实没有必要,直接使用双指针,分别从首尾开始遍历数组,尾指针遇到的0和首指针遇到的1交换,直到两个指针相遇即可。原因在于尾指针每次遇到0就将其和1交换,则尾指针指向位置之后的数组可以确保是全1的,同理首指针之前的数组可以确保是全0的。二者相遇时相遇位置之后的数组为全1,之前的为全0,就已经满足题目条件了。
|
||||||
|
|
||||||
遍历过程中,尾指针遇到0停下,移动首指针直到遇到1,计算二者距离并加和到结果中,继续移动尾指针直到头尾相遇结束。
|
遍历过程中,尾指针遇到0停下,移动首指针直到遇到1,计算二者距离并加和到结果中,继续移动尾指针直到头尾相遇结束。
|
||||||
|
|
||||||
### 代码
|
### 代码
|
||||||
```cpp
|
|
||||||
|
```cpp
|
||||||
class Solution {
|
class Solution {
|
||||||
public:
|
public:
|
||||||
long long minimumSteps(string s) {
|
long long minimumSteps(string s) {
|
||||||
@ -15675,3 +15680,249 @@ public:
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## day232 2024-10-16
|
||||||
|
|
||||||
|
### 1405. Longest Happy String
|
||||||
|
|
||||||
|
A string s is called happy if it satisfies the following conditions:
|
||||||
|
|
||||||
|
s only contains the letters 'a', 'b', and 'c'.
|
||||||
|
s does not contain any of "aaa", "bbb", or "ccc" as a substring.
|
||||||
|
s contains at most a occurrences of the letter 'a'.
|
||||||
|
s contains at most b occurrences of the letter 'b'.
|
||||||
|
s contains at most c occurrences of the letter 'c'.
|
||||||
|
Given three integers a, b, and c, return the longest possible happy string. If there are multiple longest happy strings, return any of them. If there is no such string, return the empty string "".
|
||||||
|
|
||||||
|
A substring is a contiguous sequence of characters within a string.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 题解
|
||||||
|
|
||||||
|
本题要求构造一个最长的happy字符串,可以使用给定数量的a,b,c来构造。唯一的限制是不能出现连续三个相同字符。则可以用贪心来解决。每次都给当前字符串添加上满足限制的当前剩余数量最多的字符,直到无法在满足限制的条件下添加任何字符或字符全部用光为止。
|
||||||
|
|
||||||
|
因为题中只有三个字符,则可以每次都给三个字符排序,排序后挨个遍历,找到满足条件的字符添加到字符串末尾。
|
||||||
|
|
||||||
|
当然用最大堆同样也是可以的,使用最大堆要先判断堆顶字符是否连续三个,若是连续三个,则临时弹出堆顶,用新的堆顶字符构造字符串,再将原来的堆顶恢复。若新的堆顶字符个数为0,则无法继续构造。
|
||||||
|
|
||||||
|
### 代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Solution {
|
||||||
|
public:
|
||||||
|
string longestDiverseString(int a, int b, int c) {
|
||||||
|
vector<pair<int, char>> chars = {{a, 'a'}, {b, 'b'}, {c, 'c'}};
|
||||||
|
|
||||||
|
string result;
|
||||||
|
while (true) {
|
||||||
|
// 每次都重新排序三个字符
|
||||||
|
sort(chars.begin(), chars.end(), greater<pair<int, char>>());
|
||||||
|
|
||||||
|
bool added = false;
|
||||||
|
for (auto& [count, ch] : chars) {
|
||||||
|
if (count == 0) continue; // 跳过数量为0的字符
|
||||||
|
|
||||||
|
int n = result.size();
|
||||||
|
if (n >= 2 && result[n-1] == ch && result[n-2] == ch) {
|
||||||
|
continue; // 如果会造成三个连续相同字符,尝试下一个字符
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ch;
|
||||||
|
count--;
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added) break; // 如果无法添加任何字符,结束循环
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## day233 2024-10-17
|
||||||
|
|
||||||
|
### 670. Maximum Swap
|
||||||
|
|
||||||
|
You are given an integer num. You can swap two digits at most once to get the maximum valued number.
|
||||||
|
|
||||||
|
Return the maximum valued number you can get.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 题解
|
||||||
|
|
||||||
|
本题要想交换得到最大的数字,需要将前面的小数字与后面的大数字交换,要交换前面的小数字就需要后面有比它大的数字可供交换,交换时与后面的比它大的数字中最大的那个交换。如果最大的数字有多个,最与最后面的数字交换可以得到最大值(因为小的数字会被换到后面,换的位越低相对换到一个比较高的位数字整体就会更大)。则反向遍历数组,记录到当前数字的后面数字的最大值和对应位置,如果当前数字大于最大值则更新最大值和位置。再从头遍历数组,找到第一个后面最大值大于自己的数字并按照记录的位置执行交换即得最终结果。
|
||||||
|
|
||||||
|
### 代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public:
|
||||||
|
int maximumSwap(int num) {
|
||||||
|
struct backMax {
|
||||||
|
int digit;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
string numstring = to_string(num);
|
||||||
|
int len = numstring.length();
|
||||||
|
vector<backMax> backVector(len);
|
||||||
|
|
||||||
|
// 从后向前遍历,记录每个位置后面的最大数字及其索引
|
||||||
|
int current = 0;
|
||||||
|
int curindex = len - 1;
|
||||||
|
for (int i = len - 1; i >= 0; i--) {
|
||||||
|
if (numstring[i] - '0' > current) {
|
||||||
|
current = numstring[i] - '0';
|
||||||
|
curindex = i;
|
||||||
|
}
|
||||||
|
backVector[i] = {current, curindex};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从前向后遍历,找到第一个可以交换的位置
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (backVector[i].digit > numstring[i] - '0') {
|
||||||
|
// 交换数字
|
||||||
|
swap(numstring[i], numstring[backVector[i].index]);
|
||||||
|
break; // 只交换一次
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将字符串转回整数
|
||||||
|
return stoi(numstring);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## day224 2024-10-18
|
||||||
|
|
||||||
|
### 2044. Count Number of Maximum Bitwise-OR Subsets
|
||||||
|
|
||||||
|
Given an integer array nums, find the maximum possible bitwise OR of a subset of nums and return the number of different non-empty subsets with the maximum bitwise OR.
|
||||||
|
|
||||||
|
An array a is a subset of an array b if a can be obtained from b by deleting some (possibly zero) elements of b. Two subsets are considered different if the indices of the elements chosen are different.
|
||||||
|
|
||||||
|
The bitwise OR of an array a is equal to a[0] OR a[1] OR ... OR a[a.length - 1] (0-indexed).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 题解
|
||||||
|
|
||||||
|
注意本题的位运算是or运算,因此两个数字or运算只会比两个数字都大。从二进制的角度来看,要得到最大值就是让二进制位1的个数最多,而或运算的特点是只要有一个数字在某一二进制位上为1,最终结果在该二进制位上就为1。因此可以根据哪个二进制位为1将数组中的数字分类。如第一个二进制位为1的数字为一组,第二个二进制位为1的数字为1组。最终要得到最大数字只需从各组中挑出一个数字组合即可。
|
||||||
|
|
||||||
|
但还存在数字重复的问题。首先考虑同一个数字在不同组的情况。如3同时拥有两个二进制位,可以同时放入两个组中。但通过一个简单例子如只有3,1两个数即可发现,可以直接从各个二进制位对应的组中选择数字组合,不需要去重。考虑3,1。3有两个二进制位,1有一个二进制位,因此第一个二进制位有两个数字(1,3),第二个二进制位有一个数字(3)。在组合的时候仍然直接2\*1即得最终的组合个数。因为当从两个组中同时选择3时,相当于3自身这个组合。最终组合就是3和3,1共两个。
|
||||||
|
|
||||||
|
再考虑同一个数字在数组中多次出现的问题。如出现了3个2。此时可以在选择时先将其当作一个2处理,最后再将组合结果与重复的2的组合个数相乘。对于重复的n个数字其组合个数为2^n-1(排除空集)。则若目前根据二进制位可分为三个组,从三个组中分别选择一个数字后,如果选择的数字中包含2,就将这个组合个数乘2的组合数。
|
||||||
|
|
||||||
|
有了以上思路,则按二进制位分组后组合,枚举所有可能性并加和。
|
||||||
|
|
||||||
|
一通分析好像十分精彩,但看了看题目限制,数组长度最大16,数字大小却最大可以达到10^5。这种情况下用上面这种方式分组最差情况时间复杂度能达到n^10甚至更高。既然长度才16,那为何不直接算出能取得的最大值(所有数字做或运算)然后暴力枚举所有子数组的组合,能取到最大值的就计数。这不比上面说的一大堆简单多了,时间复杂度也就只有2^n。暴力枚举时还可以先将多个相同数字视为同一个,这样需要枚举的数目又减少了一部分。枚举可以使用递归加回溯的方式。可以做一些优化如当如果已经可以得到最大值时,后面未枚举的数字可以直接计算能得到的组合个数并加和。
|
||||||
|
|
||||||
|
则递归函数需要传递剩余的数字个数,当前已经遍历的部分可以取得的组合个数,以及剩余未遍历的数组。当遍历到剩余数字个数为0时返回。
|
||||||
|
|
||||||
|
### 代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Solution {
|
||||||
|
public:
|
||||||
|
int countMaxOrSubsets(vector<int>& nums) {
|
||||||
|
// 计算所有元素的 Bitwise OR 的最大值
|
||||||
|
int max_or = 0;
|
||||||
|
for(auto num : nums){
|
||||||
|
max_or |= num;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计每个唯一元素的频率
|
||||||
|
unordered_map<int, int> freq_map;
|
||||||
|
for(auto num : nums){
|
||||||
|
freq_map[num]++;
|
||||||
|
}
|
||||||
|
vector<pair<int, int>> uniq_nums;
|
||||||
|
for(auto &[num, cnt] : freq_map){
|
||||||
|
uniq_nums.emplace_back(make_pair(num, cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归枚举所有子集,处理重复元素
|
||||||
|
long long total = 0; // 使用 long long 防止溢出
|
||||||
|
backtrack(0, 0, uniq_nums, max_or, 1, total);
|
||||||
|
return (int)total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void backtrack(int index, int current_or, const vector<pair<int, int>>& uniq_nums, int target_or, long long multiplier, long long& total){
|
||||||
|
// 基本情况:所有唯一元素都已被考虑
|
||||||
|
if(index == uniq_nums.size()){
|
||||||
|
if(current_or == target_or){
|
||||||
|
total += multiplier;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前唯一元素及其频率
|
||||||
|
int num = uniq_nums[index].first;
|
||||||
|
int count = uniq_nums[index].second;
|
||||||
|
|
||||||
|
// 不包含当前元素
|
||||||
|
backtrack(index + 1, current_or, uniq_nums, target_or, multiplier, total);
|
||||||
|
|
||||||
|
// 包含当前元素
|
||||||
|
long long ways = (1LL << count) - 1;
|
||||||
|
int new_or = current_or | num;
|
||||||
|
// 递归处理下一个唯一元素,并将组合数作为乘数传递
|
||||||
|
backtrack(index + 1, new_or, uniq_nums, target_or, multiplier * ways, total);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 总结
|
||||||
|
|
||||||
|
这是优化了数字重复的情况,但实际上只纯粹的递归枚举所有组合,组合数的或运算和为目标和就将计数加一得到的代码非常简洁
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public:
|
||||||
|
int maxOr; // 用于存储所有元素的 Bitwise OR 的最大值
|
||||||
|
int countMaxOr; // 用于统计达到 maxOr 的子集数量
|
||||||
|
|
||||||
|
int countMaxOrSubsets(vector<int>& nums) {
|
||||||
|
// 计算所有元素的 Bitwise OR 的最大值
|
||||||
|
maxOr = 0;
|
||||||
|
for(auto num : nums){
|
||||||
|
maxOr |= num;
|
||||||
|
}
|
||||||
|
|
||||||
|
countMaxOr = 0;
|
||||||
|
|
||||||
|
backtrack(nums, 0, 0);
|
||||||
|
|
||||||
|
return countMaxOr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 递归函数
|
||||||
|
void backtrack(const vector<int>& nums, int index, int current_or){
|
||||||
|
// 如果已经遍历完所有元素
|
||||||
|
if(index == nums.size()){
|
||||||
|
if(current_or == maxOr){
|
||||||
|
countMaxOr++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择不包含当前元素,继续递归下一个元素
|
||||||
|
backtrack(nums, index + 1, current_or);
|
||||||
|
|
||||||
|
// 选择包含当前元素,更新当前的 OR 值,继续递归下一个元素
|
||||||
|
backtrack(nums, index + 1, current_or | nums[index]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
而且在本题长度最多16的情况下,对可能存在多个重复数字情况的优化并没有效率上的提升,反而因为增加了更多的预处理和递归过程中的判断不如这个简单的方式来的快。因此任何优化都要结合具体场景,如果现在题目输入变为数组长度长得多但存在很多重复数字的情况,那么对重复数字的情况进行优化可能就会比单纯的直接枚举要快。
|
||||||
|
|
||||||
|
另外则是,尽管有时候我们思考过程中可能会出现一些错误的或不那么好的思路,但这些过程并非没有意义,只有经过了这样的思考,才能更加深入的理解问题出在哪里,明白什么样的问题用什么样的思想。
|
||||||
|
Loading…
Reference in New Issue
Block a user