I30. 串联所有单词的子串 - 力扣(LeetCode)
给定一个字符串 s
和一个字符串数组 words
。 words
中所有字符串 长度相同。
s
中的 串联子串 是指一个包含 words
中所有字符串以任意顺序排列连接起来的子串。
- 例如,如果
words = ["ab","cd","ef"]
, 那么"abcdef"
,"abefcd"
,"cdabef"
,"cdefab"
,"efabcd"
, 和"efcdab"
都是串联子串。"acdbef"
不是串联子串,因为他不是任何words
排列的连接。
返回所有串联子串在 s
中的开始索引。你可以以 任意顺序 返回答案。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。
这个题目就是 找到字符串中所有字母异位词 这个题目的升级版,具体可以看一下博客:
leetcode 刷题 - 最大连续1的个数 - 将 x 减到 0 的最小操作数 - 水果成篮 - 找到字符串中所有字母异位词-CSDN博客
其实就是把 找到字符串中所有字母异位词 这个题目 当中的 字符换成了 字符串。
所以,其实基本思想还是和 找到字符串中所有字母异位词 这个题目 一样的,只不过,hash表不能再用 数组来模拟了,要使用 hash容器。
而且,left 和 right 迭代器方式也不再是一步一步的向后移动了,而是一个子串的一个子串的移动。
而且,在这个题目当中,滑动窗口的 起始位置可能不是 从数组的最开始位置开始的,可能从 第二,第三··· 都有可能,但是,如果超过了 words 数组当中一个子串的 大小,那么后续就会是重复的。
所以,我们的滑动窗口,不能像 之前题目当中,只滑动一遍数组,而是要滑动 len 次数组(其实位置依次往后移一个字符),len就是words 当中子串的字符个数。
完整代码:
class Solution
{
public:
vector<int> findSubstring(string s, vector<string>& words)
{
vector<int> ret;
unordered_map<string, int> hash1; // 保存 words ⾥⾯所有单词的频次
for (auto& s : words) hash1[s]++;
int len = words[0].size(), m = words.size();
for (int i = 0; i < len; i++) // 执⾏ len 次
{
unordered_map<string, int> hash2; // 维护窗⼝内单词的频次
for (int left = i, right = i, count = 0; right + len <= s.size();
right += len)
{
// 进窗⼝ + 维护 count
string in = s.substr(right, len);
hash2[in]++;
if (hash1.count(in) && hash2[in] <= hash1[in]) count++;
// 判断
if (right - left + 1 > len * m)
{
// 出窗⼝ + 维护 count
string out = s.substr(left, len);
if (hash1.count(out) && hash2[out] <= hash1[out]) count--;
hash2[out]--;
left += len;
}
// 更新结果
if (count == m) ret.push_back(left);
}
}
return ret;
}
};
I76. 最小覆盖子串 - 力扣(LeetCode)
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
其实这个题目,和上述所说的两个题目是类似的,只不过在判断条件当中,上两个题目是 当 窗口不合法,然后出窗口,但是这个题目是 窗口合法,就更新结果,然后出窗口。
同样的,使用 left 和 right 两个指针,从开始来进行遍历,用两个 hash 表分别存储 s 和 t 两个字符串当中 各个 字符的数量。
right 依次向后走,把遍历的 每一个 s 当中的字符,记录在 自己 hash 表当中,也就是 hash 表当中计数器++。(进窗口)
当 right 向后迭代,窗口合法,就更新结果,然后 循环 left++,向后迭代,同样的,left 遍历的过的字符 来 出窗口,在 hash 的计数器--。一直left++ 到 窗口不合法。
判断两个 s 相对于 t 当中的在 t 当中在 s 当中存在的字符 的个数,要大于等于 t 当中对应字符的 数量,才称之为 s 当中的这个字符是 有效字符。
当有效字符 等于 t 当中字符种类数量,才认为 当前窗口合法。
判断 两个 hash 表 是否相等,可以使用上述 有效字符 的方式进行优化,具体就是:
我们可以用一个 count 记录 有效字符。什么是 有效字符呢?就是在我们所寻找的子串当中,有多少 种类 的字符,是和 目标字符是 一样的。如果有一个 种类 是一样的,count++。
所以,在 存储我们找出的子串当中 各个字符出现次数 的 hash 表当中,依旧按照 滑动窗口 当中的逻辑,进行 计数,只不过,如果是 和 目标字符 hash 当中有重复的,也就是当前的 计数器++ 的是 有效字符,那么就 count++。
当 count == 目标子串.size() 之时,采取更新结果,否则,当前找出的 这个子串 就不满足条件。
完整代码:
class Solution {
public:
string minWindow(string s, string t) {
char hash_s[128] = {0}; // 用于存储 s 字符串当中 各个字符出现的次数
char hash_t[128] = {0}; // 用于存储 t 字符串当中 各个字符出现的次数
//记录t字符种类数量 当前最小长度 当前最小程度的子串在 s 当中的起始位置
int kinds = 0, minlen = INT_MAX, begin = -1;
for(auto& e : t) if(hash_t[e]++ == 0) kinds++;
for(int left = 0, right = 0, count = 0; right < s.size();right++)
{
char right_str = s[right]; // 取出当前 right 指向的字符
hash_s[right_str]++; // 哈希表计数器++
if(hash_s[right_str] == hash_t[right_str]) count++; // 如果在上述计数器++之后
// 满足条件,count++
// 如果当前 s 当中的有效字符 和 t 当中的 字符种类相等
// 说明当前窗口 合法
// 更新结果,循环一直 left++, 直到 窗口不再合法
while(count == kinds)
{
if(right - left + 1 < minlen)
{
minlen = right - left + 1;
begin = left;
}
char left_str = s[left++];// 取出当前 left 指向的字符,然后 left++
if(hash_s[left_str] == hash_t[left_str]) count--; //如果left指向字符出窗口之后
// 当前字符个数不在和t相比是
// 大于等于 t 对应字符数量
hash_s[left_str]--;
}
}
if(begin == -1) return "";
else return s.substr(begin, minlen); // 如果有结果,就切割字符串,然后返回
}
};
I69. x 的平方根 - 力扣(LeetCode)
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
完整代码:
class Solution {
public:
int mySqrt(int x) {
if(x < 1) return 0; // 处理边界情况
int left = 1, right = x;
while(left < right)
{
// 找到 left 和 right 的中间数值
long long mid = left + (right - left + 1) / 2; // long long防止 mid * mid 溢出
if(mid * mid <= x) left = mid; // 刷新 左右区间
else right = mid -1;
}
return left;
}
};
I【模板】前缀和_牛客题霸_牛客网 (nowcoder.com)
描述
给你一个 n 行 m 列的矩阵 A ,下标从1开始。
接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2
请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,
输入描述:
第一行包含三个整数n,m,q.
接下来n行,每行m个整数,代表矩阵的元素
接下来q行,每行4个整数x1, y1, x2, y2,分别代表这次查询的参数
输出描述:
输出q行,每行表示查询结果。
示例1
输入:
3 4 3
1 2 3 4
3 2 1 0
1 5 7 8
1 1 2 2
1 1 3 3
1 2 3 4
复制
输出:
8
25
32
使用前缀和 -> 快速求出数组当中某一个连续区间的和。
先要预处理出来一个前缀和数组(dp数组),dp[i] 元素存储的是 原数组当中,从起始位置,到 i 位置 这个连续区间当中 元素之和。
所以,在以后 寻找的过程当中,只需要以 下标对应 dp 当中存储的和,相减即可。比如 :给定区间 [1, 3] ,那么这个区间当中的和就等于 dp[3] - dp[1]。
完整代码:
#include <iostream>
#include<vector>
using namespace std;
int main() {
int n,q;
cin >> n >> q;
vector<int> arr(n + 1);
vector<long long> dp(n + 1, 0); // long long 防止溢出
for(int i = 1;i <= n;i++) cin >> arr[i];
for(int i = 1;i <= n;i++) dp[i] = dp[i - 1] + arr[i];
int l , r;
while(q--)
{
cin >> l >> r;
cout << dp[r] - dp[l - 1] << endl;
}
return 0;
}
I【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com)
描述
给你一个 n 行 m 列的矩阵 A ,下标从1开始。
接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2
请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,
输入描述:
第一行包含三个整数n,m,q.
接下来n行,每行m个整数,代表矩阵的元素
接下来q行,每行4个整数x1, y1, x2, y2,分别代表这次查询的参数
输出描述:
输出q行,每行表示查询结果。
示例1
输入:
3 4 3
1 2 3 4
3 2 1 0
1 5 7 8
1 1 2 2
1 1 3 3
1 2 3 4
复制
输出:
8
25
32
和上述一样,使用 前缀和 来解决上述的问题。
dp[i][j] 表示,从 [1,1] 位置到 [i,j] 位置,这段区间当中所有的元素的和。
但是,dp 这个二维数组不好求,如果我们直接遍历来一个一个求的话,时间复杂度很高。
所以,其实 dp[i][j] 其实就是 A + B + C + D,这四个之和。单独求 A 其实很好求,找到 i -1 和 j -1 ,位置,就可以求出 dp[i-1][j-1] 就是 A 的面积。
因为单独求 C 和 B 不好求。
A + B + C + D = (A + B) + (A + C) + D - A
上述 (A + B) 和 (A + C) 就可以求出。
所以 : dp[ i ][ j ] = dp[ i-1 ][ j ] + dp[ i ][ j-1 ] + arr[ i ][ j ] +dp[ i-1 ][ j-1 ]
所以:
完整代码:
#include <iostream>
#include<vector>
using namespace std;
int main() {
int n = 0, m = 0, q = 0;
cin >> n >> m >> q;
vector<vector<int>> arr(n + 1, vector<int>(m + 1));
for(int i = 1; i <= n; i++)
for(int j = 1; i <= m ;j++)
cin >> arr[i][j];
vector<vector<long long>> dp(n + 1, vector<long long>(m + 1));
for(int i = 1; i <= n; i++)
for(int j = 1; i <= m ;j++)
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1];
int x1 , y1 , x2 , y2;
while(q--)
{
cin >> x1 >> y1 >> x2 >> y2;
cout << dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1] << endl;
}
return 0;
}