目錄
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;
}
}
```