惨不忍睹,这周周赛。周赛前一晚,和朋友出去吃烧烤喝酒,到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
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 <= chips.length <= 100
1 <= chips[i] <= 10^9
这道题主要要理解好题意。
如果移动两个单位,代价为0,说明同为偶数或者同为奇数之间的相互变化是不需要代价的。
移动一个单位,代价为1,我们只要将对应需要移动的变到相邻位置,剩下再移动就不需要代价了,因此主要就是考虑相邻位置之间变动的代价。
由于同为偶数或者同为奇数之间移动不需要代价,那么为了全部变成相同的数,要么把奇数变成偶数,要么把偶数变成奇数。
但为了代价最小,我们需要得到奇数和偶数的个数,选择较小个数的那个进行变化,这样子的代价就是最小的。
因此这道题,就是求偶数和奇数个数的最小值。
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);
}
}
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 <= arr.length <= 10^5
-10^4 <= arr[i], difference <= 10^4
根据题目提示的要求,数组长度是10^5,因此算法的时间复杂度应该是O(n)或者O(nlogn)才会不超时。
O(n)应该就是要求扫一遍数组就要能够得出答案。
比如示例1,[1,2,3,4]
所以主要是对于当前数,找 当前数 - difference,如果存在,就把那个数对应的最大长度 + 1,并且存起来;如果不存在,说明这个数是新的起点,也就是长度记为1。
那么对于(1,1),看成(key,value)这个记录方法,可以考虑利用HashMap,也就是 key - value的方法,查找时间为O(1),不会超时。
当然也可以利用一个一维数组,利用数组的下标对应 key,而该位置存放的值为value。如果用数组的话,因为数组下标一定是大于等于0的,而数和差可能有正有负,因此判断最小的应该是 -2*10^4,所以所有数都加上至少
2*10^4 保证非负。这样子数组至少要开 4
*10^4 那么大。
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;
}
}
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的方法比用一维数组的方法的时间复杂度更大,而空间复杂度相差不大。
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 <= grid.length, grid[i].length <= 15
0 <= grid[i][j] <= 100
- 最多 25 个单元格中有黄金。
类似迷宫题,而且迷宫大小最多 15*15,因此想到使用 DFS + 回溯
遍历所有可能的起点,从某个起点开始,DFS走完所有可能的地方,然后每次都保留得到的黄金最大值。
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;
}
}
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 <= n <= 2 * 10^4
动态规划的一道题,利用 dp[i][c],表示在长度为 i 的时候,以 字母 c 结尾的种类。
根据题目的要求:
为了表示方便,将 a,e,i,o,u 映射为0,1,2,3,4。
所以根据上面的要求,就可以列出状态转移方程出来。
最后的答案,就是把所有 dp[n][0-4] 进行累和
而上面的方法是利用二维数组的,内存开销大,所以进行改进,利用滚动数组,设两个一维数组
dp[ ] 和 f[ ],其中dp还是表示在长度为 i 的时候,以 字母 c 结尾的种类。而进行一次新的计算时,先利用 f[ ] 记录
计算完当前长度的所有情况后,把 f[ ] 的值才对应赋值给 dp[ ]。不然中途就更新了 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;
}
}
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;
}
}