LeetCode第157场周赛(Weekly Contest 157)解题报告

惨不忍睹,这周周赛。周赛前一晚,和朋友出去吃烧烤喝酒,到4点才睡,早上九点半就起床了,导致脑袋空空,做题都呆住了。隔一天,休息好了来补题,发现都很简单。

第一题,就是一个思维题,弄清题目代码一下子就解决了。

第二题,可以使用Map的key-value,value表示对应这个数,定差序列的最大长度。也可以利用大数组,下标表示key,值表示value来代替Map。

第三题,DFS+回溯,因为“迷宫范围小”。

第四题,动态规划DP问题。

详细题解如下。


1. 玩筹码(Play with Chips)

           AC代码(Java)

2. 最长定差子序列(Longest Arithmetic Subsequence of Given Difference)

           AC代码(Java,利用HashMap)

           AC代码(Java,利用一维数组)

3.黄金矿工(Path with Maximum Gold)

           AC代码(Java)

4.统计元音字母序列的数目(Count Vowels Permutation)

           AC代码(Java,二维数组dp)

           AC代码(Java,滚动数组,一维)


LeetCode第157场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-157


1. 玩筹码(Play with Chips)

题目链接

https://leetcode-cn.com/contest/weekly-contest-157/problems/play-with-chips/

题意

数轴上放置了一些筹码,每个筹码的位置存在数组 chips 当中。

你可以对 任何筹码 执行下面两种操作之一(不限操作次数,0 次也可以):

  • 将第 i 个筹码向左或者右移动 2 个单位,代价为 0
  • 将第 i 个筹码向左或者右移动 1 个单位,代价为 1

最开始的时候,同一位置上也可能放着两个或者更多的筹码。

返回将所有筹码移动到同一位置(任意位置)上所需要的最小代价。

示例 1:

输入:chips = [1,2,3]
输出:1
解释:第二个筹码移动到位置三的代价是 1,第一个筹码移动到位置三的代价是 0,总代价为 1。

示例 2:

输入:chips = [2,2,2,3,3]
输出:2
解释:第四和第五个筹码移动到位置二的代价都是 1,所以最小总代价为 2。

提示:

  1. 1 <= chips.length <= 100
  2. 1 <= chips[i] <= 10^9

解题思路

这道题主要要理解好题意。

如果移动两个单位,代价为0,说明同为偶数或者同为奇数之间的相互变化是不需要代价的。

移动一个单位,代价为1,我们只要将对应需要移动的变到相邻位置,剩下再移动就不需要代价了,因此主要就是考虑相邻位置之间变动的代价。

由于同为偶数或者同为奇数之间移动不需要代价,那么为了全部变成相同的数,要么把奇数变成偶数,要么把偶数变成奇数。

但为了代价最小,我们需要得到奇数和偶数的个数,选择较小个数的那个进行变化,这样子的代价就是最小的。

因此这道题,就是求偶数和奇数个数的最小值。

AC代码(Java)

class Solution {
    public int minCostToMoveChips(int[] chips) {
        int cnt1 = 0, cnt2 = 0;
        
        for(int chip : chips)
        {
            if(chip % 2 == 0)
                ++cnt1;
            else
                ++cnt2;
        }
        
        return Math.min(cnt1, cnt2);
            
    }
}

 


2. 最长定差子序列(Longest Arithmetic Subsequence of Given Difference)

题目链接

https://leetcode-cn.com/contest/weekly-contest-157/problems/longest-arithmetic-subsequence-of-given-difference/

题意

给你一个整数数组 arr 和一个整数 difference,请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列,并返回其中最长的等差子序列的长度。

示例 1:

输入:arr = [1,2,3,4], difference = 1
输出:4
解释:最长的等差子序列是 [1,2,3,4]。

示例 2:

输入:arr = [1,3,5,7], difference = 1
输出:1
解释:最长的等差子序列是任意单个元素。

示例 3:

输入:arr = [1,5,7,8,5,3,4,2,1], difference = -2
输出:4
解释:最长的等差子序列是 [7,5,3,1]。

提示:

  1. 1 <= arr.length <= 10^5
  2. -10^4 <= arr[i], difference <= 10^4

解题思路

根据题目提示的要求,数组长度是10^5,因此算法的时间复杂度应该是O(n)或者O(nlogn)才会不超时。

O(n)应该就是要求扫一遍数组就要能够得出答案。

比如示例1,[1,2,3,4]

  • 先是1,利用1 - difference = 1-1 = 0,发现没有0,所以我们就把这个数认为是新的一个起点,记录为 (1,1),第一位数是表示这个数值,第二个数表示定差序列的最大长度
  • 到2,2-1 = 1,发现有 1 ,那么记录 (2,数值为1对应的长度 + 1)=(2,2)
  • .......

所以主要是对于当前数,找 当前数 - difference,如果存在,就把那个数对应的最大长度 + 1,并且存起来;如果不存在,说明这个数是新的起点,也就是长度记为1。

那么对于(1,1),看成(key,value)这个记录方法,可以考虑利用HashMap,也就是 key - value的方法,查找时间为O(1),不会超时。

当然也可以利用一个一维数组,利用数组的下标对应 key,而该位置存放的值为value。如果用数组的话,因为数组下标一定是大于等于0的,而数和差可能有正有负,因此判断最小的应该是 -2*10^4,所以所有数都加上至少 2*10^4 保证非负。这样子数组至少要开 4*10^4 那么大。

AC代码(Java,利用HashMap)

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        
        int res = 1;
        Map  map = new HashMap();
        
        for(int i = 0 ;i < arr.length;i++)
        {
            int temp = arr[i] - difference;
            if(map.containsKey(temp))
            {
                int v = 1 + map.get(temp);
                map.put(arr[i], v);
                res = Math.max(res, v);
            }
            else
            {
                map.put(arr[i], 1);
            }
                
            
        }
        return res;
    }
}

AC代码(Java,利用一维数组)

class Solution {
    
    final int ADD = 30000;
    final int MAXN = 100000;
    int[] cnts = new int[MAXN];
    
    
    public int longestSubsequence(int[] arr, int difference) {
        
        Arrays.fill(cnts, 0);
        int res = 1;
        
        for(int x : arr)
        {
            if(cnts[x-difference+ADD] > 0)
            {
                cnts[x+ADD] = cnts[x-difference+ADD] + 1;
                res = Math.max(res, cnts[x+ADD]);
            }
            else
            {
                cnts[x + ADD] = 1;
            }
            
        }
        
        return res;
    }
}

对比两个,虽然HashMap说是O(1)的查找时间,但是一些操作还是会耗时,因此使用HashMap的方法比用一维数组的方法的时间复杂度更大,而空间复杂度相差不大。

 


3.黄金矿工(Path with Maximum Gold)

题目链接

https://leetcode-cn.com/contest/weekly-contest-157/problems/path-with-maximum-gold/

题意

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0

为了使收益最大化,矿工需要按以下规则来开采黄金:

  • 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
  • 矿工每次可以从当前位置向上下左右四个方向走。
  • 每个单元格只能被开采(进入)一次。
  • 不得开采(进入)黄金数目为 0 的单元格。
  • 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。

示例 1:

输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
 [5,8,7],
 [0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。

示例 2:

输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释:
[[1,0,7],
 [2,0,6],
 [3,4,5],
 [0,3,0],
 [9,0,20]]
一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。

提示:

  1. 1 <= grid.length, grid[i].length <= 15
  2. 0 <= grid[i][j] <= 100
  3. 最多 25 个单元格中有黄金。

解题分析

类似迷宫题,而且迷宫大小最多 15*15,因此想到使用 DFS + 回溯

遍历所有可能的起点,从某个起点开始,DFS走完所有可能的地方,然后每次都保留得到的黄金最大值。

 

AC代码(Java)

class Solution {
    
    int[] dx = new int[]{1, 0, -1, 0};
    int[] dy = new int[]{0, 1, 0, -1};
    
    // DFS + 回溯
    int ans = 0;
    int[][] vis = new int[20][20];
    int n,m;
    
    
    
    public void dfs(int x, int y, int cur)
    {
        int rev = vis[x][y];
        cur += vis[x][y];
        ans = Math.max(ans, cur);
        
        vis[x][y] = 0;
        
        for(int i = 0;i < 4;i++)
        {
            int tx = x + dx[i], ty = y + dy[i];
            if(tx>=0 && tx=0 && ty0)
                dfs(tx, ty, cur);
        }
        vis[x][y] = rev;
        
        return;
        
    }
    
    public int getMaximumGold(int[][] grid) {
        
        n = grid.length;
        m = grid[0].length;
        
        // vis 标记,0表示走过了或者不能走
        // 非0 表示还可以走
        for(int i = 0;i < n;i++)
            for(int j = 0;j < m;j++)
            {
                vis[i][j] = grid[i][j];
            }
        
        ans = 0;
        
        for(int i = 0;i < n;i++)
            for(int j = 0;j < m; j++)
                if(grid[i][j] > 0)
                    dfs(i, j, 0);
        
        return ans;
    }
    
}

 


4.统计元音字母序列的数目(Count Vowels Permutation)

题目链接

https://leetcode-cn.com/contest/weekly-contest-157/problems/count-vowels-permutation/

题意

给你一个整数 n,请你帮忙统计一下我们可以按下述规则形成多少个长度为 n 的字符串:

  • 字符串中的每个字符都应当是小写元音字母('a''e''i''o''u'
  • 每个元音 'a' 后面都只能跟着 'e'
  • 每个元音 'e' 后面只能跟着 'a' 或者是 'i'
  • 每个元音 'i' 后面 不能 再跟着另一个 'i'
  • 每个元音 'o' 后面只能跟着 'i' 或者是 'u'
  • 每个元音 'u' 后面只能跟着 'a'

由于答案可能会很大,所以请你返回 模 10^9 + 7 之后的结果。

示例 1:

输入:n = 1
输出:5
解释:所有可能的字符串分别是:"a", "e", "i" , "o" 和 "u"。

示例 2:

输入:n = 2
输出:10
解释:所有可能的字符串分别是:"ae", "ea", "ei", "ia", "ie", "io", "iu", "oi", "ou" 和 "ua"。

示例 3:

输入:n = 5
输出:68

提示:

  1. 1 <= n <= 2 * 10^4

解题分析

动态规划的一道题,利用 dp[i][c],表示在长度为 i 的时候,以 字母 c 结尾的种类。

根据题目的要求:

  • 当结尾是 a 时,前一位只能是 e,i,u
  • 当结尾是 e 时,前一位只能是 a,i
  • 当结尾是 i 时,前一位只能是 e,o
  • 当结尾是 o 时,前一位只能是 i
  • 当结尾是 u 时,前一位只能是 i,o

为了表示方便,将 a,e,i,o,u 映射为0,1,2,3,4。

所以根据上面的要求,就可以列出状态转移方程出来。

最后的答案,就是把所有 dp[n][0-4] 进行累和

 

而上面的方法是利用二维数组的,内存开销大,所以进行改进,利用滚动数组,设两个一维数组

dp[ ] 和 f[ ],其中dp还是表示在长度为 i 的时候,以 字母 c 结尾的种类。而进行一次新的计算时,先利用 f[ ] 记录

计算完当前长度的所有情况后,把 f[ ] 的值才对应赋值给 dp[ ]。不然中途就更新了 dp[ ],会导致计算错误。

AC代码(Java,二维数组dp)

class Solution {
    
    final int MAXN = (int)2e4 + 10;
    final int MOD = (int)1e9 + 7;
    
    long[][] dp = new long[MAXN][5];
    
    
    public int countVowelPermutation(int n) {
        
        for(int i = 0;i < 5;i++)
            dp[1][i] = 1;
        
        for(int i = 2;i <= n;i++)
        {
            dp[i][0] = (dp[i-1][1] + dp[i-1][2] + dp[i-1][4]) % MOD;
            dp[i][1] = (dp[i-1][0] + dp[i-1][2]) % MOD;
            dp[i][2] = (dp[i-1][1] + dp[i-1][3]) % MOD;
            dp[i][3] = (dp[i-1][2]) % MOD;
            dp[i][4] = (dp[i-1][2] + dp[i-1][3]) % MOD;
        }
        long ans = 0;
        for(int i = 0;i < 5;i++)
            ans = (ans + dp[n][i]) % MOD;
        
        return (int)ans;
        
    }
}

AC代码(Java,滚动数组,一维)

class Solution {
    
    final int MOD = (int)1e9 + 7;
    
    long[] dp = new long[5];
    long[] f = new long[5];
    
    
    public int countVowelPermutation(int n) {
        
        for(int i = 0;i < 5;i++)
            dp[i] = 1;
        
        for(int i = 2;i <= n;i++)
        {
            f[0] = (dp[1] + dp[2] + dp[4]) % MOD;
            f[1] = (dp[0] + dp[2]) % MOD;
            f[2] = (dp[1] + dp[3]) % MOD;
            f[3] = (dp[2]) % MOD;
            f[4] = (dp[2] + dp[3]) % MOD;
            
            for(int j = 0; j < 5;j++) dp[j] = f[j];
        }
        
        long ans = 0;
        for(int i = 0;i < 5;i++)
            ans = (ans + dp[i]) % MOD;
        
        return (int)ans;
        
    }
}

你可能感兴趣的:(LeetCode刷题记录及题解,#,LeetCode比赛,LeetCode,第157周赛,Java,动态规划DP,DFS)