戳印序列
LeetCode936题,通过一个印章将一串文字印盖成最终所需的字符串。
你想要用小写字母组成一个目标字符串 target。
开始的时候,序列由 target.length 个 '?' 记号组成。而你有一个小写字母印章 stamp。
在每个回合,你可以将印章放在序列上,并将序列中的每个字母替换为印章上的相应字母。你最多可以进行 10 * target.length 个回合。
举个例子,如果初始序列为 "?????",而你的印章 stamp 是 "abc",那么在第一回合,你可以得到 "abc??"、"?abc?"、"??abc"。(请注意,印章必须完全包含在序列的边界内才能盖下去。)
如果可以印出序列,那么返回一个数组,该数组由每个回合中被印下的最左边字母的索引组成。如果不能印出序列,就返回一个空数组。
例如,如果序列是 "ababc",印章是 "abc",那么我们就可以返回与操作 "?????" -> "abc??" -> "ababc" 相对应的答案 [0, 2];
另外,如果可以印出序列,那么需要保证可以在 10 * target.length 个回合内完成。任何超过此数字的答案将不被接受。
示例 1:
输入:stamp = "abc", target = "ababc"
输出:[0,2]
([1,0,2] 以及其他一些可能的结果也将作为答案被接受)
示例 2:
输入:stamp = "abca", target = "aabcaca"
输出:[3,0,1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/stamping-the-sequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析:
1.逆向思想:考虑如何将target字符变为???这种形式的
2.首先找到target字符中完全匹配的字符串,将这个范围内的所有字符变为?
3.再次循环时是?的字符串则不需要考虑了,只需要考虑部分匹配的字符串
4.记录每次盖章的位置,倒序得到最终答案
public static int[] movesToStamp(String stamp, String target) {
char[] S = stamp.toCharArray();
char[] T = target.toCharArray();
//保存最终结果的倒序
List<Integer> res = new ArrayList<>();
//记录是否访问过
boolean[] visited = new boolean[T.length];
//被替换的个数
int count = 0;
//如果没有全部替换掉就接着循环
while (count < T.length) {
//标记
boolean replaceFlag = false;
for (int i = 0; i <= T.length - S.length; i++) {
//如果该点没有被访问,并且可以被替换
if (!visited[i] && canReplace(S, i, T)) {
//盖章,并返回盖住的字符
int temp = doReplace(S.length, i, T);
replaceFlag = true;
count += temp;
visited[i]=true;
res.add(i);
//该范围内的已经完全匹配了
i += temp - 1;
if (count == T.length) {
break;
}
}
}
//如果一轮循环都没有盖过一次,则说明不能替换
if (!replaceFlag) {
return new int[]{};
}
}
int[] resArray = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
resArray[i] = res.get(res.size() - i - 1);
}
return resArray;
}
private static int doReplace(int len, int i, char[] t) {
int count=0;
for (int j = 0; j < len; j++) {
//如果当前字符不为*,就将他变为*
if (t[j + i] != '*') {
t[j + i] = '*';
count++;
}
}
return count;
}
private static boolean canReplace(char[] s, int i, char[] t) {
for (int j = 0; j < s.length; j++) {
//如果在印章长度范围内,如果出现没有盖过章的字符,并且对应下标的字符不相等,则说明不能盖章
if (t[i + j] != '*' && t[i + j] != s[j]) {
return false;
}
}
return true;
}
数组中的逆序对
LeetCode1528题,求一个数组中降序排列的数有几对。
思路分析:
1.将数组nums不断的拆分,拆成最小的一个数组
2.将数组里的元素进行排序后,通过两个指针,对子数组进行遍历排序比较
3.排序比较结束后,当前这个子数组肯定是有序的,并且计算了该子数组的逆序对
4.之后计算更大规模的数组时,也是按照上面的步骤进行循环计算
public static int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) {
return 0;
}
return reversePairs(nums, 0, len - 1, new int[len]);
}
private static int reversePairs(int[] nums, int left, int right, int[] temp) {
if (left == right) {
return 0;
}
//求出中间下标值
int mid = (left + right) >> 1;
//计算左边子数组的逆序对(返回的子数组左边和右边肯定是有序的了)
int lc = reversePairs(nums, left, mid, temp);
//计算右边子数组的逆序对
int rc = reversePairs(nums, mid + 1, right, temp);
int count = lc + rc;
//如果左边最大的数小于右边最小的数,则说明整个子数组都是有序的
if (nums[mid] <= nums[mid + 1]) {
//则直接返回总数,不用在排序合并了
return count;
}
//否则将左右子数组排好序,并且计数整个数组的逆序对
return count + mergeAndCount(nums, left, mid, right, temp);
}
private static int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
//将left right范围内的元素存入临时数组中
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}
int res = 0;
//左边第一个数
int l = left;
//右边第一个数
int r = mid + 1;
//循环排序并计数
for (int i = left; i <= right; i++) {
if (l > mid) {//说明左子数组的元素已经重新放入到nums中了,只需要取右边的数即可
nums[i] = temp[r++];
} else if (r > right) {//说明右子数组的元素已经重新放入到nums中了,只需要取左边的数即可
nums[i] = temp[l++];
} else if (temp[l] <= temp[r]) {//如果右边的数大于等于左边的数,后移左指针并将左元素放入nums中
nums[i] = temp[l++];
} else {//如果右边的数小于左边的数,后移右指针并将右元素放入nums中,并且计算逆序对,注意如果temp[l]>temp[r],那么左子数组中l后面的元素肯定也大于temp[r]
nums[i] = temp[r++];
res += (mid - l) + 1;
}
}
return res;
}