周赛地址:https://leetcode-cn.com/contest/weekly-contest-227/
第一题:检查数组是否经排序和轮转得到
题目已经告诉了轮转的意思:A[i] == B[(i+x) % A.length],我们就要使用这个条件作为判断的依据。
数据量也不大,直接循环所有的x进行判断即可,在判断过程中,如果有符合的,直接返回true,如果所有的x都跑完了还没有返回,那就是false了。
class Solution {
public boolean check(int[] nums) {
int length = nums.length;
// 复制一个用于排序
int[] copy = Arrays.copyOf(nums, length);
Arrays.sort(copy);
// 遍历所有的轮转情况
for (int i = 0; i < length; i++) {
boolean flag = true;
for (int j = 0; j < length; j++) {
if (nums[j] != copy[(j + i) % length]) {
flag = false;
break;
}
}
// 一次轮转完,满足条件直接返回
if (flag) {
return true;
}
}
return false;
}
}
第二题:移除石子的最大得分
当存在两个或更多空堆的时候,游戏停止,我们需要在游戏停止前,更多的得分,那么可以每次选择当前最多的两堆石子,这样可以保证最迟游戏停止。
class Solution {
public int maximumScore(int a, int b, int c) {
// 构造一个倒序的优先队列
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(3, Collections.reverseOrder());
priorityQueue.offer(a);
priorityQueue.offer(b);
priorityQueue.offer(c);
int score = 0;
while (true) {
// 每次从优先队列中取出两个最大的
a = priorityQueue.poll();
b = priorityQueue.poll();
// a == 0时,b和c一定是0,b == 0时,c一定是0
if (a == 0 || b == 0) {
break;
}
// 将最大和次大的减一后放入优先队列
priorityQueue.offer(--a);
priorityQueue.offer(--b);
score++;
}
return score;
}
}
此题还有一种数学解法,时空复杂度都会降低。
假设a,b,c是按照从小到大的顺序,c最大。
考虑两种情况:
1.当a+b≤c的时候,先在a和c里取石子,经过a次操作后,a变为0,c变为c-a,再从b和c里取石子,经过b次操作,b变为0,c变为c-a-b。此时a和b都是0了,游戏结束,此时分数为a+b。
2.当a+b>c的时候,可以使得最后的结果,每个堆尽可能为0,因为取的时候,每次都会选择两个最大的数字取石子,最后一定会出现两种情况:[1,1,1]和[1,1,0]。第一种情况再取一次就是[1,0,0],第二种情况再取一次就是[0,0,0],此时的分数是最高的。根据游戏规则,每次在两个不同的堆里取两个石子。如果最后石子正好可以取完,那么a+b+c的值就是偶数,否则就是奇数。每次从堆里取两个石子,得1分,最终的得分就是(a+b+c)/2了。
class Solution {
public int maximumScore(int a, int b, int c) {
int[] array = new int[]{a, b, c};
// 从小到大进行排序
Arrays.sort(array);
a = array[0];
b = array[1];
c = array[2];
if (a + b <= c) {
return a + b;
} else {
return (a + b + c) / 2;
}
}
}
第三题:构造字典序最大的合并字符串
最初看到这个题目的时候,以为就是两路归并嘛,觉得很简单,结果直接就做错了。
仔细分析了一下,漏掉了一些情况:当word1和word2在index1和index2位置,要对比的字符相同的时候,要看后面的字符情况,要想构造最大的合并字符串,当碰到相同字符的时候,就要先选择子串更大的那一个,这样更容易把大的字符放在前面。
将问题再做精简,在判断从word1还是word2取字符的时候,直接用子串判断,就不用考虑index1和index2处位置字符是否相同了。
class Solution {
public String largestMerge(String word1, String word2) {
int index1 = 0, index2 = 0, length1 = word1.length(), length2 = word2.length();
StringBuilder stringBuilder = new StringBuilder(length1 + length2);
while (index1 < length1 && index2 < length2) {
// 判断子串谁大,先取子串大的,更容易把更大的字符放在前面
if (word1.substring(index1).compareTo(word2.substring(index2)) < 0) {
stringBuilder.append(word2.charAt(index2++));
} else {
stringBuilder.append(word1.charAt(index1++));
}
}
// 若word1还有字符
while (index1 < length1) {
stringBuilder.append(word1.charAt(index1++));
}
// 若word2还有字符
while (index2 < length2) {
stringBuilder.append(word2.charAt(index2++));
}
return stringBuilder.toString();
}
}
第四题:最接近目标值的子序列和
困难题是真的难想,以至于我看着题解的代码都看不懂,或许我刷题量不够吧,还需努力啊。
看着题解想了许久,好像有点眉目了,赶紧记录下来。
先上一段代码,仔细读里面的注释,就能看懂位运算的含义了。
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3};
int length = 1 << 3;
int[] sum = new int[length];
// [0, length - 1]:nums对应的所有组合,遍历每一种组合
for (int i = 1; i < length; i++) {
for (int j = 0; j < 3; j++) {
// 组合i,对应的二进制表示中,j位置是0,也就是没有选择nums[j],不做处理
if ((i & (1 << j)) == 0) {
continue;
}
// 组合i,对应的二进制表示中,j位置不是0,就要把nums[j]加到sum中
// nums[j]不加的时候,是sum[?]呢?
// 是sum[i - (1 << j)]:从i中扣除j位是1其余位是0二进制表示对应的整数
System.out.println("sum[" + i + "] = sum[" + i + " - (1 << " + j + ") = " + (i - (1 << j)) + "] + nums[" + j + "]");
sum[i] = sum[i - (1 << j)] + nums[j];
break;
}
}
for (int i = 1; i < length; i++) {
System.out.print(sum[i] + " ");
}
}
如果能理解上面的代码,那么就可以求得所有组合对应的子序列和了,后面就是遍历子序列和求abs()最小值了。
class Solution {
public int minAbsDifference(int[] nums, int goal) {
int length = nums.length, mid = length / 2, leftSize = 1 << mid, rightSize = 1 << (length - mid);
int[] leftSum = new int[leftSize], rightSum = new int[rightSize];
// 遍历所有的组合[0, leftSize - 1]
for (int i = 1; i < leftSize; i++) {
for (int j = 0; j < mid; j++) {
// 对每个组合i,判断i对应二进制的第j位置有没有选中,如果是1表示选中,要把nums[j]加到sum中
if ((i & (1 << j)) != 0) {
// 不加nums[j]的时候,原来组合的sum是多少呢?
// 从i里减去:j位置是1,其余位置是0对应二进制的整数,即是不选中nums[j]对应的sum值
leftSum[i] = leftSum[i - (1 << j)] + nums[j];
break;// 避免重复计算
}
}
}
for (int i = 1; i < rightSize; i++) {
for (int j = mid; j < length; j++) {
if ((i & (1 << (j - mid))) != 0) {
rightSum[i] = rightSum[i - (1 << (j - mid))] + nums[j];
break;
}
}
}
int min = Integer.MAX_VALUE;
// 最值组合对应的sum位于leftSum[]中
for (int i : leftSum) {
min = Math.min(Math.abs(i - goal), min);
}
// 最值组合对应的sum位于rightSum[]中
for (int i : rightSum) {
min = Math.min(Math.abs(i - goal), min);
}
// 最值组合对应的sum位于leftSum[]和rightSum[]中
Arrays.sort(leftSum);
Arrays.sort(rightSum);
int sum, left = 0, right = rightSize - 1;
while (left < leftSize && right >= 0) {
sum = leftSum[left] + rightSum[right];
min = Math.min(min, Math.abs(sum - goal));
if (sum < goal) {
left++;
} else {
right--;
}
}
return min;
}
}