子数组以及子字符串(子串)就是连续的序列。既然是连续,常使用到的方法就是使用滑动窗口,滑动窗口的滑动条件就是题目的要求,滑动条件可以借助有序的set、multiset或者无序的unordered_set等来实现。
对于子串的最值问题的求解常常会使用动态规划的思想,找出状态转移方程是关键。
滑动窗口参考链接:CSDN
对比子序列的题目:CSDN
1004. 最大连续1的个数 III
class Solution {
public:
int longestOnes(vector& nums, int k) {
int ones = 0;
vector counter(2,0);
queue q;
for(auto& i : nums)
{
q.push(i);
counter[i]++;
if (counter[0] <= k)
ones = std::max(ones, counter[0]+counter[1]);
else
{
while (counter[0] > k)
{
counter[q.front()]--;
q.pop();
}
}
}
return ones;
}
};
1438. 绝对差不超过限制的最长连续子数组
//链接:https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/solution/jue-dui-chai-bu-chao-guo-xian-zhi-de-zui-5bki/
class Solution {
public:
int longestSubarray(vector& nums, int limit) {
//数组中可能存在重复的数据,所以使用multiset
multiset s;
int n = nums.size();
int left = 0, right = 0;
int ret = 0;
while (right < n)
{
s.insert(nums[right]);
while (*s.rbegin() - *s.begin() > limit)
{
s.erase(s.find(nums[left++]));
}
ret = max(ret, right - left + 1);
right++;
}
return ret;
}
};
480. 滑动窗口中位数
class Solution {
public:
vector medianSlidingWindow(vector& nums, int k) {
vector r;
multiset s;//因为需要排序且可能存在重复的数据,所以选择multiset
int left = 0;
for(auto& i : nums)
{
s.insert(i);
if (s.size() < k)
{
continue;
}
else if (s.size() > k)
{
//这里是关键,题目https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/ 与此操作类似
s.erase(s.find(nums[left++]));
}
auto it = s.begin();
double mid = 0.0;
if (k & 0x01 == 1)
{
std::advance(it, k/2);
mid = *it;
}
else
{
std::advance(it, k/2-1);
mid += *it;
std::advance(it, 1);
mid += *it;
mid /= 2;
}
r.push_back(mid);
}
return r;
}
};
674. 最长连续递增序列
//使用滑动窗口
class Solution {
public:
int findLengthOfLCIS(vector& nums) {
int maxLen = 1, left = 0, right = 0;
for (;right < nums.size();++right)
{
if (right > 0)
{
if (nums[right] > nums[right-1])
maxLen = std::max(maxLen, right-left+1);
else
left = right;
}
}
return maxLen;
}
};
718. 最长重复子数组
class Solution {
public:
int findLength(vector& nums1, vector& nums2) {
int n = nums1.size(), m = nums2.size();
if (n*m == 0)
return 0;
//dp[i][j]:nums1的前i个元素和nums2的前j个元素的最长公共子数组的长度
vector> dp(n+1, vector(m+1,0));
//base case 上面的默认值已经进行了初始化,这里不用再进行单独初始化了
// for (int i=0;i<=n;++i)
// dp[i][0] = 0;
// for (int i=0;i<=m;++i)
// dp[0][i] = 0;
int maxLen = 0;
for (int i=1;i<=n;++i)
{
for (int j=1;j<=m;++j)
{
if (nums1[i-1] == nums2[j-1])
{
dp[i][j] = 1 + dp[i-1][j-1];
maxLen = std::max(maxLen, dp[i][j]);
}
}
}
return maxLen;
}
};
53. 最大子数组和
剑指 Offer 42. 连续子数组的最大和
class Solution {
public:
int maxSubArray(vector& nums) {
int pre = nums[0], max = pre;
for(int i=1;i < nums.size();i++){
pre = std::max(pre + nums[i],nums[i]);
max = std::max(max,pre);
}
return max;
}
};
2.7
2.8
2.9
2.10
2.11
3. 无重复字符的最长子串
剑指 Offer 48. 最长不含重复字符的子字符串
剑指 Offer II 016. 不含重复字符的最长子字符串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set w;
int left = 0, right = 0, len = 0;
while(right < s.length())
{
char c = s[right];
if (w.count(c))
{
len = std::max(len, right - left);
w.erase(s[left++]);
continue;
}
else
{
w.insert(c);
}
++right;
}
len = std::max(len, right - left);
return len;
}
};
动态规划的解答:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector dp(128,-1);//存储每个字符最后出现的位置
int i=0,j=0,res=0;
for(;j
76. 最小覆盖子串
//从leetcode 567 https://leetcode-cn.com/problems/permutation-in-string/ 中的方法二修改而来
//因为方法二不要求字符串中全部是小写字母
class Solution {
public:
string minWindow(string s, string t) {
//方法一
string result;
unordered_map need, window;
for (int i = 0;i
567. 字符串的排列
剑指 Offer II 014. 字符串中的变位词
/*
滑动窗口
由于排列不会改变字符串中每个字符的个数,所以只有当两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列。
根据这一性质,记 s1 的长度为 n,我们可以遍历 s2 中的每个长度为 n 的子串,判断子串和 s1
中每个字符的个数是否相等,若相等则说明该子串是 s1 的一个排列。
使用两个数组 cnt1 和 cnt2, cnt1 统计 s1 中各个字符的个数,cnt2统计当前遍历的子串中各个字符的个数。
由于需要遍历的子串长度均为 n,我们可以使用一个固定长度为n 的滑动窗口来维护 cnt2 :
滑动窗口每向右滑动一次,就多统计一次进入窗口的字符,少统计一次离开窗口的字符。
然后,判断 cnt1 是否与 cnt2相等,若相等则意味着 s1 的排列之一是 s2 的子串。
https://leetcode-cn.com/problems/permutation-in-string/solution/zi-fu-chuan-de-pai-lie-by-leetcode-solut-7k7u/
*/
class Solution {
public:
bool checkInclusion(string s1, string s2) {
//方法一
// int n = s1.length(), m = s2.length();
// if (n > m) {
// return false;
// }
// vector cnt1(26), cnt2(26);
// for (int i = 0; i < n; ++i) {
// ++cnt1[s1[i] - 'a'];
// ++cnt2[s2[i] - 'a'];
// }
// if (cnt1 == cnt2) {
// return true;
// }
// for (int i = n; i < m; ++i) {
// ++cnt2[s2[i] - 'a'];
// --cnt2[s2[i - n] - 'a'];
// if (cnt1 == cnt2) {
// return true;
// }
// }
// return false;
//方法二,速度稍低于方法一
unordered_map need, window;
for (int i = 0;i
剑指 Offer II 015. 字符串中的所有变位词
438. 找到字符串中所有字母异位词
class Solution {
public:
vector findAnagrams(string s, string p) {
vector r;
do
{
int n = p.length(), m = s.length();
if (n > m)
{
break;
}
vector cnt1(26), cnt2(26);
for (int i = 0; i < n; ++i)
{
++cnt1[p[i] - 'a'];
++cnt2[s[i] - 'a'];
}
if (cnt1 == cnt2)
{
r.push_back(0);
}
for (int i = n; i < m; ++i)
{
++cnt2[s[i] - 'a'];
--cnt2[s[i - n] - 'a'];
if (cnt1 == cnt2)
{
r.push_back(i-n+1);
}
}
}while(0);
return r;
}
};
187. 重复的DNA序列
class Solution {
public:
vector findRepeatedDnaSequences(string s) {
int n = s.length();
unordered_map m;
unordered_set st;
for (int i=0;i<=n-10;++i)
{
string str = s.substr(i,10);
if (m.count(str))
st.insert(str);
m[str]++;
}
return vector(st.begin(), st.end());
}
};
1044. 最长重复子串
Rabin-Karp算法《算法导论3rd-p580》
参考链接:
https://leetcode-cn.com/problems/longest-duplicate-substring/solution/wei-rao-li-lun-rabin-karp-er-fen-sou-suo-3c22/
【字符串哈希】字符串哈希入门
//参考链接:https://leetcode-cn.com/problems/longest-duplicate-substring/solution/wei-rao-li-lun-rabin-karp-er-fen-sou-suo-3c22/
class Solution {
public:
int n;
//选择的是一个素数,相当于Rabin-Karp算法中的31进制,《算法导论3rd-p580》介绍的是十进制
unsigned long long prime = 31;
string longestDupSubstring(string s) {
n = s.size();
int l = 1;
int r = n - 1;
int pos = -1;
int len = 0;
auto find = [&](int len)
{
unsigned long long hash = 0;
unsigned long long power = 1;
//将[0,len)这个len长度的字符串作为模式串,也就是RK算法中所说的P[1...m]《算法导论3rd-p580》
for (int i = 0; i < len; i++)
{
hash = hash * prime + (s[i] - 'a');
power *= prime;
}
unordered_set exist{ hash };
for (int i = len; i < n; i++)
{
hash = hash * prime - power * (s[i - len] - 'a') + (s[i] - 'a');
//如果已经存在该hash,则说明存在一个长度为 len 的字符串s.substr(i-len+1, len)等于 s.substr(0, len)
if (exist.count(hash))
return (i - len + 1);
exist.insert(hash);
}
return -1;
};
//二分查找
while (l <= r)
{
//将索引[0,mid)范围内长度为mid字符串作为模式串,然后在[1, n-1](l的初始值为1;r的初始值为n-1)查找是否存在与模式串相同的字符串
int mid = (l + r) / 2;
int start = find(mid);
if (start != -1)
{
//在[1, n-1]内找到与模式串相同的子串,也就是存在重复子串,需要增加模式串的长度(也就是扩展上面的mid)来看是否存在更长的重复子串,要扩展模式串的长度就需要递增左边界
len = mid;
pos = start;
l = mid + 1;
}
else
{
//在[1, n-1]内没有找到与模式串相同的子串,此时需要缩短模式串的长度(也就是缩短上面的mid),要缩短模式串的长度就需要递减右边界
r = mid - 1;
}
}
if (pos == -1)
return "";
else
return s.substr(pos, len);
}
};
5. 最长回文子串
class Solution {
public:
string longestPalindrome(string s) {
//方法一
// string res;
// for (int i = 0; i < s.size(); i++)
// {
// // 以 s[i] 为中心的最长回文子串
// string s1 = palindrome(s, i, i);
// // 以 s[i] 和 s[i+1] 为中心的最长回文子串
// string s2 = palindrome(s, i, i + 1);
// res = res.size() > s1.size() ? res : s1;
// res = res.size() > s2.size() ? res : s2;
// }
// return res;
//方法二,马拉车
string T = preProcess(s);
int n = T.length();
int *P = new int[n];
int C = 0, R = 0;
int maxLen = 0, maxC = 0;
for (int i = 1; i < n - 1; i++)
{
int i_mirror = 2 * C - i;
if (R > i)
{
P[i] = std::min(R - i, P[i_mirror]);// 防止超出 R
}
else
{
P[i] = 0;// 等于 R 的情况
}
// 碰到之前讲的三种情况时候,需要利用中心扩展法
while (T[i + 1 + P[i]] == T[i - 1 - P[i]])
{
P[i]++;
}
// 判断是否需要更新 R
if (i + P[i] > R)
{
C = i;
R = i + P[i];
}
if (P[i] > maxLen)
{
maxLen = P[i];
maxC = i;
}
}
int start = (maxC - maxLen) / 2; //最开始讲的求原字符串下标
return s.substr(start, maxLen);
}
string palindrome(string& s, int l, int r) {
// 防止索引越界
while (l >= 0 && r < s.size() && s[l] == s[r])
{
// 向两边展开
l--; r++;
}
// 返回以 s[l] 和 s[r] 为中心的回文子串
return s.substr(l + 1, r - l - 1);
}
//原字符串:abcba ===> ^#a#b#c#b#a#$
string preProcess(string s) {
int n = s.length();
if (n == 0)
{
return "^$";
}
string ret = "^";
for (int i = 0; i < n; i++)
{
ret.push_back('#');
ret.push_back(s[i]);
}
ret.append("#$");
return ret;
}
};
647. 回文子串
class Solution {
public:
int countSubstrings(string s) {
int count = 0;
for (int i = 0; i < s.size(); i++)
{
count += palindrome(s, i, i) + palindrome(s, i, i + 1);
}
return count;
}
int palindrome(string& s, int l, int r) {
int count = 0;
// 防止索引越界
while (l >= 0 && r < s.size() && s[l] == s[r])
{
++count;
// 向两边展开
l--; r++;
}
return count;
}
string palindromeString(string& s, int l, int r) {
// 防止索引越界
while (l >= 0 && r < s.size() && s[l] == s[r])
{
// 向两边展开
l--; r++;
}
return s.substr(l+1,r-l-1);
}
};
131. 分割回文串
class Solution {
public:
vector> partition(string s) {
vector path;
vector> result;
helper(s,0,path,result);
return result;
}
// [begin, end]
void helper(const string& str, int begin, vector& path, vector>& result)
{
if (begin == str.size())
{
result.emplace_back(path);
return;
}
for (int end=begin;end end)
{
return false;
}
while (begin < end)
{
if (str[begin++] != str[end--])
{
return false;
}
}
return true;
}
};
剑指 Offer II 094. 最少回文分割
132. 分割回文串 II
//参考题目:https://leetcode-cn.com/problems/M99OJA/
class Solution {
public:
int minCut(string s) {
//方法一:借鉴 剑指 Offer II 086. 分割回文子字符串,https://leetcode-cn.com/problems/M99OJA/ 超时
// vector path;
// int min = INT_MAX;
// dfs(s,0,path,min);
// return min-1;
//方法二,https://leetcode-cn.com/problems/palindrome-partitioning-ii/solution/fen-ge-hui-wen-chuan-ii-by-leetcode-solu-norx/
// https://leetcode-cn.com/problems/palindrome-partitioning-ii/solution/wei-rao-li-lun-yu-chu-li-dong-tai-gui-hu-akpu/
/********************************************************************************************
PalindromeTable[i][j] 表示 s[i..j] 是否为回文串
dp[i] 表示 s[0..i] 的最小分割
状态转移方程:dp[i] 遍历每一个j(0<=j> PalindromeTable(n, vector(n, true));
//PalindromeTable[i][j]表示 s[i..j] 是否为回文串
for (int i = n - 1; i >= 0; --i)
{
for (int j = i + 1; j < n; ++j)
{ //从两边到中间来判断是否是回文
PalindromeTable[i][j] = (s[i] == s[j]) && PalindromeTable[i+1][j-1];
}
}
vector dp(n, INT_MAX);
for (int i = 0; i < n; ++i)
{
//字符串s[0][i]本来就是回文字符串,不需要分割,故 dp[i] = 0
if (PalindromeTable[0][i])
{
dp[i] = 0;
}
else
{ // 0<=j& path, int& min)
{
if (begin == str.size())
{
min = min > path.size() ? path.size() : min;
return;
}
for (int end=begin;end end)
{
return false;
}
while (begin < end)
{
if (str[begin++] != str[end--])
{
return false;
}
}
return true;
}
};
剑指 Offer II 019. 最多删除一个字符得到回文
680. 验证回文字符串 Ⅱ
class Solution {
public:
bool validPalindrome(string s) {
bool isValidPalindrome = false;
int n = s.length(), outBegin = 0, outEnd = 0;
isValidPalindrome = isPalindrome(s,0,n-1,outBegin,outEnd);
if (isValidPalindrome)
{
return true;
}
else
{
int tmp1,tmp2;
isValidPalindrome = (isPalindrome(s,outBegin,outEnd-1,tmp1,tmp2) || isPalindrome(s,outBegin+1,outEnd,tmp1,tmp2));
}
return isValidPalindrome;
}
// [begin, end]
bool isPalindrome(const string& str, int begin, int end, int& outBegin, int& outEnd)
{
if (begin > end)
{
outBegin = begin;
outEnd = end;
return false;
}
while (begin < end)
{
if (str[begin] != str[end])
{
outBegin = begin;
outEnd = end;
return false;
}
++begin;
--end;
}
return true;
}
};
926. 将字符串翻转到单调递增
剑指 Offer II 092. 翻转字符
class Solution {
public:
int minFlipsMonoIncr(string s) {
// 方法一
// int n = s.length();
// //dp[i][0]表示第i个元素被设置为0,使得s为单调递增的最小翻转次数
// //dp[i][1]表示第i个元素被设置为1,使得s为单调递增的最小翻转次数
// vector> dp(n, vector(2,0));
// //base case
// if (s[0]=='0')
// dp[0][0] = 0, dp[0][1] = 1;
// else
// dp[0][0] = 1, dp[0][1] = 0;
// for (int i=1;i dp[n-1][1] ? dp[n-1][1] : dp[n-1][0];
// 方法二,不用dp数组,而用两个变量进行优化
int n = s.length(), pre0, pre1, cur0, cur1;
//base case
if (s[0]=='0')
pre0 = 0, pre1 = 1;
else
pre0 = 1, pre1 = 0;
for (int i=1;i pre1 ? pre1 : pre0;
}
};
3.13
3.14
3.15
3.16
3.17
3.18
3.19
3.20
3.21
3.22
3.23
3.24
3.25
3.26
3.27
3.28
3.29
3.30
3.31