LeetCode第238场周赛

本文介绍了LeetCode周赛中的四道题目,包括K进制表示下的各位数字总和、最高频元素的频数、所有元音按顺序排布的最长子字符串以及最高建筑高度。解析了每道题目的两种解法,涉及进制转换、数组操作、字符串处理和动态规划等算法思想,旨在帮助读者理解和掌握相关编程技巧。
摘要由CSDN通过智能技术生成

周赛地址:https://leetcode-cn.com/contest/weekly-contest-238

第一题:K 进制表示下的各位数字总和

一道进制转换的题目,对于Java语言有两种做法:调用 t o S t r i n g ( n , k ) ; toString(n, k); toString(n,k);函数;取余求和。

class Solution {
    public int sumBase(int n, int k) {
        int sum = 0;
        // toString(n, k);可以将n转换成k进制的字符串形式
        for (char c : Integer.toString(n, k).toCharArray()) {
            sum += c - '0';
        }
        return sum;
    }
}
class Solution {
    public int sumBase(int n, int k) {
        int result = 0;
        while (n != 0) {
        	// 对k取余求和
            result += n % k;
            n /= k;
        }
        return result;
    }
}

第二题:最高频元素的频数

思考朴素做法,可知时间复杂度是 O ( N 2 ) O(N^{2}) O(N2)的,所以需要优化。
显然,频次最高的数字一定是原数组中的数,否则,就有更优解。如果存在一个解 a n s w e r answer answer不属于原数组,那么,原数组中,比 a n s w e r answer answer小的数字一定也满足最高频元素,此时需要的代价少于将解化为 a n s w e r answer answer的代价,所以,解一定来自原数组中的数。
假设答案是 a n s w e r answer answer,优先操作距离 a n s w e r answer answer最近的数字可以获得最优解。
既然最终解位于原数组,所以遍历原数组,对于每个数字 a n s w e r answer answer,把总的代价 k k k,按照由远及近的方向,分配到前面的数字里,使前面的数字变为 a n s w e r answer answer,求得最远可以触及到的下标。
一种是前缀和+二分,一种是滑动窗口。

class Solution {
    public int maxFrequency(int[] nums, int k) {
        Arrays.sort(nums);
        int length = nums.length, answer = 0;
        long[] sum = new long[length + 1];// 前缀和
        for (int i = 0; i < length; i++) {
            // sum[i]:前i个数的和
            sum[i + 1] = sum[i] + nums[i];
        }
        // 假设频次最高的数字是nums[i],使用代价k最多可以让多少个数字变成nums[i]
        for (int i = 0; i < length; i++) {
            int left = binarySearch(sum, k, nums, i);
            answer = Math.max(answer, i - left + 1);
        }
        return answer;
    }

    /**
     * @param sum      前缀和
     * @param k        操作次数的上限
     * @param nums     原数组
     * @param aimIndex 频次最高数字的下标
     * @return 二分查找满足条件的左边界
     */
    private int binarySearch(long[] sum, int k, int[] nums, int aimIndex) {
        int l = 0, r = aimIndex;
        while (l <= r) {
            int mid = (l + r) / 2;
            // 把nums数组中[mid, aimIndex]区间的值都变成nums[aimIndex]所需要的代价,利用前缀和求解
            long cost = (long) (aimIndex - mid + 1) * nums[aimIndex] - (sum[aimIndex + 1] - sum[mid]);
            if (cost < k) {
                r = mid - 1;
            } else if (cost > k) {
                l = mid + 1;
            } else {
                return mid;
            }
        }
        return l;
    }
}
class Solution {
    public int maxFrequency(int[] nums, int k) {
        Arrays.sort(nums);
        int length = nums.length, answer = 0, l = 0, r = 0;
        long sum = 0;
        while (r < length) {
            sum += nums[r];
            // 把nums的[l,r]区间都变成nums[r]所需要的代价超过k,收缩左边界
            while ((long) (r - l + 1) * nums[r] - sum > k) {
                sum -= nums[l++];
            }
            answer = Math.max(answer, r - l + 1);
            r++;
        }
        return answer;
    }
}

第三题:所有元音按顺序排布的最长子字符串

遍历字符串,同时对比当前字符和前一个字符,考虑这几种情形:
如果当前字符等于前一个字符,下标后移;
如果当前字符大于前一个字符,kind++,kind的含义是当前遇到了几种字符;
如果当前字符小于前一个字符,重新开始计算kind,left归为为当前位置。
在遍历的过程中,如果出现 k i n d = = 5 kind==5 kind==5,那么,就记录一次 r i g h t − l e f t + 1 right-left+1 rightleft+1,并维护这个值的最大值。

class Solution {
    public int longestBeautifulSubstring(String word) {
        int length = word.length(), left = 0, maxLength = 0, kind = 1;
        for (int i = 1; i < length; i++) {
            char previous = word.charAt(i - 1), current = word.charAt(i);
            if (current > previous) {
                kind++;
            } else if (current < previous) {
                kind = 1;
                left = i;
            }
            if (kind == 5) {
                maxLength = Math.max(maxLength, i - left + 1);
            }
        }
        return maxLength;
    }
}

第四题:最高建筑高度

题目有两种做法:约束传递;差分约束。
已知 n n n 1 0 9 10^{9} 109数量级的,即使是 O ( n ) O(n) O(n)也会超时。观察到restrictions是 1 0 5 10^{5} 105数量级,而且某个建筑的高度可能根本达不到 r e s t r i c t i o n s [ i ] [ 1 ] restrictions[i][1] restrictions[i][1],可以从 r e s t r i c t i o n s restrictions restrictions考虑,对于每两个相邻的 r e s t r i c t i o n s restrictions restrictions之间的建筑,我们只关心这两个相邻的 r e s t r i c t i o n s restrictions restrictions之间的最大值,无需把这两个相邻的 r e s t r i c t i o n s restrictions restrictions之间的所有建筑高度都求解出来。
只观察这些 r e s t r i c t i o n s restrictions restrictions,假设 i i i j j j是相邻的两个 r e s t r i c t i o n s restrictions restrictions的下标,且 i < j i<j i<j,根据题意,需要满足高度差≤距离差,即: a b s ( r e s t r i c t i o n s [ j ] − r e s t r i c t i o n s [ i ] ) ≤ j − i abs(restrictions[j]-restrictions[i])≤j-i abs(restrictions[j]restrictions[i])ji
在从左向右看的时候,右侧的建筑高度受到左侧相邻建筑高度的限制;
在从右向左看的时候,左侧的建筑高度受到右侧相邻建筑高度的限制。
因此,需要跑两遍,将当前可以确定的约束关系传递给下一个建筑,用以确定下一个建筑的实际限制高度,通过左右限制,确定建筑物实际的限制高度。当相邻的两个实际限制高度已经确定了之后,就可以利用相邻限高建筑高度和距离求得两相邻限制之间的最高值了,再遍历这些最高值,选择最大值即可。

class Solution {
    public static int maxBuilding(int n, int[][] restrictions) {
        int length = restrictions.length, newLength;
        if (length == 0) {// 所有建筑的高度都没有限制
            return n - 1;
        }
        // 按照id升序排序
        Arrays.sort(restrictions, Comparator.comparingInt(r -> r[0]));
        if (restrictions[length - 1][0] != n) {// 需要加上{n, n - 1}限制和{1, 0}限制
            newLength = length + 2;
        } else {// 需要加上{1, 0}限制
            newLength = length + 1;
        }
        int[][] r = new int[newLength][2];
        // 将{1, 0}加到约束数组中
        r[0][0] = 1;
        r[0][1] = 0;
        // 将约束转移到新数组中
        for (int i = 0; i < length; i++) {
            r[i + 1][0] = restrictions[i][0];
            r[i + 1][1] = restrictions[i][1];
        }
        // 如果最后一个约束建筑的编号不是n,将{n, n - 1}加到约束数组中
        if (restrictions[length - 1][0] != n) {
            r[newLength - 1][0] = n;
            r[newLength - 1][1] = n - 1;
        }
        // 从左到右传递约束,修改真实约束高度
        for (int i = 1; i < newLength; i++) {
            r[i][1] = Math.min(r[i][1], r[i - 1][1] + r[i][0] - r[i - 1][0]);
        }
        // 从右向左传递约束,修改真实约束高度
        for (int i = newLength - 2; i >= 0; i--) {
            r[i][1] = Math.min(r[i][1], r[i + 1][1] + r[i + 1][0] - r[i][0]);
        }
        int answer = 0;
        // 这maxLength个高度约束,将n分隔成maxLength - 1个区间,分别求出每个区间内的最大值
        for (int i = 1; i < newLength; i++) {
            answer = Math.max(answer, (r[i][0] - r[i - 1][0] + r[i][1] + r[i - 1][1]) / 2);
        }
        return answer;
    }
}

根据题意可知,相邻的两个 r e s t r i c t i o n s restrictions restrictions,需要满足:高度差≤距离差,可以列出如下差分约束方程:
{ r e s t r i c t i o n s [ 1 ] [ 1 ] − r e s t r i c t i o n s [ 0 ] [ 1 ] ≤ r e s t r i c t i o n s [ 1 ] [ 0 ] − r e s t r i c t i o n s [ 0 ] [ 0 ] r e s t r i c t i o n s [ 2 ] [ 1 ] − r e s t r i c t i o n s [ 1 ] [ 1 ] ≤ r e s t r i c t i o n s [ 2 ] [ 0 ] − r e s t r i c t i o n s [ 1 ] [ 0 ] r e s t r i c t i o n s [ 3 ] [ 1 ] − r e s t r i c t i o n s [ 2 ] [ 1 ] ≤ r e s t r i c t i o n s [ 3 ] [ 0 ] − r e s t r i c t i o n s [ 2 ] [ 0 ] … … r e s t r i c t i o n s [ m − 1 ] [ 1 ] − r e s t r i c t i o n s [ m − 2 ] [ 1 ] ≤ r e s t r i c t i o n s [ m − 1 ] [ 0 ] − r e s t r i c t i o n s [ m − 2 ] [ 0 ] \left\{ \begin{aligned} restrictions[1][1] - restrictions[0][1]≤restrictions[1][0]-restrictions[0][0]\\ restrictions[2][1] - restrictions[1][1]≤restrictions[2][0]-restrictions[1][0]\\ restrictions[3][1] - restrictions[2][1]≤restrictions[3][0]-restrictions[2][0]\\ ……\\ restrictions[m-1][1] - restrictions[m-2][1]≤restrictions[m-1][0]-restrictions[m-2][0]\\ \end{aligned} \right. restrictions[1][1]restrictions[0][1]restrictions[1][0]restrictions[0][0]restrictions[2][1]restrictions[1][1]restrictions[2][0]restrictions[1][0]restrictions[3][1]restrictions[2][1]restrictions[3][0]restrictions[2][0]restrictions[m1][1]restrictions[m2][1]restrictions[m1][0]restrictions[m2][0]
根据差分约束系统的规则,如果存在 d [ i ] − d [ j ] ≤ w d[i]-d[j]≤w d[i]d[j]w,移项得 d [ i ] ≤ d [ j ] + w d[i]≤d[j]+w d[i]d[j]+w,那么,就建立一条 j j j i i i权值为 w w w的有向边。可知,当跑完最短路算法后,图中的元素满足 d [ i ] ≤ d [ j ] + w d[i]≤d[j]+w d[i]d[j]+w,因为 d [ i ] > d [ j ] + w d[i]>d[j]+w d[i]>d[j]+w的边都被松弛了。因此利用差分约束,跑最短路求解出建筑物实际的限制高度,后面的结果计算就一样了。

class Solution {
    int[][] r;
    int[] distance;
    boolean[] visit;
    PriorityQueue<int[]> priorityQueue;
    Map<Integer, List<int[]>> graph;

    public int maxBuilding(int n, int[][] restrictions) {
        int length = restrictions.length, newLength, answer = 0;
        if (length == 0) {
            return n - 1;
        }
        Arrays.sort(restrictions, Comparator.comparingInt(r -> r[0]));
        if (restrictions[length - 1][0] == n) {
            newLength = length + 1;
        } else {
            newLength = length + 2;
        }
        init(newLength);
        r[0] = new int[]{1, 0};
        for (int i = 0; i < length; i++) {
            r[i + 1][0] = restrictions[i][0];
            r[i + 1][1] = restrictions[i][1];
        }
        if (restrictions[length - 1][0] != n) {
            r[newLength - 1] = new int[]{n, n - 1};
        }
        // 设相邻的两个限高建筑编号为i和j且i < j,限高为r[i]和r[j],应满足r[i] - r[j] ≤ j - i && r[j] - r[i] ≤ j - i
        for (int i = 1; i < newLength; i++) {
            // 建边的时候,用下标建,不用编号,因为编号可能到1e9,开不了这么大的数组
            add(i, i - 1, r[i][0] - r[i - 1][0]);
            add(i - 1, i, r[i][0] - r[i - 1][0]);
        }
        dijkstra(newLength);
        for (int i = 1; i < newLength; i++) {
            answer = Math.max(answer, (r[i][0] - r[i - 1][0] + distance[i] + distance[i - 1]) / 2);
        }
        return answer;
    }

    private void init(int newLength) {
        r = new int[newLength][2];
        distance = new int[newLength];
        visit = new boolean[newLength];
        priorityQueue = new PriorityQueue<>(Comparator.comparingInt(r -> r[1]));
        graph = new HashMap<>();
    }

    private void add(int u, int v, int w) {
        graph.computeIfAbsent(u, k -> new ArrayList<>()).add(new int[]{v, w});
    }

    /**
     * 写法和常规的dijkstra有不同
     * 常规的写法是指定一个起始节点,从这个结点开始做dijkstra
     * 这里没有指定起始节点,而是根据distance[i]从小到大的顺序做dijkstra,这样可以将过高的限高条件压低到实际的限高条件
     */
    private void dijkstra(int newLength) {
        // 将所有限高都放入优先队列
        for (int i = 0; i < newLength; i++) {
            distance[i] = r[i][1];// distance[i] = r[i][1] - r[0][1];因为r[0][1] == 0,所以写法上忽略了r[0][1]
            priorityQueue.offer(new int[]{i, distance[i]});
        }
        int[] temp;
        int v;
        // 从限高小的建筑开始求最短路进行松弛,这样可以把过大的限高给降低到实际的限高
        // 比如:i = 1,j = 3,r[i] = 2,r[j] = 100,对于r[j]来说,100是个没有约束力的条件,因为r[j]最大可以取到4,始终是小于100的,所以这里要更新r[j]的值为它实际的限高
        while (!priorityQueue.isEmpty()) {
            temp = priorityQueue.poll();
            v = temp[0];
            if (!visit[v]) {
                visit[v] = true;
                for (int[] edge : graph.getOrDefault(v, new ArrayList<>())) {
                    if (!visit[edge[0]] && distance[v] + edge[1] < distance[edge[0]]) {
                        distance[edge[0]] = distance[v] + edge[1];
                        priorityQueue.offer(new int[]{edge[0], distance[edge[0]]});
                    }
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值