You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
4.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class_2022_09_1_week;
// 给你一个由小写字母组成的字符串 s ,和一个整数 k
// 如果满足下述条件,则可以将字符串 t 视作是 理想字符串
// t 是字符串 s 的一个子序列。
// t 中每两个 相邻 字母在字母表中位次的绝对差值小于或等于 k 。
// 返回 最长 理想字符串的长度。
// 字符串的子序列同样是一个字符串,并且子序列还满足:
// 可以经由其他字符串删除某些字符(也可以不删除)但不改变剩余字符的顺序得到。
// 注意:字母表顺序不会循环
// 例如,'a' 和 'z' 在字母表中位次的绝对差值是 25而不是 1 。
// 测试链接 : https://leetcode.cn/problems/longest-ideal-subsequence/
public class Code01_LongestIdealString {
// 二维动态规划的解
// N为字符串长度E为字符集大小K为差值要求
// 时间复杂度O(N*E)
// 空间复杂度O(N*E)
public static int longestIdealString1(String s, int k) {
int n = s.length();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = s.charAt(i) - 'a';
}
int[][] dp = new int[n][27];
for (int i = 0; i < n; i++) {
for (int j = 0; j <= 26; j++) {
dp[i][j] = -1; // -1代表没算过
}
}
return f(arr, 0, 26, k, dp);
}
// 数组s中所有的值都在0~25对应a~z
// 当前在s[i...]选择数字, 并且前一个数字是p
// 如果p<26说明选择的前一个数字是p
// 如果p==26说明之前没有选过任何数字
// 返回在前一个数字是p的情况下在s[i...]上选择数字,最长理想子序列能是多长
// dp仅仅是缓存结构暴力递归改动态规划常规技巧
public static int f(int[] s, int i, int p, int k, int[][] dp) {
if (i == s.length) {
return 0;
}
if (dp[i][p] != -1) {
return dp[i][p];
}
int p1 = f(s, i + 1, p, k, dp);
int p2 = 0;
if (p == 26 || Math.abs(s[i] - p) <= k) {
p2 = 1 + f(s, i + 1, s[i], k, dp);
}
int ans = Math.max(p1, p2);
dp[i][p] = ans;
return ans;
}
// 一维动态规划从左往右递推版
// N为字符串长度E为字符集大小K为差值要求
// 时间复杂度O(N*K)
// 空间复杂度O(E)
public static int longestIdealString2(String s, int k) {
int[] dp = new int[26];
int c, l, r, pre, ans = 0;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i) - 'a';
l = Math.max(c - k, 0);
r = Math.min(c + k, 25);
pre = 0;
for (int j = l; j <= r; j++) {
pre = Math.max(pre, dp[j]);
}
dp[c] = 1 + pre;
ans = Math.max(ans, dp[c]);
}
return ans;
}
// 从左往右递推 + 线段树优化
// N为字符串长度E为字符集大小K为差值要求
// 时间复杂度O(N * logE)
// 空间复杂度O(E)
public static int longestIdealString3(String s, int k) {
// 0 0 0
// 1(a) 2(b) ... 26(z)
SegmentTree st = new SegmentTree(26);
int c, pre, ans = 0;
for (int i = 0; i < s.length(); i++) {
// i s.charAt(i)
// a 1
// b 2
// z 26
c = s.charAt(i) - 'a' + 1;
// 2 k = 3
// 1 2 3 4 5 6 7
// l = Math.max(c - k, 1)
// r = Math.min(c + k, 26)
pre = st.max(Math.max(c - k, 1), Math.min(c + k, 26));
ans = Math.max(ans, 1 + pre);
st.update(c, 1 + pre);
}
return ans;
}
public static class SegmentTree {
private int n;
private int[] max;
public SegmentTree(int maxSize) {
n = maxSize + 1;
max = new int[n << 2];
}
public void update(int index, int c) {
update(index, index, c, 1, n, 1);
}
public int max(int left, int right) {
return max(left, right, 1, n, 1);
}
private void pushUp(int rt) {
max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]);
}
private void update(int L, int R, int C, int l, int r, int rt) {
if (L <= l && r <= R) {
max[rt] = C;
return;
}
int mid = (l + r) >> 1;
if (L <= mid) {
update(L, R, C, l, mid, rt << 1);
}
if (R > mid) {
update(L, R, C, mid + 1, r, rt << 1 | 1);
}
pushUp(rt);
}
private int max(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) {
return max[rt];
}
int mid = (l + r) >> 1;
int ans = 0;
if (L <= mid) {
ans = Math.max(ans, max(L, R, l, mid, rt << 1));
}
if (R > mid) {
ans = Math.max(ans, max(L, R, mid + 1, r, rt << 1 | 1));
}
return ans;
}
}
}