modify code

master
algorithmzuo 3 years ago
parent 0e843ed49b
commit 8a031aa7eb

@ -0,0 +1,42 @@
package class_2022_10_3_week;
// 给定一个整数数组 A坡是元组 (i, j)其中  i < j  A[i] <= A[j]
// 这样的坡的宽度为 j - i
// 找出 A 中的坡的最大宽度如果不存在返回 0
// 示例 1
// 输入:[6,0,8,2,1,5]
// 输出4
// 解释:
// 最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5
// 示例 2
// 输入:[9,8,1,0,1,9,4,0,4,1]
// 输出7
// 解释:
// 最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1
// 测试链接 : https://leetcode.cn/problems/maximum-width-ramp/
public class Code01_MaximumWidthRamp {
public static int maxWidthRamp(int[] arr) {
int n = arr.length;
// 栈中只放下标
int[] stack = new int[n];
// 栈的大小
int r = 0;
for (int i = 0; i < n; i++) {
if (r == 0 || arr[stack[r - 1]] > arr[i]) {
stack[r++] = i;
}
}
int ans = 0;
// 从右往左遍历
// j = n - 1
for (int j = n - 1; j >= 0; j--) {
while (r != 0 && arr[stack[r - 1]] <= arr[j]) {
int i = stack[--r];
ans = Math.max(ans, j - i);
}
}
return ans;
}
}

@ -0,0 +1,80 @@
package class_2022_10_3_week;
// 给定一个由 0 和 1 组成的数组 arr ,将数组分成 3 个非空的部分
// 使得所有这些部分表示相同的二进制值。
// 如果可以做到,请返回任何 [i, j],其中 i+1 < j这样一来
// arr[0], arr[1], ..., arr[i] 为第一部分
// arr[i + 1], arr[i + 2], ..., arr[j - 1] 为第二部分
// arr[j], arr[j + 1], ..., arr[arr.length - 1] 为第三部分
// 这三个部分所表示的二进制值相等
// 如果无法做到,就返回 [-1, -1]
// 注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体
// 例如,[1,1,0] 表示十进制中的 6而不会是 3。此外前导零也是被允许的
// 所以 [0,1,1] 和 [1,1] 表示相同的值。
// 测试链接 : https://leetcode.cn/problems/three-equal-parts/
public class Code02_ThreeEqualParts {
public static int[] threeEqualParts(int[] arr) {
// 计算arr中1的数量
int ones = 0;
for (int num : arr) {
ones += num == 1 ? 1 : 0;
}
// 如果1的数量不能被3整除肯定不存在方案
if (ones % 3 != 0) {
return new int[] { -1, -1 };
}
int n = arr.length;
// 如果1的数量是0怎么划分都可以了因为全是0
if (ones == 0) {
return new int[] { 0, n - 1 };
}
// 接下来的过程
// 因为1的数量能被3整除比如一共有12个1
// 那么第一段肯定含有第1个1~第4个1
// 那么第二段肯定含有第5个1~第8个1
// 那么第三段肯定含有第9个1~第12个1
// 所以把第1个1当做第一段的开头start1
// 所以把第5个1当做第二段的开头start2
// 所以把第9个1当做第三段的开头start3
int part = ones / 3;
int start1 = -1;
int start2 = -1;
int start3 = -1;
int cnt = 0;
// 1个数21个
// part = 7
// 1 8
for (int i = 0; i < n; i++) {
if (arr[i] == 1) {
cnt++;
if (start1 == -1 && cnt == 1) {
start1 = i;
}
if (start2 == -1 && cnt == part + 1) {
start2 = i;
}
if (start3 == -1 && cnt == 2 * part + 1) {
start3 = i;
}
}
}
// 第一段的开头往下的部分
// 第二段的开头往下的部分
// 第三段的开头往下的部分
// 要都一样,这三段的状态才是一样的
// 所以接下来就验证这一点,是不是每一步都一样
while (start3 < n) {
if (arr[start1] != arr[start2] || arr[start1] != arr[start3]) {
// 一旦不一样,肯定没方案了
return new int[] { -1, -1 };
}
start1++;
start2++;
start3++;
}
// 如果验证通过,返回断点即可
return new int[] { start1 - 1, start2 };
}
}

@ -0,0 +1,312 @@
package class_2022_10_3_week;
// 来自阿里
// 给定一个长度n的数组每次可以选择一个数x
// 让这个数组中所有的x都变成x+1问你最少的操作次数
// 使得这个数组变成一个非降数组
// n <= 3 * 10^5
// 0 <= 数值 <= 10^9
public class Code03_AddToSorted {
// 方法1
// 暴力方法
// 为了验证
public static int minOp1(int[] arr) {
int max = 0;
for (int num : arr) {
max = Math.max(max, num);
}
return process1(0, max, new boolean[max + 1], arr);
}
public static int process1(int num, int max, boolean[] op, int[] arr) {
if (num == max + 1) {
int cnt = 0;
int[] help = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
help[i] = arr[i];
}
for (int i = 0; i <= max; i++) {
if (op[i]) {
cnt++;
add(help, i);
}
}
for (int i = 1; i < arr.length; i++) {
if (help[i - 1] > help[i]) {
return Integer.MAX_VALUE;
}
}
return cnt;
} else {
op[num] = true;
int p1 = process1(num + 1, max, op, arr);
op[num] = false;
int p2 = process1(num + 1, max, op, arr);
return Math.min(p1, p2);
}
}
public static void add(int[] arr, int num) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == num) {
arr[i]++;
}
}
}
// 方法2
// 最优解的思路
// 但依然不能通过考试数据量
// 为了验证
public static int minOp2(int[] arr) {
if (arr.length < 2) {
return 0;
}
int n = arr.length;
int[] min = new int[n];
min[n - 1] = arr[n - 1];
for (int i = n - 2; i >= 0; i--) {
min[i] = Math.min(min[i + 1], arr[i]);
}
int max = 0;
for (int num : arr) {
max = Math.max(max, num);
}
boolean[] add = new boolean[max + 1];
for (int i = 0; i < n - 1; i++) {
if (arr[i] > min[i + 1]) {
for (int j = min[i + 1]; j < arr[i]; j++) {
add[j] = true;
}
}
}
int ans = 0;
for (boolean is : add) {
ans += is ? 1 : 0;
}
return ans;
}
// 方法3
// 最优解 + 动态开点线段树
// 时间复杂度O(N*logN)
public static int minOp3(int[] arr) {
int n = arr.length;
int m = 0;
for (int num : arr) {
m = Math.max(m, num);
}
// 0 ~ 1000000000
DynamicSegmentTree dst = new DynamicSegmentTree(m);
for (int i = 1, max = arr[0]; i < n; i++) {
// max左边最大值
// 10 4
// 4...9
if (max > arr[i]) {
dst.set(arr[i], max - 1);
}
max = Math.max(max, arr[i]);
}
return dst.sum();
}
public static class Node {
public int sum;
public boolean set;
public Node left;
public Node right;
}
// 动态增加
public static class DynamicSegmentTree {
public Node root;
public int size;
public DynamicSegmentTree(int max) {
root = new Node();
size = max;
}
private void pushDown(Node p, int ln, int rn) {
if (p.left == null) {
p.left = new Node();
}
if (p.right == null) {
p.right = new Node();
}
if (p.set) {
p.left.set = true;
p.right.set = true;
p.left.sum = ln;
p.right.sum = rn;
p.set = false;
}
}
public void set(int s, int e) {
update(root, 0, size, s, e);
}
private void update(Node c, int l, int r, int s, int e) {
if (s <= l && r <= e) {
c.set = true;
c.sum = (r - l + 1);
} else {
int mid = (l + r) >> 1;
pushDown(c, mid - l + 1, r - mid);
if (s <= mid) {
update(c.left, l, mid, s, e);
}
if (e > mid) {
update(c.right, mid + 1, r, s, e);
}
c.sum = c.left.sum + c.right.sum;
}
}
public int sum() {
return root.sum;
}
}
// 方法4
// 最优解 + 固定数组的动态开点线段树(多次运行更省空间)
// 时间复杂度O(N*logN)
public static int minOp4(int[] arr) {
int n = arr.length;
int m = 0;
for (int num : arr) {
m = Math.max(m, num);
}
for (int i = 0; i < cnt; i++) {
lchild[i] = -1;
rchild[i] = -1;
}
cnt = 0;
sum[cnt] = 0;
set[cnt] = false;
left[cnt] = 0;
right[cnt++] = m;
for (int i = 1, max = arr[0]; i < n; i++) {
if (max > arr[i]) {
set(arr[i], max - 1, 0);
}
max = Math.max(max, arr[i]);
}
return sum();
}
// 空间固定!
public static final int MAXM = 8000000;
public static int[] sum = new int[MAXM];
public static boolean[] set = new boolean[MAXM];
public static int[] left = new int[MAXM];
public static int[] right = new int[MAXM];
public static int[] lchild = new int[MAXM];
public static int[] rchild = new int[MAXM];
static {
for (int i = 0; i < MAXM; i++) {
lchild[i] = -1;
rchild[i] = -1;
}
}
public static int cnt = 0;
public static void set(int s, int e, int i) {
int l = left[i];
int r = right[i];
if (s <= l && r <= e) {
set[i] = true;
sum[i] = (r - l + 1);
} else {
int mid = (l + r) >> 1;
down(i, l, mid, mid + 1, r, mid - l + 1, r - mid);
if (s <= mid) {
set(s, e, lchild[i]);
}
if (e > mid) {
set(s, e, rchild[i]);
}
sum[i] = sum[lchild[i]] + sum[rchild[i]];
}
}
public static void down(int i, int l1, int r1, int l2, int r2, int ln, int rn) {
if (lchild[i] == -1) {
sum[cnt] = 0;
set[cnt] = false;
left[cnt] = l1;
right[cnt] = r1;
lchild[i] = cnt++;
}
if (rchild[i] == -1) {
sum[cnt] = 0;
set[cnt] = false;
left[cnt] = l2;
right[cnt] = r2;
rchild[i] = cnt++;
}
if (set[i]) {
set[lchild[i]] = true;
set[rchild[i]] = true;
sum[lchild[i]] = ln;
sum[rchild[i]] = rn;
set[i] = false;
}
}
public static int sum() {
return sum[0];
}
// 为了测试
public static int[] randomArray(int n, int v) {
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[i] = (int) (Math.random() * v);
}
return ans;
}
// 为了测试
public static void main(String[] args) {
int N = 10;
int V = 12;
int testTime = 5000;
System.out.println("功能测试开始");
for (int i = 0; i < testTime; i++) {
int n = (int) (Math.random() * N) + 1;
int[] arr = randomArray(n, V);
int ans1 = minOp1(arr);
int ans2 = minOp2(arr);
int ans3 = minOp3(arr);
int ans4 = minOp4(arr);
if (ans1 != ans2 || ans1 != ans3 || ans1 != ans4) {
System.out.println("出错了!");
}
}
System.out.println("功能测试结束");
System.out.println("性能测试开始");
N = 300000;
V = 1000000000;
testTime = 10;
System.out.println("数组长度 : " + N);
System.out.println("数值范围 : " + V);
System.out.println("测试次数 : " + testTime);
long runTime = 0;
for (int i = 0; i < testTime; i++) {
int[] arr = randomArray(N, V);
long start = System.currentTimeMillis();
minOp4(arr);
long end = System.currentTimeMillis();
runTime += end - start;
}
System.out.println(testTime + "次测试总运行时间 : " + runTime + " 毫秒");
System.out.println("性能测试结束");
}
}

@ -0,0 +1,213 @@
package class_2022_10_3_week;
import java.util.ArrayList;
import java.util.Arrays;
// 来自Lucid Air
// 给定一个无向图,保证所有节点连成一棵树,没有环
// 给定一个正数n为节点数所以节点编号为0~n-1那么就一定有n-1条边
// 每条边形式为{a, b, w}意思是a和b之间的无向边权值为w
// 要求给定一个正数k表示在挑选之后每个点相连的边数量都不能超过k
// 注意是每个点的连接数量都不超过k不是总连接数量不能超过k
// 你可以随意挑选边留下,剩下的边删掉,但是要满足上面的要求
// 返回不违反要求的情况下,你挑选边所能达到的最大权值累加和
public class Code04_EveryNodePickMostkEdgesMaxValue {
// public static class Edge {
// public int to;
// public int weight;
//
// public Edge(int t, int w) {
// to = t;
// weight = w;
// }
// }
//
// public static int bestSum(int n, int[][] edges, int k) {
// ArrayList<ArrayList<Edge>> graph = new ArrayList<>();
// for (int i = 0; i < n; i++) {
// graph.add(new ArrayList<>());
// }
// for (int[] edge : edges) {
// int a = edge[0];
// int b = edge[1];
// int w = edge[2];
// graph.get(a).add(new Edge(b, w));
// graph.get(b).add(new Edge(a, w));
// }
// Info info = process(graph, 0, -1);
// return info.no;
// }
//
// public static class Info {
// public int no;
// public int yes;
//
// public Info(int a, int b) {
// no = a;
// yes = b;
// }
// }
//
// public static Info process(ArrayList<ArrayList<Edge>> graph, int cur, int father) {
// ArrayList<Info> childsInfo = new ArrayList<>();
// for (Edge edge : graph.get(cur)) {
// if (edge.to != father) {
// childsInfo.add(process(graph, edge.to, cur));
// }
// }
// // 孩子所有的信息都在childsInfo
// // 整合了!
// // 父亲不跟当前节点相连
// int no = 0;
// // 先把所有后代不连的加起来
// // 挑选k个加成最大的
// // 加成:连的边 + yes - no
// int yes = 0;
// // 先把所有后代不连的加起来
// // 挑选k-1个加成最大的
// // 加成:连的边 + yes - no
//
// return new Info(no, yes);
// }
// 暴力方法
// 为了验证
public static int maxSum1(int n, int k, int[][] edges) {
return process(edges, 0, new boolean[edges.length], n, k);
}
public static int process(int[][] edges, int i, boolean[] pick, int n, int k) {
if (i == edges.length) {
int[] cnt = new int[n];
int ans = 0;
for (int j = 0; j < edges.length; j++) {
if (pick[j]) {
cnt[edges[j][0]]++;
cnt[edges[j][1]]++;
ans += edges[j][2];
}
}
for (int j = 0; j < n; j++) {
if (cnt[j] > k) {
return -1;
}
}
return ans;
} else {
pick[i] = true;
int p1 = process(edges, i + 1, pick, n, k);
pick[i] = false;
int p2 = process(edges, i + 1, pick, n, k);
return Math.max(p1, p2);
}
}
// 最优解
// 时间复杂度O(N * logN)
public static int[][] dp = new int[100001][2];
public static int[] help = new int[100001];
public static int maxSum2(int n, int k, int[][] edges) {
ArrayList<ArrayList<int[]>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : edges) {
int a = edge[0];
int b = edge[1];
int c = edge[2];
graph.get(a).add(new int[] { b, c });
graph.get(b).add(new int[] { a, c });
}
for (int i = 0; i < n; i++) {
dp[i][0] = -1;
dp[i][1] = -1;
}
dfs(0, -1, k, graph);
return dp[0][0];
}
public static void dfs(int cur, int father, int k, ArrayList<ArrayList<int[]>> graph) {
ArrayList<int[]> edges = graph.get(cur);
for (int i = 0; i < edges.size(); i++) {
int next = edges.get(i)[0];
if (next != father) {
dfs(next, cur, k, graph);
}
}
int ans0 = 0;
int ans1 = 0;
int m = 0;
for (int i = 0; i < edges.size(); i++) {
int next = edges.get(i)[0];
int weight = edges.get(i)[1];
if (next != father) {
ans0 += dp[next][0];
ans1 += dp[next][0];
if (dp[next][0] < dp[next][1] + weight) {
help[m++] = dp[next][1] + weight - dp[next][0];
}
}
}
Arrays.sort(help, 0, m);
for (int i = m - 1, cnt = 1; i >= 0 && cnt <= k; i--, cnt++) {
if (cnt <= k - 1) {
ans0 += help[i];
ans1 += help[i];
}
if (cnt == k) {
ans0 += help[i];
}
}
dp[cur][0] = ans0;
dp[cur][1] = ans1;
}
// 为了测试
// 生成无环无向图
public static int[][] randomEdges(int n, int v) {
int[] order = new int[n];
for (int i = 0; i < n; i++) {
order[i] = i;
}
for (int i = n - 1; i >= 0; i--) {
swap(order, i, (int) (Math.random() * (i + 1)));
}
int[][] edges = new int[n - 1][3];
for (int i = 1; i < n; i++) {
edges[i - 1][0] = order[i];
edges[i - 1][1] = order[(int) (Math.random() * i)];
edges[i - 1][2] = (int) (Math.random() * v) + 1;
}
return edges;
}
// 为了测试
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 为了测试
public static void main(String[] args) {
int N = 16;
int V = 50;
int testTimes = 2000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N) + 1;
int k = (int) (Math.random() * n) + 1;
int[][] edges = randomEdges(n, V);
int ans1 = maxSum1(n, k, edges);
int ans2 = maxSum2(n, k, edges);
if (ans1 != ans2) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
}

@ -0,0 +1,163 @@
package class_2022_10_3_week;
// 给定一个字符串str
// 如果删掉连续一段子串,剩下的字符串拼接起来是回文串
// 那么该删除叫做有效的删除
// 返回有多少种有效删除
// 注意 : 不能全删除,删成空串不允许
// 字符串长度 <= 3000
public class Code05_RemoveSubstringRestPalindrome {
// 暴力方法
// 时间复杂度O(N^3)
// 为了验证而写,不是正式方法
// 删掉每一个可能的子串
// 然后验证剩下的子串是不是回文串
public static int good1(String str) {
int n = str.length();
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (isPalindrome(str.substring(0, i) + str.substring(j + 1))) {
ans++;
}
}
}
return ans - 1;
}
public static boolean isPalindrome(String str) {
for (int l = 0, r = str.length() - 1; l <= r; l++, r--) {
if (str.charAt(l) != str.charAt(r)) {
return false;
}
}
return true;
}
// 正式方法
// 时间复杂度O(N^2)
public static int good2(String str) {
if (str.length() == 1) {
return 0;
}
// O(N)
int[] pArr = manacher(str);
char[] s = str.toCharArray();
int n = s.length;
// 左右逆序对应的最大长度
int range = 0;
for (int l = 0, r = n - 1; l <= r && s[l] == s[r]; l++, r--) {
range++;
}
int ans = 0;
for (int l = 0; l < n; l++) {
for (int r = l; r < n; r++) {
if (l < n - r - 1) {
if (range >= l && check(pArr, r + 1, n - l - 1)) {
ans++;
}
} else if (l > n - r - 1) {
if (range >= n - r - 1 && check(pArr, n - r - 1, l - 1)) {
ans++;
}
} else {
if (range >= l) {
ans++;
}
}
}
}
return ans - 1;
}
public static int[] manacher(String s) {
char[] str = manacherString(s);
int[] pArr = new int[str.length];
int C = -1;
int R = -1;
for (int i = 0; i < str.length; i++) { // 0 1 2
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
while (i + pArr[i] < str.length && i - pArr[i] > -1) {
if (str[i + pArr[i]] == str[i - pArr[i]])
pArr[i]++;
else {
break;
}
}
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
}
return pArr;
}
public static char[] manacherString(String str) {
char[] charArr = str.toCharArray();
char[] res = new char[str.length() * 2 + 1];
int index = 0;
for (int i = 0; i != res.length; i++) {
res[i] = (i & 1) == 0 ? '#' : charArr[index++];
}
return res;
}
// 根据原字符串的回文半径数组判断原字符串l...r这一段是不是回文
public static boolean check(int[] pArr, int l, int r) {
int n = r - l + 1;
l = l * 2 + 1;
r = r * 2 + 1;
return pArr[(l + r) / 2] - 1 >= n;
}
// 为了测试
public static String randomString(int n, int v) {
char[] str = new char[n];
for (int i = 0; i < n; i++) {
str[i] = (char) ((int) (Math.random() * v) + 'a');
}
return String.valueOf(str);
}
// 为了测试
public static void main(String[] args) {
int N = 50;
int V = 3;
int testTime = 5000;
System.out.println("功能测试开始");
for (int i = 0; i < testTime; i++) {
int n = (int) (Math.random() * N) + 1;
String str = randomString(n, V);
int ans1 = good1(str);
int ans2 = good2(str);
if (ans1 != ans2) {
System.out.println("出错了!");
System.out.println(ans1);
System.out.println(ans2);
break;
}
}
System.out.println("功能测试结束");
System.out.println("性能测试开始");
int n = 3000;
int v = 26;
String str = randomString(n, v);
System.out.println("字符串的长度 : " + n);
System.out.println("小写字符种类 : " + v);
long start;
long end;
start = System.currentTimeMillis();
int ans1 = good1(str);
end = System.currentTimeMillis();
System.out.println("方法1(暴力方法)答案 : " + ans1);
System.out.println("方法1(暴力方法)运行时间 : " + (end - start) + " 毫秒");
start = System.currentTimeMillis();
int ans2 = good2(str);
end = System.currentTimeMillis();
System.out.println("方法2(正式方法)答案 : " + ans2);
System.out.println("方法2(正式方法)运行时间 : " + (end - start) + " 毫秒");
System.out.println("性能测试结束");
}
}

@ -2183,7 +2183,58 @@ n <= 1000
第045节 2022年10月第3周流行算法题目解析
给定一个整数数组 A坡是元组 (i, j)其中  i < j  A[i] <= A[j]
这样的坡的宽度为 j - i
找出 A 中的坡的最大宽度如果不存在返回 0
示例 1
输入:[6,0,8,2,1,5]
输出4
解释:
最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5
示例 2
输入:[9,8,1,0,1,9,4,0,4,1]
输出7
解释:
最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1
测试链接 : https://leetcode.cn/problems/maximum-width-ramp/
给定一个由 0 和 1 组成的数组 arr ,将数组分成 3 个非空的部分
使得所有这些部分表示相同的二进制值。
如果可以做到,请返回任何 [i, j],其中 i+1 < j这样一来
arr[0], arr[1], ..., arr[i] 为第一部分
arr[i + 1], arr[i + 2], ..., arr[j - 1] 为第二部分
arr[j], arr[j + 1], ..., arr[arr.length - 1] 为第三部分
这三个部分所表示的二进制值相等
如果无法做到,就返回 [-1, -1]
注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体
例如,[1,1,0] 表示十进制中的 6而不会是 3。此外前导零也是被允许的
所以 [0,1,1] 和 [1,1] 表示相同的值。
测试链接 : https://leetcode.cn/problems/three-equal-parts/
来自阿里
给定一个长度n的数组每次可以选择一个数x
让这个数组中所有的x都变成x+1问你最少的操作次数
使得这个数组变成一个非降数组
n <= 3 * 10^5
0 <= 数值 <= 10^9
来自Lucid Air
给定一个无向图,保证所有节点连成一棵树,没有环
给定一个正数n为节点数所以节点编号为0~n-1那么就一定有n-1条边
每条边形式为{a, b, w}意思是a和b之间的无向边权值为w
要求给定一个正数k表示在挑选之后每个点相连的边数量都不能超过k
注意是每个点的连接数量都不超过k不是总连接数量不能超过k
你可以随意挑选边留下,剩下的边删掉,但是要满足上面的要求
返回不违反要求的情况下,你挑选边所能达到的最大权值累加和
给定一个字符串str
如果删掉连续一段子串,剩下的字符串拼接起来是回文串
那么该删除叫做有效的删除
返回有多少种有效删除
注意 : 不能全删除,删成空串不允许
字符串长度 <= 3000

Loading…
Cancel
Save