leetcode update

This commit is contained in:
gameloader 2025-01-14 09:59:55 +08:00
parent 07ab2fd1d5
commit 578d680475

View File

@ -20852,20 +20852,21 @@ For example, "ace" is a subsequence of "abcde".
如此反复直到遍历到数组末尾或者头指针已经遇到了全部的26个字母为止。
### 代码
```cpp
```cpp
class Solution {
public:
int countPalindromicSubsequence(string s) {
vector<bool> visited(26, false);
int count = 0;
vector<bool> visited(26, false);
int count = 0;
int result = 0;
for (int i = 0; i < s.length() && count < 26; i++) {
char curr = s[i];
if (!visited[curr - 'a']) {
visited[curr - 'a'] = true;
count++;
for (int j = s.length() - 1; j > i; j--) {
if (s[j] == curr) {
unordered_set<char> middle;
@ -20876,18 +20877,21 @@ public:
}
}
result += middle.size();
break;
break;
}
}
}
}
return result;
}
};
```
## day303 2025-01-05
### 2381. Shifting Letters II
### 2381. Shifting Letters II
You are given a string s of lowercase English letters and a 2D integer array shifts where shifts[i] = [starti, endi, directioni]. For every i, shift the characters in s from the index starti to the index endi (inclusive) forward if directioni = 1, or shift the characters backward if directioni = 0.
Shifting a character forward means replacing it with the next letter in the alphabet (wrapping around so that 'z' becomes 'a'). Similarly, shifting a character backward means replacing it with the previous letter in the alphabet (wrapping around so that 'a' becomes 'z').
@ -20897,54 +20901,56 @@ Return the final string after all such shifts to s are applied.
![010523U95OIXuCVA](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/010523U95OIXuCVA.png)
### 题解
本题最直观的方法就是遍历shifts数组根据shifts数组的内容来改变s中对应范围的字符。
但考虑如果shift中的范围有重叠并且在前一个shift中方向为0而在后一个shift中方向为1则重叠范围内的字符实际上并未发生变化。因此为了避免在这种情况下来回变化字符可以想办法记录每个位置处字符的变化量这里以方向1的变化为+1以方向0的变化为-1这样只需根据最终记录的变化量的值直接变化对应字符即可。为了避免每次记录某个范围内的变化值时都要遍历该范围内的所有记录并改变数值可以使用线段树使用线段树可以只记录某个范围内的变化量而不用直接将范围内所有数字的具体值算出来到shift全部遍历完成后再一边遍历s字符串一边计算每个位置具体的变化量并改变字符。
### 代码
```cpp
```cpp
class SegmentTree {
private:
vector<int> tree;
int n;
vector<int> tree;
int n;
void build(int node, int start, int end) {
if (start == end) {
tree[node] = 0;
tree[node] = 0;
return;
}
int mid = (start + end) / 2;
build(2 * node + 1, start, mid);
build(2 * node + 2, mid + 1, end);
tree[node] = 0;
build(2 * node + 1, start, mid);
build(2 * node + 2, mid + 1, end);
tree[node] = 0;
}
void update(int node, int start, int end, int l, int r, int val) {
if (r < start || l > end) return;
if (r < start || l > end) return;
if (l <= start && end <= r) {
tree[node] += val;
tree[node] += val;
return;
}
int mid = (start + end) / 2;
update(2 * node + 1, start, mid, l, r, val);
update(2 * node + 2, mid + 1, end, l, r, val);
update(2 * node + 1, start, mid, l, r, val);
update(2 * node + 2, mid + 1, end, l, r, val);
}
int query(int node, int start, int end, int idx) {
if (start == end) return tree[node];
if (start == end) return tree[node];
int mid = (start + end) / 2;
if (idx <= mid) {
return tree[node] + query(2 * node + 1, start, mid, idx);
return tree[node] + query(2 * node + 1, start, mid, idx);
} else {
return tree[node] + query(2 * node + 2, mid + 1, end, idx);
return tree[node] + query(2 * node + 2, mid + 1, end, idx);
}
}
public:
SegmentTree(int size) {
n = size;
tree.resize(4 * n);
build(0, 0, n - 1);
tree.resize(4 * n);
build(0, 0, n - 1);
}
void rangeUpdate(int l, int r, int val) {
@ -20960,7 +20966,7 @@ class Solution {
public:
string shiftingLetters(string s, vector<vector<int>>& shifts) {
int n = s.length();
SegmentTree st(n);
SegmentTree st(n);
for (const auto& shift : shifts) {
int start = shift[0];
@ -20981,27 +20987,28 @@ public:
private:
char shiftChar(char c, int shift) {
shift = shift % 26;
if (shift < 0) shift += 26;
int newChar = c - 'a' + shift;
newChar = newChar % 26;
if (newChar < 0) newChar += 26;
return 'a' + newChar;
shift = shift % 26;
if (shift < 0) shift += 26;
int newChar = c - 'a' + shift;
newChar = newChar % 26;
if (newChar < 0) newChar += 26;
return 'a' + newChar;
}
};
```
### 总结
这种需要频繁处理区间变化的问题还可以使用差分数组本题如果使用差分数组则效率要高得多因为差分数组的实现和处理更简单。差分数组的思想建立在前缀和的基础上我们构建一个差分数组diff数组中每个位置的数字表示当前位置的数字比前一个位置大多少如diff\[i]=3表示i比i-1的数字大3差分数组为什么可以很方便的用于处理区间变化问题呢我们考虑一个简单情况差分数组初始化为全0表示所有位置的数字都一样此时假如将diff\[i]变为3那么i比i-1大3此时我们可以发现i后面的位置虽然在差分数组中的值仍然为0但由于i发生了变化则后面的数字在差分数组为0的情况下表示和i的大小相同也就自然跟随i发生了变化。这样就实现了大于等于i的全部位置都比i-1大3的效果。那么如果要只将中间某一段变为比i前面的数字大3应该怎么办呢。假如我们想让i~j的数字加3j以后的数字不变则在diff\[i]加3的情况下让j+1位置不加3则只需让diff\[j+1]减3抵消掉前面的加3带来的效果即可大于j+1的位置也就同样跟随着j+1自然产生了抵消效果。最终计算每个位置的变化量时只需计算diff数组的前缀和即可是非常巧妙的思路。
这里的思想是我们只需知道变化的路径就可以知道最终的值相当于初始值的总体变化量在一些特定场景下相对变化量变化频繁可以不必每次都记下绝对变化量仅将相对变化全部记录下来最终再去计算出绝对变化量会大大增加计算效率。当然这需要有一个固定的初始值比如本题其实默认在开始之前字符是没发生变化的即初始为0。
```cpp
```cpp
class Solution {
public:
string shiftingLetters(string s, vector<vector<int>>& shifts) {
int n = s.length();
vector<int> prefix(n + 1, 0);
vector<int> prefix(n + 1, 0);
for (auto &shift : shifts) {
int a = shift[0];
@ -21015,7 +21022,7 @@ public:
int currentShift = 0;
for (int i = 0; i < n; i++) {
currentShift = (currentShift + prefix[i]) % 26;
s[i] = 'a' + (s[i] - 'a' + currentShift + 26) % 26;
s[i] = 'a' + (s[i] - 'a' + currentShift + 26) % 26;
}
return s;
@ -21023,8 +21030,11 @@ public:
};
```
## day304 2025-01-06
### 1769. Minimum Number of Operations to Move All Balls to Each Box
### 1769. Minimum Number of Operations to Move All Balls to Each Box
You have n boxes. You are given a binary string boxes of length n, where boxes[i] is '0' if the ith box is empty, and '1' if it contains one ball.
In one operation, you can move one ball from a box to an adjacent box. Box i is adjacent to box j if abs(i - j) == 1. Note that after doing so, there may be more than one ball in some boxes.
@ -21036,6 +21046,7 @@ Each answer[i] is calculated considering the initial state of the boxes.
![0106FLjfNN7WT45O](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0106FLjfNN7WT45O.png)
### 题解
本题要求出将全部球移动到各个位置需要的移动次数最直观的可以直接暴力求解对每个位置从头遍历数组将全部为1的位置和当前位置的距离求和。
显然这样浪费了太多的信息,如即时只遍历一遍,其实也能确定各个位置之间的相对距离,因为第一遍遍历是以第一个盒子的位置为基准的,后面的任意两个盒子之间的距离都可以通过这两个盒子分别与第一个盒子的相对距离通过做差计算出来。这也可以使我们想起昨天做过的差分数组的问题,可以想到本题同样只需找出从前一个盒子到下一个盒子时的改变量,再在前一个盒子的结果已知的情况下增减改变量就能得出下一个盒子对应的结果。
@ -21045,7 +21056,8 @@ Each answer[i] is calculated considering the initial state of the boxes.
通过这个例子可以发现在从左向右遍历的过程中先算出第一个位置的总距离并记录下为1的盒子的总数。再在向右遍历的过程中减去在该位置右面的为1的盒子个数加上在该位置左面的盒子个数即得当前位置的总距离。用两个变量分别保存在左面和在右面为1的盒子个数在遍历到为1的盒子时就将右面的个数减1左面的个数加1。
### 代码
```cpp
```cpp
class Solution {
public:
vector<int> minOperations(string boxes) {
@ -21073,8 +21085,11 @@ public:
}
};
```
## day305 2025-01-07
### 1408. String Matching in an Array
### 1408. String Matching in an Array
Given an array of string words, return all strings in words that is a substring of another word. You can return the answer in any order.
A substring is a contiguous sequence of characters within a string
@ -21082,17 +21097,19 @@ A substring is a contiguous sequence of characters within a string
![0107lUtkjOdxYtdh](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0107lUtkjOdxYtdh.png)
### 题解
本题是简单题但只是说题面比较简单思路也比较直接因为题目限制words最多100个因此直接暴力求解也是没有问题的。
但我们仍然希望能有一种方法可以使得求解速度快一些判定一个字符串是不是另一个字符串的子字符串有很多种方法像经典的kmpBM算法Rabin-Karp算法此处我们使用Rabin-Karp算法使用该算法的好处在于可以一次算出所有字符串的哈希值保存起来后续就不用重复计算模式串的哈希值了只需根据字符串长度去字符串中使用滑动窗口计算哈希值尝试匹配即可。
### 代码
```cpp
```cpp
class Solution {
public:
const int MOD = 1000000007;
const int BASE = 31;
// 计算整个字符串的哈希值
long long getStringHash(const string& s) {
long long hash = 0;
@ -21103,7 +21120,7 @@ public:
}
return hash;
}
// 计算文本串中长度为len的子串的哈希值
long long getWindowHash(const string& s, int start, int len) {
long long hash = 0;
@ -21118,18 +21135,18 @@ public:
vector<string> stringMatching(vector<string>& words) {
int n = words.size();
vector<string> result;
for (int i = 0; i < n; i++) {
bool isSubstring = false;
int len1 = words[i].length();
long long patternHash = getStringHash(words[i]);
for (int j = 0; j < n && !isSubstring; j++) {
if (i == j) continue;
int len2 = words[j].length();
if (len1 > len2) continue;
// 在较长的字符串中滑动窗口
for (int k = 0; k + len1 <= len2; k++) {
if (patternHash == getWindowHash(words[j], k, len1)) {
@ -21138,12 +21155,12 @@ public:
}
}
}
if (isSubstring) {
result.push_back(words[i]);
}
}
return result;
}
};
@ -21155,13 +21172,13 @@ public:
这也提醒我们在实际应用过程中一定要注意具体场景和限制条件不要把知识学死算法更多的是提供一些巧妙的解决问题的思路但并不一定适于所有场景就好像芯片一样专门针对具体领域设计的领域专用芯片在某个特定领域效果未必比最先进的通用芯片差。同样这也是在不同场景可能会有很多通用算法的变体如KMP变体BM变体一样。根据具体场景分析不同情况下的最优解并根据限制条件优化算法需要我们一直铭记在心。
```cpp
```cpp
class Solution {
public:
bool isSubstring(const string& pattern, const string& text) {
int n = text.length();
int m = pattern.length();
for (int i = 0; i <= n - m; i++) {
int j;
for (j = 0; j < m; j++) {
@ -21173,11 +21190,11 @@ public:
}
return false;
}
vector<string> stringMatching(vector<string>& words) {
vector<string> result;
int n = words.size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && isSubstring(words[i], words[j])) {
@ -21186,8 +21203,307 @@ public:
}
}
}
return result;
}
};
```
## day306 2025-01-08
### 3042. Count Prefix and Suffix Pairs I
You are given a 0-indexed string array words.
Let's define a boolean function isPrefixAndSuffix that takes two strings, str1 and str2:
isPrefixAndSuffix(str1, str2) returns true if str1 is both a
prefix
and a
suffix
of str2, and false otherwise.
For example, isPrefixAndSuffix("aba", "ababa") is true because "aba" is a prefix of "ababa" and also a suffix, but isPrefixAndSuffix("abc", "abcd") is false.
Return an integer denoting the number of index pairs (i, j) such that i < j, and isPrefixAndSuffix(words[i], words[j]) is true.
![01087uvaVMzNKhtB](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/01087uvaVMzNKhtB.png)
### 题解
先按照题意构造isPrefixAndSuffix函数本题isPrefixAndSuffix函数是关键如何快速的比对str1是否是str2的前缀和后缀字符串影响整体的解答效率。本题使用字符串哈希或者直接根据str1的长度进行判断都是可行的。考虑题目条件给出的数据量不大直接根据str1的长度通过字符比较确定str1是否是str2的前缀和后缀即可。
### 代码
```cpp
class Solution {
public:
bool isPrefixAndSuffix(string str1, string str2){
int len = str1.size();
int len2 = str2.size();
for(int i=0;i<len;i++){
if(str1[i] != str2[i] || str1[len-i-1] != str2[len2-i-1]){
return false;
}
}
return true;
}
int countPrefixSuffixPairs(vector<string>& words) {
int result = 0;
int wordlen = words.size();
for(int i=wordlen-1;i>=0;i--){
for(int j=0;j<i;j++){
if(isPrefixAndSuffix(words[j],words[i])){
result++;
}
}
}
return result;
}
};
```
## day307 2025-01-10
### 916. Word Subsets
You are given two string arrays words1 and words2.
A string b is a subset of string a if every letter in b occurs in a including multiplicity.
For example, "wrr" is a subset of "warrior" but is not a subset of "world".
A string a from words1 is universal if for every string b in words2, b is a subset of a.
Return an array of all the universal strings in words1. You may return the answer in any order.
![0110so7opHq6lIQN](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0110so7opHq6lIQN.png)
### 题解
本题要找到words1中满足words2中所有字符串均为该字符串子集的字符串作为结果。读题可知这里的子集只要求str1中包含str2中所有的字符且对应字符的个数大于等于str2。没有任何顺序要求因此可以想到本题可以通过对字符计数的方式来判断子集是否成立。进一步题目要找words2中所有字符串均为其子集的字符串就要求对于words2中的每个字符串words1中的这个字符串均包含全部全部字符且个数大于等于words2中的字符串。则可先统计words2中单个字符串包含的各个字符的最大个数再遍历words1中的字符串如果words1中的字符串满足字符个数大于等于统计的全部字符的最大值说明words2中任何一个字符串均可以成为words1中该字符串的子集该字符串满足条件。
注意题目条件限制每个字符串的长度不超过10因在统计最大值时可以仅对字符串中出现过的字符进行更新而不必更新全部26个字母算是一个小小的优化。
### 代码
```cpp
class Solution {
public:
vector<string> wordSubsets(vector<string>& words1, vector<string>& words2) {
vector<int> maxCount(26, 0);
for (const string& word : words2) {
vector<int> currCount(26, 0);
// 只统计当前字符串中出现的字符
for (char c : word) {
currCount[c - 'a']++;
}
// 只更新出现过的字符的最大值
for (char c : word) {
maxCount[c - 'a'] = max(maxCount[c - 'a'], currCount[c - 'a']);
}
}
vector<string> result;
for (const string& word : words1) {
vector<int> count(26, 0);
for (char c : word) {
count[c - 'a']++;
}
bool isUniversal = true;
for (int i = 0; i < 26; i++) {
if (count[i] < maxCount[i]) {
isUniversal = false;
break;
}
}
if (isUniversal) {
result.push_back(word);
}
}
return result;
}
};
```
## day308 2025-01-11
### 1400. Construct K Palindrome Strings
Given a string s and an integer k, return true if you can use all the characters in s to construct k palindrome strings or false otherwise.
![0111YVANRmanuGki](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0111YVANRmanuGki.png)
### 题解
本题考虑如何构造回文字符串,注意单独的一个字符满足回文字符串的要求,因此如果想获得最多的回文串可以直接将整个字符串全部拆分为单个字符,就可以得到由单个字符组成的字符串长度个数的回文串,因此可以用贪心法,一个一个的取回文串,再将剩余的全部字符作为一个回文串。
但要注意字符能构造成回文串的前提是剩余的字符中每个字符的个数中最多只有一个字符个数是奇数如果多个字符个数是奇数则无法构成回文串因为两个奇数的字符必定不能对称分布因此可以统计出所有为奇数的字符再根据k从这些奇数字符中挨个取出一个使其变为偶数直到取到第k个此时如果剩余字符满足不超过一个字符是奇数则可以构造出k个回文串否则不能。也就是说如果为奇数的字符个数小于等于k个则一定能构造成功否则一定不能。
则本题先考虑字符串长度如果字符串长度小于k显然不能构造出k个回文串大于等于k时按照上面的思路尝试构造回文串成功则返回true否则返回false。
### 代码
```cpp
class Solution {
public:
bool canConstruct(string s, int k) {
if (s.length() < k) return false;
if (s.length() == k) return true;
vector<int> count(26, 0);
for (char c : s) {
count[c - 'a']++;
}
int oddCount = 0;
for (int i = 0; i < 26; i++) {
if (count[i] % 2 == 1) {
oddCount++;
}
}
if (oddCount > k) return false;
return true;
}
};
```
## day309 2025-01-12
### 2116. Check if a Parentheses String Can Be Valid
A parentheses string is a non-empty string consisting only of '(' and ')'. It is valid if any of the following conditions is true:
It is ().
It can be written as AB (A concatenated with B), where A and B are valid parentheses strings.
It can be written as (A), where A is a valid parentheses string.
You are given a parentheses string s and a string locked, both of length n. locked is a binary string consisting only of '0's and '1's. For each index i of locked,
If locked[i] is '1', you cannot change s[i].
But if locked[i] is '0', you can change s[i] to either '(' or ')'.
Return true if you can make s a valid parentheses string. Otherwise, return false.
![0112lVVn6vsyWRUD](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0112lVVn6vsyWRUD.png)
### 题解
本题就是在常规的判断括号字符串是否匹配的基础上增加了锁,来确定某个位置的括号字符是否可以随意改变,如果有锁则不能改变,无锁则可以任意改变。首先确定当字符串的长度为奇数时一定不能匹配成功,仅在字符串长度为偶数的情况下才可能匹配成功。
考虑对于无锁的位置,因为可以随意改变,其实不用考虑,重点关注有锁的位置,对于有锁的位置,我们可以使用无锁的位置或者有锁的与其可以匹配的括号来匹配它,那么只要始终可以匹配上就说明最终可以匹配成功。而左右括号需要的可以被匹配的位置方向不同,对于有锁右括号来说,需要其左面有足够的剩余的左括号或者无锁位置才能保证右括号匹配成功,对于有锁左括号则相反。因此可以正反分别遍历一遍数组,一次用来检查右括号是否可以匹配成功,一次用来检查左括号是否可以匹配成功。都成功则最终可以成功,否则不成功。
### 代码
```cpp
class Solution {
public:
bool canBeValid(string s, string locked) {
int n = s.length();
if (n % 2) return false;
// 从左到右检查右括号是否过多
int balance = 0;
for (int i = 0; i < n; i++) {
if (locked[i] == '0' || s[i] == '(') {
balance++;
} else {
balance--;
}
if (balance < 0) return false;
}
// 从右到左检查左括号是否过多
balance = 0;
for (int i = n-1; i >= 0; i--) {
if (locked[i] == '0' || s[i] == ')') {
balance++;
} else {
balance--;
}
if (balance < 0) return false;
}
return true;
}
};
```
## day310 2025-01-13
### 3223. Minimum Length of String After Operations
You are given a string s.
You can perform the following process on s any number of times:
Choose an index i in the string such that there is at least one character to the left of index i that is equal to s[i], and at least one character to the right that is also equal to s[i].
Delete the closest character to the left of index i that is equal to s[i].
Delete the closest character to the right of index i that is equal to s[i].
Return the minimum length of the final string s that you can achieve.
![0113qhIkf1ekZWQH](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0113qhIkf1ekZWQH.png)
### 题解
本题考虑题中所述的操作可以发现无需知道字符所在的具体位置当一个字符在字符串中的个数大于等于3个时处在中间位置的字符一定满足题目所述的条件并可以将两侧的字符删除并可如此反复直到字符的个数小于3个由此发现只需统计字符的个数当字符个数为奇数时则可以将中间位置的字符两侧不断删除直到只剩下正中间的字符而为偶数时则不断删除后最终会剩余两个字符。再将所有字符的剩余个数加和即得结果。
### 代码
```cpp
class Solution {
public:
int minimumLength(string s) {
vector<int> chars(26,0);
for(const auto& ch : s){
chars[ch-'a']++;
}
int result = 0;
for(const int& count : chars){
if(count > 0){
if(count % 2 == 0){
result += 2;
}else{
result += 1;
}
}
}
return result;
}
};
```
## day311 2025-01-14
### 2657. Find the Prefix Common Array of Two Arrays
You are given two 0-indexed integer permutations A and B of length n.
A prefix common array of A and B is an array C such that C[i] is equal to the count of numbers that are present at or before the index i in both A and B.
Return the prefix common array of A and B.
A sequence of n integers is called a permutation if it contains all integers from 1 to n exactly once.
![0114gjhZRYENaC7W](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0114gjhZRYENaC7W.png)
### 题解
本题考虑A和B都仅包含1~n的不重复数字并且n的长度最大不超过50要想快速了解A和B的前缀数组中包含哪些共同的数字我们就需要将这些不重复数字想办法保存起来每个数字的位置互不冲突还能方便的了解到哪些数字是二者都有的如果使用数组的话满足位置互不冲突但不能快速的得出哪些数字是二者共有的数组只能靠遍历来判断此时可以考虑位运算因为对于每个数字我们仅需要知道它在前缀数组中是否出现统计共同数字的个数时也不需要知道共同的数字具体都是哪些仅需要个数则用0表示未出现用1表示出现。这样做的好处在于在寻找共同数字的个数时可以通过对两个二进制数直接取按位与相同位置的1会自动保留即表示同时在A和B的前缀数组中出现过统计个数仅需统计按位与后得到的数字中二进制位1的个数。
统计某个数字中二进制位1的个数最直接的方法就是移位直到为0也可以使用Brian Kernighan算法每次消除数字最右侧的1。这里使用Brian Kernighan算法。
注意n的范围到50因此要使用一个64位的数字才能有足够的二进制位来表示每个数字因此要使用unsigned long int。
### 代码
```cpp
class Solution {
public:
// 使用Brian Kernighan算法计算二进制中1的个数
int countOnes(unsigned long int n) {
int count = 0;
while (n) {
n = n & (n - 1); // 消除最右边的1
count++;
}
return count;
}
vector<int> findThePrefixCommonArray(vector<int>& A, vector<int>& B) {
int n = A.size();
vector<int> C(n);
unsigned long int maskA = 0, maskB = 0;
for (int i = 0; i < n; i++) {
maskA |= ((unsigned long int)1 << A[i]);
maskB |= ((unsigned long int)1 << B[i]);
C[i] = countOnes(maskA & maskB);
}
return C;
}
};
```