【LeetCode】周赛纪录(三)第191场周赛20200531 数组中两元素的最大乘积 切割后面积最大的蛋糕 重新规划路线 两个盒子中球的颜色数相同的概率

本文详细解析了LeetCode周赛中的四道题目,包括寻找数组中两元素的最大乘积、切割蛋糕以获取最大面积、重新规划路线确保所有城市能到达城市0,以及计算两个盒子中球颜色数相同概率的问题。通过动态规划、排序、图的邻接表等方法进行求解。
摘要由CSDN通过智能技术生成

1464. 数组中两元素的最大乘积

难度:简单

题目描述

给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使(nums[i]-1)*(nums[j]-1)取得最大值。

请你计算并返回该式的最大值。

示例:

示例 1:
输入:nums = [3,4,5,2]
输出:12 
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。 

示例 2:
输入:nums = [1,5,4,5]
输出:16
解释:选择下标 i=1 和 j=3(下标从 0 开始),则可以获得最大值 (5-1)*(5-1) = 16 。

示例 3:
输入:nums = [3,7]
输出:12

Solution

需要找数组中最大的两个数字。

class Solution {
    public int maxProduct(int[] nums) {
        int max = 0;
        int max2 = 0;
        for(int num : nums){
            if(num > max){
                max2 = max;
                max = num;
            }else if(num > max2){
                max2 = num;
            }
        }
        return (max - 1) * (max2 - 1);
    }
}

1465. 切割后面积最大的蛋糕

难度;中等

题目描述

矩形蛋糕的高度为 h 且宽度为 w,给你两个整数数组 horizontalCuts 和 verticalCuts,其中 horizontalCuts[i] 是从矩形蛋糕顶部到第 i 个水平切口的距离,类似地, verticalCuts[j] 是从矩形蛋糕的左侧到第 j 个竖直切口的距离。

请你按数组 horizontalCuts 和 verticalCuts 中提供的水平和竖直位置切割后,请你找出 面积最大 的那份蛋糕,并返回其 面积 。由于答案可能是一个很大的数字,因此需要将结果对 10^9 + 7 取余后返回。

示例1:

img

输入:h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3]
输出:4 
解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色的那份蛋糕面积最大。

示例2:

img

输入:h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1]
输出:6
解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色和黄色的两份蛋糕面积最大。

示例 3:

输入:h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3]
输出:9

提示

  • 2 <= h, w <= 10^9
  • 1 <= horizontalCuts.length < min(h, 10^5)
  • 1 <= verticalCuts.length < min(w, 10^5)
  • 1 <= horizontalCuts[i] < h
  • 1 <= verticalCuts[i] < w
  • 题目数据保证 horizontalCuts 中的所有元素各不相同
  • 题目数据保证 verticalCuts 中的所有元素各不相同

Solution

先排序,然后找水平方向和垂直方向上的最长间隔。

注意头尾0和h,w的补齐。

最后相乘并取模。

class Solution {
    public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
        Arrays.sort(horizontalCuts);
        Arrays.sort(verticalCuts);
        // 头尾补齐
        int hh = horizontalCuts.length;
        int vv = verticalCuts.length;
        int[] newh = new int[hh + 2];
        int[] newv = new int[vv + 2];
        newh[0] = 0;
        newh[hh + 1] = h;
        newv[0] = 0;
        newv[vv + 1] = w;
        System.arraycopy(horizontalCuts, 0, newh, 1, hh);
        System.arraycopy(verticalCuts, 0, newv, 1, vv);
        hh +=2;
        vv +=2;
        // 找最长间隔
        long maxh = 0;
        long maxv = 0;
        for(int i = 0; i < hh-1; i++){
            maxh = Math.max(maxh, newh[i+1]-newh[i]);
        }
        for(int j = 0; j < vv-1; j++){
            maxv = Math.max(maxv, newv[j+1]-newv[j]);
        }
        int mod = 1000000007;
        return (int) ((maxh * maxv) % mod);
    }
}

注意求积的时候会溢出。只能用long不能用int。

long maxh = 0;
long maxv = 0;

不能是int,否则最后一个测试用例会报错。

不补齐的话,也可以先求完最大间隔,再验证首尾

class Solution {
    public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
        Arrays.sort(horizontalCuts);
        Arrays.sort(verticalCuts);
        int hh = horizontalCuts.length;
        int vv = verticalCuts.length;
        // 找最长间隔
        long maxh = 0;
        long maxv = 0;
        for(int i = 0; i < hh-1; i++){
            maxh = Math.max(maxh, horizontalCuts[i+1]-horizontalCuts[i]);
        }
        for(int j = 0; j < vv-1; j++){
            maxv = Math.max(maxv, verticalCuts[j+1]-verticalCuts[j]);
        }
        //验证首尾
        maxh = Math.max(maxh, horizontalCuts[0]);
        maxh = Math.max(maxh, h - horizontalCuts[hh-1]);
        maxv = Math.max(maxv, verticalCuts[0]);
        maxv = Math.max(maxv, w - verticalCuts[vv-1]);
        int mod = 1000000007;
        return (int) ((maxh * maxv) % mod);
    }
}

1466. 重新规划路线

难度:中等

题目描述

n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线。因此,要想在两座不同城市之间旅行只有唯一一条路线可供选择(路线网形成一颗树)。去年,交通运输部决定重新规划路线,以改变交通拥堵的状况。

路线用 connections 表示,其中 connections[i] = [a, b] 表示从城市 a 到 b 的一条有向路线。

今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0 。

请你帮助重新规划路线方向,使每个城市都可以访问城市 0 。返回需要变更方向的最小路线数。

题目数据保证每个城市在重新规划路线方向后都能到达城市 0 。

示例 1:

img

输入:n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]]
输出:3
解释:更改以红色显示的路线的方向,使每个城市都可以到达城市 0 。

示例 2:

img

输入:n = 5, connections = [[1,0],[1,2],[3,2],[3,4]]
输出:2
解释:更改以红色显示的路线的方向,使每个城市都可以到达城市 0 。

示例 3:

输入:n = 3, connections = [[1,0],[2,0]]
输出:0

提示:

2 <= n <= 5 * 10^4
connections.length == n-1
connections[i].length == 2
0 <= connections[i][0], connections[i][1] <= n-1
connections[i][0] != connections[i][1]

Solution

构建邻接表+BFS遍历

相似题目1462. 课程安排 IV

class Solution {
    public int minReorder(int n, int[][] connections) {
        int result = 0;
        Map<Integer, Set<Integer>> conn_idx = new HashMap<>();//表示含有城市i的connection的index
        boolean[] visited = new boolean[n];//某条边是否被访问过
        for(int i = 0; i < connections.length; i++){
            int city1 = connections[i][0];
            int city2 = connections[i][1];
            if(!conn_idx.containsKey(city1)){
                conn_idx.put(city1, new HashSet<>());
            }
            conn_idx.get(city1).add(i);
            if(!conn_idx.containsKey(city2)){
                conn_idx.put(city2, new HashSet<>());
            }
            conn_idx.get(city2).add(i);
        }
        // bfs
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(0);
        while(!queue.isEmpty()){
            int curr = queue.poll();
            // 对和curr相关的连接进行遍历,通过上面储存的连接的index
            for(int i : conn_idx.get(curr)){
                if(visited[i]){
                    continue;
                }
                visited[i] = true;
                int a = connections[i][0];//连接的起始
                int b = connections[i][1];//连接的终点
                // 如果当前点是出的,那么要修改为入,result++
                result += ((a == curr) ? 1 : 0);
                a = ((a == curr) ? b : a);
                queue.offer(a);
            }
        }
        return result;
    }
}

以下解法要求connections按照顺序给出(刚好这道题的测试用例都是按顺序的)

class Solution {
    public int minReorder(int n, int[][] connections) {
        Set<Integer> available = new HashSet<>();
        available.add(0);
        int change = 0;
        for(int[] line : connections){
            if(available.contains(line[1])){
                available.add(line[0]);
            }else{
                change++;
                available.add(line[1]);
            }
        }
        return change;
    }
}

1462. 课程安排 IV

难度:中等

题目描述

你总共需要上 n 门课,课程编号依次为 0 到 n-1 。

有的课会有直接的先修课程,比如如果想上课程 0 ,你必须先上课程 1 ,那么会以 [1,0] 数对的形式给出先修课程数对。

给你课程总数 n 和一个直接先修课程数对列表 prerequisite 和一个查询对列表 queries

对于每个查询对 queries[i] ,请判断 queries[i][0] 是否是 queries[i][1] 的先修课程。

请返回一个布尔值列表,列表中每个元素依次分别对应 queries 每个查询对的判断结果。

注意:如果课程 a 是课程 b 的先修课程且课程 b 是课程 c 的先修课程,那么课程 a 也是课程 c 的先修课程。

示例1:

输入:n = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
输出:[false,true]
解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。

示例2:

输入:n = 2, prerequisites = [], queries = [[1,0],[0,1]]
输出:[false,false]
解释:没有先修课程对,所以每门课程之间是独立的。

示例3:

输入:n = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
输出:[true,true]

示例4:

输入:n = 3, prerequisites = [[1,0],[2,0]], queries = [[0,1],[2,0]]
输出:[false,true]

示例5:

输入:n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]
输出:[true,false,true,false]

提示:

2 <= n <= 100
0 <= prerequisite.length <= (n * (n - 1) / 2)
0 <= prerequisite[i][0], prerequisite[i][1] < n
prerequisite[i][0] != prerequisite[i][1]
先修课程图中没有环。
先修课程图中没有重复的边。
1 <= queries.length <= 10^4
queries[i][0] != queries[i][1]

Solution

思路一:

图的邻接表+bfs

class Solution {
    public List<Boolean> checkIfPrerequisite(int n, int[][] prerequisites, int[][] queries) {
         HashMap<Integer, HashSet<Integer>> map = new HashMap<>();
         for(int[] val : prerequisites){
            // if(!map.containsKey(val[0])){
            //     map.put(val[0], new HashSet<>());
            // }
            // map.get(val[0]).add(val[1]);
             //Java8新增computeIfAbsent用法
            map.computeIfAbsent(val[0], k -> new HashSet<>()).add(val[1]);
        }
        List<Boolean> ans = new ArrayList<>();
        for(int[] num : queries){
            Queue<Integer> queue = new LinkedList<>();
            queue.offer(num[0]);
            boolean[] visited = new boolean[n];
            visited[num[0]] = true;
            boolean tmpRes = false;
            while(!queue.isEmpty()){
                int curr = queue.poll();
                if(curr == num[1]){
                    tmpRes = true;
                    break;
                }
                Set<Integer> next = map.getOrDefault(curr, new HashSet<>());
                for(int c : next){
                    if(visited[c]) continue;
                    visited[c] = true;
                    queue.offer(c);
                }
            }
            ans.add(tmpRes);
        }
        return ans;
    }
}

思路二:

打表法

判断二维表格中,i行j列是不是先导关系

class Solution {
	public List<Boolean> checkIfPrerequisite(int n, int[][] prerequisites, int[][] queries) {
		boolean[][] arr = new boolean[n][n];
		for (int[] prerequisite : prerequisites) {
			for (int i = 0; i < arr.length; i++) {
				if (i == prerequisite[0] || arr[i][prerequisite[0]]) {
					arr[i][prerequisite[1]] = true;
					for (int j = 0; j < n; j++) {
						if (arr[prerequisite[1]][j]) {
							arr[i][j] = true;
						}
					}
				}
			}
		}
		List<Boolean> result = new ArrayList<>(queries.length);
		for (int[] query : queries) {
			result.add(arr[query[0]][query[1]]);
		}
		return result;
	}
}

1467. 两个盒子中球的颜色数相同的概率

题目描述

桌面上有 2n 个颜色不完全相同的球,球上的颜色共有 k 种。给你一个大小为 k 的整数数组 balls ,其中 balls[i] 是颜色为 i 的球的数量。

所有的球都已经 随机打乱顺序 ,前 n 个球放入第一个盒子,后 n 个球放入另一个盒子(请认真阅读示例 2 的解释部分)。

注意:这两个盒子是不同的。例如,两个球颜色分别为 a 和 b,盒子分别为 [] 和 (),那么 [a] (b) 和 [b] (a) 这两种分配方式是不同的(请认真阅读示例 1 的解释部分)。

请计算「两个盒子中球的颜色数相同」的情况的概率。

示例 1:

输入:balls = [1,1]
输出:1.00000
解释:球平均分配的方式只有两种:

颜色为 1 的球放入第一个盒子,颜色为 2 的球放入第二个盒子

颜色为 2 的球放入第一个盒子,颜色为 1 的球放入第二个盒子
这两种分配,两个盒子中球的颜色数都相同。所以概率为 2/2 = 1 。

示例 2:

输入:balls = [2,1,1]
输出:0.66667
解释:球的列表为 [1, 1, 2, 3]
随机打乱,得到 12 种等概率的不同打乱方案,每种方案概率为 1/12 :
[1,1 / 2,3], [1,1 / 3,2], [1,2 / 1,3], [1,2 / 3,1], [1,3 / 1,2], [1,3 / 2,1], [2,1 / 1,3], [2,1 / 3,1], [2,3 / 1,1], [3,1 / 1,2], [3,1 / 2,1], [3,2 / 1,1]
然后,我们将前两个球放入第一个盒子,后两个球放入第二个盒子。
这 12 种可能的随机打乱方式中的 8 种满足「两个盒子中球的颜色数相同」。
概率 = 8/12 = 0.66667

示例3:

输入:balls = [1,2,1,2]
输出:0.60000
解释:球的列表为 [1, 2, 2, 3, 4, 4]。要想显示所有 180 种随机打乱方案是很难的,但只检查「两个盒子中球的颜色数相同」的 108 种情况是比较容易的。
概率 = 108 / 180 = 0.6 。

示例 4:

输入:balls = [3,2,1]
输出:0.30000
解释:球的列表为 [1, 1, 1, 2, 2, 3]。要想显示所有 60 种随机打乱方案是很难的,但只检查「两个盒子中球的颜色数相同」的 18 种情况是比较容易的。
概率 = 18 / 60 = 0.3 。

示例 5:

输入:balls = [6,6,6,6,6,6]
输出:0.90327

提示:

  • 1 <= balls.length <= 8
  • 1 <= balls[i] <= 6
  • sum(balls) 是偶数
  • 答案与真实值误差在 10^-5 以内,则被视为正确答案

Solution

参考 https://www.bilibili.com/video/BV1PA411q7c5

class Solution {
    public double getProbability(int[] balls) {
        int n = balls.length;
        int[] left = new int[n];
        int[] right = new int[n];
        Arrays.fill(left, 0);
        Arrays.fill(right, 0);
        int sum = 0;//一共有多个个球
        for(int ball : balls){
            sum += ball;
        }
        return dfs(0, balls, left, right, sum, 0, 0) / get(balls);
    }
    //求阶乘
    public double fact(int n){
        double res = 1;
        for(double i = 1; i <= n; i++){
            res *= i;
        }
        return res;
    }
    //求可重复排列问题
    public double get(int[] balls){
        int sum = 0;
        for(int ball : balls){
            sum += ball;
        }
        double tot = fact(sum);//总和的阶乘
        //除去每一个数
        for(int b : balls){
            if(b > 0){
                tot /= fact(b);
            }
        }
        return tot;
    }
    //枚举分类函数
    // u:当前枚举到第几种颜色
    // left, right :左右边枚举球的数量
    // ts:球的总数量   ls,rs:左边、右边球的总数量
    public double dfs(int u, int[] balls, int[] left, int[] right, int ts, int ls, int rs){
        //剪枝
        if(2 * ls > ts || 2 * rs > ts){
            return 0.0;
        }
        //边界,枚举完了所有情况
        if(u == balls.length){
            int l = 0;
            int r = 0;
            for(int x : left){
                if(x > 0) l++;
            }
            for(int x : right){
                if(x > 0) r++;
            }
            if(l != r){
                return 0.0;
            }else{
                return get(left) * get(right);
            }
        }
        // 枚举划分
        double res = 0;
        for(int i = 0; i <= balls[u]; i++){
            left[u] = i;
            right[u] = balls[u] - i;
            res += dfs(u+1, balls, left, right, ts, ls+left[u], rs+right[u]);
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值