收到读者私信说:为什么你的算法越讲越简单了?
我告诉他:因为你越来越聪明了!
今天要讲的算法,《算法导论》书上是看不到的,因为无论是思考过程还是代码实现上都是非常容易理解的,所以各大算法书上都不屑将它归为算法,但是它却作为职场面试,省赛水题的绝佳选择,它有一个比较优雅的名字叫 尺取法,英文把它叫 “two pointers”,也就是 “双指针” 的意思。
现在几点来着?四点?哈哈,早睡早起,方能养生!
【例题1】给定一个长度为 n ( 1 ≤ n ≤ 1 0 7 ) n (1 \le n \le 10^7) n(1≤n≤107) 的字符串 s s s,求一个最长的满足所有字符不重复的子串。
用 a n s ans ans 记录我们需要求的最大不重复子串的长度,用一个哈希表 h h h 来代表某个字符是否出现过,算法描述如下:
1)枚举子串的左端点 i = 0 → n − 1 i = 0 \to n-1 i=0→n−1;
2)清空哈希表 h h h;
3)枚举子串的右端点 j = i → n − 1 j = i \to n-1 j=i→n−1,如果当前这个字符 s j s_j sj 出现过(即 h [ s j ] = t r u e h[s_j] = true h[sj]=true),则跳出 j j j 的循环;否则,令 h [ s j ] = t r u e h[s_j] = true h[sj]=true,并且用当前长度去更新 a n s ans ans(即 a n s = m a x ( a n s , j − i + 1 ) ans= max(ans, j - i +1) ans=max(ans,j−i+1));
4)回到 2);
int getmaxlen(int n, char *str, int& l, int& r) {
int ans = 0, i, j, len;
memset(h, 0, sizeof(h));
for(i = 0; i < n; ++i) {
// 1)
j = i;
memset(h, false, sizeof(h)); // 2)
while(j < n && !h[str[j]]) {
h[ str[j] ] = true; // 3)
len = j - i + 1;
if(len > ans)
ans = len, l = i, r = j;
++j;
}
}
return ans;
}
int getmaxlen(int n, char *str, int& l, int& r) {
int ans = 0, i = 0, j = -1, len; // 1)
memset(h, 0, sizeof(h)); // 2)
while (j++ < n - 1) {
// 3)
++h[ str[j] ]; // 4)
while (h[ str[j] ] > 1) {
// 5)
--h[ str[i] ];
++i;
}
len = j - i + 1;
if(len > ans) // 6)
ans = len, l = i, r = j;
}
return ans;
}
i = 0, j = -1
,代表 s [ i : j ] s[i:j] s[i:j] 为一个空串,从空串开始枚举;h[ str[j] ] > 1
满足时,代表出现了重复字符,这时候左端点 i i i 推进,直到没有重复字符为止;j-i+1
;算法描述如下:
1)初始化 i = 0 i=0 i=0, j = i − 1 j=i-1 j=i−1,代表一开始 “尺子” 的长度为 0;
2)增加 “尺子” 的长度,即 j = j + 1 j = j +1 j=j+1;
3)判断当前这把 “尺子” [ i , j ] [i, j] [i,j] 是否满足题目给出的条件:
3.a)如果不满足,则减小 “尺子” 长度,即 i = i + 1 i = i + 1 i=i+1,回到 3);
3.b)如果满足,记录最优解,回到 2);
【例题2】给定 n ( n < 1 0 5 ) n (n \lt 10^5) n(n<105) 个正数 a i ( a i ≤ 1 0 4 ) a_i ( a_i \le 10^4) ai(ai≤104) 和一个正数 p ( p < 1 0 8 ) p(p < 10^8) p(p<108)。找到一个最短的连续子序列,满足它的和 s ≥ p s \ge p s≥p。
int getminlen(int n, int *sum) {
int len = n + 1, i = 1, j = 0;
while (j++ < n) {
while (sum[j] - sum[i - 1] >= p) {
len = min(len, j - i + 1);
++i;
}
}
if (len == n + 1) len = 0;
return len;
}
【例题3】对于一个字符串,如果它的每个字符数都 ≤ k \le k ≤k,则称其为 g o o d good good 串。给定一个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 且只包含小写字母的字符串,求它的 g o o d good good 串的数量。
int has[256];
ll getcount(int n, char* str, int k) {
ll ans = 0;
int s = 0, i = 0, j = -1;
memset(has, 0, sizeof(has));
while (j++ < n - 1) {
if (++has[str[j]] > k)
s = 1;
while (s) {
if (--has[str[i]] == k)
s = 0;
++i;
}
ans += (j - i + 1);
}
return ans;
}
【例题4】给定 n ( n ≤ 200000 ) n ( n \le 200000) n(n≤200000) 个数 a i a_i ai,以及一个数 m m m,求第 k k k 大的数 ≥ m \ge m ≥m 的区间的个数。
#define ll long long
ll getcount(int n, int k) {
ll ans = 0;
int i = 1, j = 0;
while (j++ < n) {
while (sum[j] - sum[i - 1] >= k) {
ans += n - j + 1;
++i;
}
}
return ans;
}
题目链接 | 难度 | 解法 |
---|---|---|
HDU 2668 Daydream | ★★☆☆☆ | 【例题1】哈希 + 最长不重复子串 |
NC41 最长无重复子串 | ★★☆☆☆ | 哈希 + 最长不重复子串 |
PKU 2100 Graveyard Design | ★☆☆☆☆ | 平方和单调性 |
PKU 3061 Subsequence | ★☆☆☆☆ | 【例题2】求和单调性 |
PKU 2739 Sum of Consecutive Prime Numbers | ★☆☆☆☆ | 素数筛选 + 求和单调性 |
PKU 3320 Jessica’s Reading Problem | ★★☆☆☆ | 离散化 / 哈希 + 全集合连续子序列 |
HDU 2158 最短区间版大家来找碴 | ★★☆☆☆ | 哈希 + 全集合连续子序列 |
HDU 2369 Broken Keyboard | ★★☆☆☆ | 哈希 + 最长 m 子串 |
HDU 5672 String | ★★☆☆☆ | 哈希 + m 子串方案数 |
HDU 5056 Boring count | ★★☆☆☆ | 哈希 + m 子串方案数 |
HDU 6205 card card card | ★★☆☆☆ | 前缀和 + 求和单调性 |
HDU 5178 pairs | ★★☆☆☆ | 前缀和 + 求和单调性 |
HDU 6103 Kirinriki | ★★★☆☆ | 枚举 + 求和单调性 |
HDU 6119 小小粉丝度度熊 | ★★★☆☆ | 区间合并 + 前缀和 |
HDU 1937 Finding Seats | ★★★☆☆ | 矩阵前缀和 + 求和单调性 |
PKU 2566 Bound Found | ★★★☆☆ | 思维转换 + 求和单调性 |
HDU 5806 NanoApe Loves Sequence Ⅱ | ★★★☆☆ | 【例题4】思维转换 + 求和单调性 |