LeetCode Weekly Contest 178

本文详细解析LeetCode周赛178的四道题目,包括5344. 有多少小于当前数字的数字、5345. 通过投票对团队排名、5346. 二叉树中的列表以及5347. 使网格图至少有一条有效路径的最小代价,涵盖思路和代码实现。
摘要由CSDN通过智能技术生成

5344. 有多少小于当前数字的数字

给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。

换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。

以数组形式返回答案。

示例 1:

输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
解释:
对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。
对于 nums[1]=1 不存在比它小的数字。
对于 nums[2]=2 存在一个比它小的数字:(1)。
对于 nums[3]=2 存在一个比它小的数字:(1)。
对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。
示例 2:

输入:nums = [6,5,4,8]
输出:[2,1,0,3]
示例 3:

输入:nums = [7,7,7,7]
输出:[0,0,0,0]

提示:

2 <= nums.length <= 500
0 <= nums[i] <= 100

思路

比赛的时候为了快速实现,使用了暴力统计的方法。如果要优化时间复杂度的话,可以使用排序的方法,可以将时间复杂度从O(n^2)降到O(nlogn).

代码

class Solution {
    public int[] smallerNumbersThanCurrent(int[] nums) {
        int n = nums.length, i = 0, j = 0;
        int[] ans = new int[n];
        for (i=0; i<n; ++i) {
            for (j=0; j<n; ++j) {
                if (i != j && nums[j] < nums[i]) {
                    ++ans[i];
                }
            }
        }
        return ans;
    }
}

5345. 通过投票对团队排名

现在有一个特殊的排名系统,依据参赛团队在投票人心中的次序进行排名,每个投票者都需要按从高到低的顺序对参与排名的所有团队进行排位。

排名规则如下:

参赛团队的排名次序依照其所获「排位第一」的票的多少决定。如果存在多个团队并列的情况,将继续考虑其「排位第二」的票的数量。以此类推,直到不再存在并列的情况。
如果在考虑完所有投票情况后仍然出现并列现象,则根据团队字母的字母顺序进行排名。
给你一个字符串数组 votes 代表全体投票者给出的排位情况,请你根据上述排名规则对所有参赛团队进行排名。

请你返回能表示按排名系统 排序后 的所有团队排名的字符串。

示例 1:

输入:votes = [“ABC”,“ACB”,“ABC”,“ACB”,“ACB”]
输出:“ACB”
解释:A 队获得五票「排位第一」,没有其他队获得「排位第一」,所以 A 队排名第一。
B 队获得两票「排位第二」,三票「排位第三」。
C 队获得三票「排位第二」,两票「排位第三」。
由于 C 队「排位第二」的票数较多,所以 C 队排第二,B 队排第三。
示例 2:

输入:votes = [“WXYZ”,“XYZW”]
输出:“XWYZ”
解释:X 队在并列僵局打破后成为排名第一的团队。X 队和 W 队的「排位第一」票数一样,但是 X 队有一票「排位第二」,而 W 没有获得「排位第二」。
示例 3:

输入:votes = [“ZMNAGUEDSJYLBOPHRQICWFXTVK”]
输出:“ZMNAGUEDSJYLBOPHRQICWFXTVK”
解释:只有一个投票者,所以排名完全按照他的意愿。
示例 4:

输入:votes = [“BCA”,“CAB”,“CBA”,“ABC”,“ACB”,“BAC”]
输出:“ABC”
解释:
A 队获得两票「排位第一」,两票「排位第二」,两票「排位第三」。
B 队获得两票「排位第一」,两票「排位第二」,两票「排位第三」。
C 队获得两票「排位第一」,两票「排位第二」,两票「排位第三」。
完全并列,所以我们需要按照字母升序排名。
示例 5:

输入:votes = [“M”,“M”,“M”,“M”]
输出:“M”
解释:只有 M 队参赛,所以它排名第一。

提示:

1 <= votes.length <= 1000
1 <= votes[i].length <= 26
votes[i].length == votes[j].length for 0 <= i, j < votes.length
votes[i][j] 是英文 大写 字母
votes[i] 中的所有字母都是唯一的
votes[0] 中出现的所有字母 同样也 出现在 votes[j] 中,其中 1 <= j < votes.length

思路

模拟题,实现一个自定义的排序类即可

代码

class Solution {
    private class Rank implements Comparable<Rank> {
        public char name;
        public int[] ranks;
        
        public Rank(char name, int[] ranks) {
            this.name = name;
            this.ranks = Arrays.copyOf(ranks, ranks.length);
        }
        
        @Override
        public int compareTo(Rank other) {
            int n = ranks.length, i = 0;
            for (i=0; i<n; ++i) {
                if (ranks[i] < other.ranks[i]) {
                    return 1;
                } else if (ranks[i] > other.ranks[i]) {
                    return -1;
                }
            }
            return name - other.name;
        }
    }
    
    public String rankTeams(String[] votes) {
        int v = votes.length, n = votes[0].length(), i = 0;
        int[][] rank = new int[n][n];
        char[] i2c = new char[n];
        int[] c2i = new int[26];
        i = 0;
        for (char c: votes[0].toCharArray()) {
            i2c[i] = c;
            c2i[c-'A'] = i++;
        }
        for (String vote: votes) {
            i = 0;
            for (char c: vote.toCharArray()) {
                ++rank[c2i[c-'A']][i++];
            }
        }
        StringBuilder sb = new StringBuilder();
        Rank[] objs = new Rank[n];
        for (i=0; i<n; ++i) {
            objs[i] = new Rank(i2c[i], rank[i]);
        }
        Arrays.sort(objs);
        for (i=0; i<n; ++i) {
            sb.append(objs[i].name);
        }
        return sb.toString();
    }
}

5346. 二叉树中的列表

给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。

如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。

一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。

示例 1:
在这里插入图片描述输入:head = [4,2,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
输出:true
解释:树中蓝色的节点构成了与链表对应的子路径。
示例 2:
在这里插入图片描述
输入:head = [1,4,2,6], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
输出:true
示例 3:

输入:head = [1,4,2,6,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
输出:false
解释:二叉树中不存在一一对应链表的路径。

思路

递归。对于每一个可以匹配链表头的位置都进行顺序判断。假设链表长度为m, 树的节点个数为n, 时间复杂度为O(mn).

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private boolean match(ListNode curList, TreeNode curTree) {
        if (curList == null) {
            return true;
        }
        if (curTree == null) {
            return false;
        }
        if (curList.val == curTree.val) {
            return match(curList.next, curTree.left) || match(curList.next, curTree.right);
        }
        return false;
    }
    
    public boolean isSubPath(ListNode head, TreeNode root) {
        if (root == null) {
            return false;
        }
        if (head.val == root.val) {
            return match(head, root) || isSubPath(head, root.left) || isSubPath(head, root.right);
        } else {
            return isSubPath(head, root.left) || isSubPath(head, root.right);
        }
    }
}

5347. 使网格图至少有一条有效路径的最小代价

给你一个 m x n 的网格图 grid 。 grid 中每个格子都有一个数字,对应着从该格子出发下一步走的方向。 grid[i][j] 中的数字可能为以下几种情况:

1 ,下一步往右走,也就是你会从 grid[i][j] 走到 grid[i][j + 1]
2 ,下一步往左走,也就是你会从 grid[i][j] 走到 grid[i][j - 1]
3 ,下一步往下走,也就是你会从 grid[i][j] 走到 grid[i + 1][j]
4 ,下一步往上走,也就是你会从 grid[i][j] 走到 grid[i - 1][j]
注意网格图中可能会有 无效数字 ,因为它们可能指向 grid 以外的区域。

一开始,你会从最左上角的格子 (0,0) 出发。我们定义一条 有效路径 为从格子 (0,0) 出发,每一步都顺着数字对应方向走,最终在最右下角的格子 (m - 1, n - 1) 结束的路径。有效路径 不需要是最短路径 。

你可以花费 cost = 1 的代价修改一个格子中的数字,但每个格子中的数字 只能修改一次 。

请你返回让网格图至少有一条有效路径的最小代价。

示例 1:
在这里插入图片描述
输入:grid = [[1,1,1,1],[2,2,2,2],[1,1,1,1],[2,2,2,2]]
输出:3
解释:你将从点 (0, 0) 出发。
到达 (3, 3) 的路径为: (0, 0) --> (0, 1) --> (0, 2) --> (0, 3) 花费代价 cost = 1 使方向向下 --> (1, 3) --> (1, 2) --> (1, 1) --> (1, 0) 花费代价 cost = 1 使方向向下 --> (2, 0) --> (2, 1) --> (2, 2) --> (2, 3) 花费代价 cost = 1 使方向向下 --> (3, 3)
总花费为 cost = 3.
示例 2:
在这里插入图片描述
输入:grid = [[1,1,3],[3,2,2],[1,1,4]]
输出:0
解释:不修改任何数字你就可以从 (0, 0) 到达 (2, 2) 。
示例 3:
在这里插入图片描述
输入:grid = [[1,2],[4,3]]
输出:1
示例 4:

输入:grid = [[2,2,2],[2,2,2]]
输出:3
示例 5:

输入:grid = [[4]]
输出:0

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 100

思路

令可以直接走到的邻居格点之间的距离为0, 令需要改变格点方向才能走到的邻居格点之间的距离为1,问题转化为求在格点邻接图上(0, 0)格点与(m-1, n-1)格点之间的最短距离。
由于格点之间的距离要么是0,要么是1,所以最短路可以使用扩展的BFS求解。相对于求解等权图的BFS,主要有两点不同:

  1. 扩展的BFS可以访问已经访问过的节点并将其入队,前提是更新过后节点到起始点的距离要小于更新之前的距离(否则会死循环);
  2. 距离当前取出的队首节点为0的节点入队时放入队首,距离当前取出的队首节点为1的节点入队时放入队尾,这样可以保证队列中的节点按距离起始节点的距离升序排列

令N = m * n等于格点总数,时间复杂度近似等于BFS的时间复杂度O(E + V) = O(N + 4N) = O(N) = O(mn),其中E表示BFS图中的边的数目,V表示BFS图中点的数目

代码

class Solution {
    private static final int[] MOVX = {0, 0, 1, -1}, MOVY = {1, -1, 0, 0};
    
    // for debug
    private void printDis(int[][] dis, int m, int n) {
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                System.out.print(dis[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("--------------");
    }
    
    public int minCost(int[][] grid) {
        int m = grid.length, n = grid[0].length, cur = 0, curx = 0, cury = 0, mov = 0, g = 0, nextx = 0, nexty = 0;
        int[][] dis = new int[m][n];
        for (int[] row: dis) {
            Arrays.fill(row, -1);       // -1: unvisited
        }        
        LinkedList<Integer> dq = new LinkedList<>();
        dq.add(0);
        dis[0][0] = 0;
        
        while (!dq.isEmpty()) {
            cur = dq.removeFirst();
            curx = cur / n;
            cury = cur % n;
            if (curx == m - 1 && cury == n - 1) {
                return dis[curx][cury];
            }
            g = grid[curx][cury] - 1;           // specified grid move
            for (mov = 0; mov < 4; ++mov) {
                nextx = curx + MOVX[mov];
                nexty = cury + MOVY[mov];
                if (nextx >= 0 && nextx < m && nexty >=0 && nexty < n) {
                    if (mov == g) {
                        if (dis[nextx][nexty] == -1 || dis[nextx][nexty] > dis[curx][cury]) {
                            dis[nextx][nexty] = dis[curx][cury];
                            dq.addFirst(nextx * n + nexty);
                        }
                    } else {
                        if (dis[nextx][nexty] == -1 || dis[nextx][nexty] > dis[curx][cury] + 1) {
                            dis[nextx][nexty] = dis[curx][cury] + 1;
                            dq.addLast(nextx * n + nexty);
                        }
                    }
                }
            }
        }
        return -1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值