周赛地址: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();
    }
}

第四题:最接近目标值的子序列和

困难题是真的难想,以至于我看着题解的代码都看不懂,或许我刷题量不够吧,还需努力啊。

看着题解想了许久,好像有点眉目了,赶紧记录下来。LeetCode第227场周赛_子序列


先上一段代码,仔细读里面的注释,就能看懂位运算的含义了。

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;
    }
}