目录
leetcode第186场周赛,时间2020/04/26 10:30-12:00
第一题、分割字符串的最大得分
难度:easy,链接:https://leetcode-cn.com/problems/maximum-score-after-splitting-a-string/
给你一个由若干 0 和 1 组成的字符串 s ,请你计算并返回将该字符串分割成两个 非空 子字符串(即 左 子字符串和 右 子字符串)所能获得的最大得分。
「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1 的数量。
给你一个由若干 0 和 1 组成的字符串 s ,请你计算并返回将该字符串分割成两个 非空 子字符串(即 左 子字符串和 右 子字符串)所能获得的最大得分。
「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1 的数量。
思路:暴力即可,用一个变量存储左边0的数量,用一个变量存储右边1的数量,然后遍历。
class Solution {
public int maxScore(String s) {
int num1 = 0;//右边1的数量,最开始左边为空,右边是整个字符串,所以计算出整个字符串1的数量
for (int i = 0; i < s.length(); i++){
if (s.charAt(i) == '1') num1++;
}
int num0 = 0;//左边0的数量
int res = 0;//结果
for (int i = 0; i < s.length() - 1; i++){
if (s.charAt(i) == '0'){
num0++;
}
else {
num1--;
}
res = Math.max(res,num0 + num1);
}
return res;
}
}
第二题、可获得的最大点数
难度:medium,链接:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。
你的点数就是你拿到手中的所有卡牌的点数之和。
给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。
思路:题目即等价为从数组[cardPoints[len - k], cardPoints[len - k],cardPoints[len - k],...cardPoints[len - 1],cardPoints[0],cardPoints[1],...cardPoints[k - 1]]中找出大小为k的子数组中和最大的,用滑动窗口滑动一下即可。代码如下:
class Solution {
public int maxScore(int[] cardPoints, int k) {
int len = cardPoints.length;
int res = 0;
int sum = 0;
int start = len - k;
int end = len - 1;
for (int i = start; i <= end; i++){
sum += cardPoints[i];
}
res = sum;
for (int i = 1; i <= k; i++){
end = (end + 1) % len;
sum += cardPoints[end] - cardPoints[start];
res = Math.max(res,sum);
start = (start + 1) % len;
}
return res;
}
}
第三题、对角线遍历
难度:medium,链接:https://leetcode-cn.com/problems/diagonal-traverse-ii/
给你一个列表
nums
,里面每一个元素都是一个整数列表。请你依照下面各图的规则,按顺序返回nums
中对角线上的整数。
输入:nums = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,4,2,7,5,3,8,6,9]
思路:题目让输出对角线上的整数,而同一对角线的不同数的共同点是row+col相同,因为又要排序,所以结果数组输出应该为(row+col等于0的数,row+col等于1的数,row+col等于2的数。。。)。所以直接遍历矩阵,根据矩阵的行列放入一个二维数组中即可,然后最后将二维数组取出来。然后再看按这种思路会得到什么结果
[[1],[2,4],[3,5,7],[6,8],[9]],每个行加列相同的集合与结果输出的顺序相反,这是因为遍历的时候因为先遍历到行小的元素,所以行小的元素放在前面,但是结果却是行小的元素放在最后,所以只要将每个行加列相等的集合逆序一下即可。
代码如下:
class Solution {
public int[] findDiagonalOrder(List<List<Integer>> nums) {
int count = 0;//计算有多少数,以便最后res数组申请空间
ArrayList<ArrayList<Integer>> arr = new ArrayList();
for (int i = 0; i < nums.size(); i++)
{
for (int j = 0 ; j < nums.get(i).size(); j++)
{
if ((i + j + 1) > arr.size()){
arr.add(new ArrayList());
}
arr.get(i + j).add(nums.get(i).get(j));
}
count += nums.get(i).size();
}
int[] res = new int[count];
int index = 0;
for (int i = 0; i < arr.size(); i++)
{
for (int j = arr.get(i).size() - 1 ; j >= 0; j--)
{
res[index++] = arr.get(i).get(j);
}
}
return res;
}
}
第四题、带限制的子序列和
难度:hard,链接:https://leetcode-cn.com/problems/constrained-subset-sum/
给你一个整数数组 nums 和一个整数 k ,请你返回 非空 子序列元素和的最大值,子序列需要满足:子序列中每两个 相邻 的整数 nums[i] 和 nums[j] ,它们在原数组中的下标 i 和 j 满足 i < j 且 j - i <= k 。
数组的子序列定义为:将数组中的若干个数字删除(可以删除 0 个数字),剩下的数字按照原本的顺序排布。
给你一个整数数组 nums 和一个整数 k ,请你返回 非空 子序列元素和的最大值,子序列需要满足:子序列中每两个 相邻 的整数 nums[i] 和 nums[j] ,它们在原数组中的下标 i 和 j 满足 i < j 且 j - i <= k 。
数组的子序列定义为:将数组中的若干个数字删除(可以删除 0 个数字),剩下的数字按照原本的顺序排布。
我的思路:看到最大子序列和就很容易想到用动态规划来解决。动态规划递归式也很容易想出来。
dp[i] 为以nums[i]为结尾的子序列的最大元素和,又因为子序列中的上一个元素必然在{nums[i - 1],nums[i - 2],...,nums[i - k]}中,所以
dp[i] = max(dp[i - 1] + nums[i],dp[i - 2] + nums[i],dp[i - 3] + nums[i],...,dp[i - k] + nums[i])
所以可以写出对应代码:
class Solution {
public int constrainedSubsetSum(int[] nums, int k) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
for (int i = 1; i < nums.length; i++)
{
dp[i] = nums[i];
for (int j = Math.max(0,i - k); j < i; j++){
dp[i] = Math.max(dp[i],dp[j] + nums[i]);
}
res = Math.max(res,dp[i]);
}
return res;
}
}
分析时间复杂度,因为k <= 数组大小,所以最坏情况下k可能会等于数组大小,假设数组大小为N,最坏情况下时间复杂度会达到O(n^2),run了之后果然超时了,所以需要优化。
回到原来的动态规划递归式:
dp[i] = max{dp[i - 1] + nums[i],dp[i - 2] + nums[i],dp[i - 3] + nums[i],...,dp[i - k] + nums[i]}
即dp[i] = max{dp[i-1] , dp[i - 2] , dp[i - 3] , ... , dp[i - k]} + nums[i]
尝试化简,写出dp[i - 1]的递推式,看看两者是否有关系,可否从dp[i - 1]推到dp[i]
dp[ i - 1] = max{dp[i - 2] , dp[i - 3] , ... , dp[i - k] ,dp[i - k - 1]} + nums[i - 1]
尝试相减,不能直接相减,找两者的共同点,发现两者都有一段max {dp[i - 2],dp[i - 3] , dp[ i - k]},再将两个dp递推式转化一下:
dp[i] - nums[i] = max{dp[i-1] ,** max {dp[i - 2] , dp[i - 3] , ... , dp[i - k]}**}
dp[i - 1] - nums[i - 1] = max{**max{dp[i - 2] , dp[i - 3] , ... , dp[i - k]}** ,dp[i - k - 1]}
找到了共同点之后发现不能直接从dp[i - 1]推到dp[i],但观察一下,dp[i - 1]就是找dp[i- k - 1]到dp[ i - 2]的最大值,dp[i] 就是找dp[i - k]到dp[i - 1]的最大值,那这不就是大小为k的滑动窗口吗,用单调队列即可以O(n)的时间复杂度求出所有窗口的最大值。
lc里也有这道题:https://leetcode-cn.com/problems/sliding-window-maximum/
代码如下:
```
class Solution {
public int constrainedSubsetSum(int[] nums, int k) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
Deque<Integer> deque = new ArrayDeque();//单调递减队列,队首是窗口的最大值,
deque.addFirst(dp[0]);
for (int i = 1; i < k; i++) //当窗口大小没有达到k的时候
{
dp[i] = Math.max(deque.getFirst() + nums[i] , nums[i]);
res = Math.max(res,dp[i]);
while (!deque.isEmpty() && deque.getLast() < dp[i])
{
deque.removeLast();
}
deque.addLast(dp[i]);
}
for (int i = k; i < nums.length; i++)//当窗口大小达到k后
{
dp[i] = Math.max(deque.getFirst() + nums[i] , nums[i]);
res = Math.max(res,dp[i]);
while (!deque.isEmpty() && deque.getLast() < dp[i])
{
deque.removeLast();
}
deque.addLast(dp[i]);
if (dp[i - k] == deque.getFirst()) deque.removeFirst();
}
return res;
}
}
```