跳至主要內容

力扣第 267 场周赛

大约 3 分钟

力扣第 267 场周赛

比赛时间 2021-11-14。纪念下第一次 AK。本场周赛国服共 402 人 AK。

T1. 买票需要的时间(3 分)

解题思路

模拟。

时间复杂度:O(nT),其中 T = tickets[k]

参考代码

class Solution {
    public int timeRequiredToBuy(int[] tickets, int k) {
        int len = tickets.length;
        int cnt = 0;
        while (tickets[k] > 0) {
            for (int i = 0; i < len; i++) {
                if (tickets[i] > 0) {
                    tickets[i]--;
                    cnt++;
                    // 退出循环
                    if (tickets[k] == 0) {
                        return cnt;
                    }
                }
            }
        }
        return cnt;
    }
}

T2. 反转偶数长度组的节点(4 分)

解题思路

模拟。翻转链表细节较多容易出错,比赛时可以先转化为数组,在数组的基础上进行模拟,再转换成链表返回。

时间复杂度:O(n)

参考代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseEvenLengthGroups(ListNode head) {
        // ListNode => int[]
        int[] nums = toArray(head);

        int len = nums.length;
        // group 长度
        int groupLen = 1;
        // 下标
        int left = 0;
        int right = 0;
        while (left <= right) {
            groupLen++;
            left = right + 1;
            right = Math.min(len - 1, left + groupLen - 1);
            // 反转 每个 偶数 长度组中的节点
            if ((right - left + 1) % 2 == 0) {
                swap(nums, left, right);
            }
        }

        // int[] => ListNode
        return toListNode(nums);
    }

    private void swap(int[] arr, int left, int right) {
        int len = right - left + 1;
        for (int i = 0; i < len / 2; i++) {
            int tmp = arr[left + i];
            arr[left + i] = arr[left + len - i - 1];
            arr[left + len - i - 1] = tmp;
        }
    }

    private int[] toArray(ListNode head) {
        List<Integer> list = new ArrayList<>();
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            res[i] = list.get(i);
        }
        return res;
    }

    private ListNode toListNode(int[] nums) {
        ListNode dummy = new ListNode(-1);
        ListNode head = dummy;
        for (int num : nums) {
            head.next = new ListNode(num);
            head = head.next;
        }
        return dummy.next;
    }
}

T3. 解码斜向换位密码(5 分)

解题思路

矩阵模拟。注意 originalText 不含尾随空格,需特殊处理(可用 String#replaceAll("\\s+$", "") 去掉尾随空格,jdk11 可使用 String#stripTrailing())。

时间复杂度:O(n)

参考代码

class Solution {
    public String decodeCiphertext(String encodedText, int rows) {
        int len = encodedText.length();
        int cols = len / rows;
        char[][] chars = new char[rows][cols];
        int idx = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                chars[i][j] = encodedText.charAt(idx++);
            }
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < cols; i++) {
            for (int j = 0; j < rows; j++) {
                if (j + i < cols) {
                    stringBuilder.append(chars[j][j + i]);
                }
            }
        }
        return stringBuilder.toString().replaceAll("\\s+$","");
    }
}

T4. 处理含限制条件的好友请求(6 分)

解题思路

并查集。对每一次询问,在 α(n) 时间复杂度内判断是否违反限制条件,如果违反限制条件,并查集需进行回退。

时间复杂度:O(Q*R*α(n)),其中 Q 为查询数,R 为限制数,α 表示阿克曼函数的反函数,在宇宙可观测的 n 内(例如宇宙中包含的粒子总数),α(n) 不会超过 5。

参考代码

class Solution {
    public boolean[] friendRequests(int n, int[][] restrictions, int[][] requests) {
        UnionFind unionFind = new UnionFind(n);
        int len = requests.length;
        boolean[] res = new boolean[len];
        Arrays.fill(res, true);

        for (int i = 0; i < len; i++) {
            // 先并
            unionFind.union(requests[i][0], requests[i][1]);
            for (int[] restriction : restrictions) {
                if (unionFind.connected(restriction[0], restriction[1])) {
                    res[i] = false;
                    // 不行就复原
                    unionFind.reset();
                    break;
                }
            }
        }
        return res;
    }

    private static class UnionFind {
        // 记录每个节点的父节点
        int[] parent;
        // 记录每棵树的重量
        int[] rank;
        // (可选) 连通分量
        int count;
        // 记录上一个状态
        int[] preParent;

        public UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                rank[i] = i;
            }
            count = n;
        }

        // 返回节点 x 的根节点
        private int find(int x) {
            int ret = x;
            while (ret != parent[ret]) {
                // 路径压缩
                parent[ret] = parent[parent[ret]];
                ret = parent[ret];
            }
            return ret;
        }

        // 将 p 和 q 连通
        public void union(int p, int q) {
            preParent = parent.clone();
            int rootP = find(p);
            int rootQ = find(q);
            if (rootP != rootQ) {
                if (rank[rootP] > rank[rootQ]) {
                    parent[rootQ] = rootP;
                } else if (rank[rootP] < rank[rootQ]) {
                    parent[rootP] = rootQ;
                } else {
                    parent[rootQ] = rootP;
                    // 重量平衡
                    rank[rootP] += 1;
                }
                count--;
            }
        }

        // p 和 q 是否连通
        public boolean connected(int p, int q) {
            int rootP = find(p);
            int rootQ = find(q);
            return rootP == rootQ;
        }

        public void reset() {
            parent = preParent.clone();
        }
    }
}

(全文完)