程序员进阶之算法练习(九十二)leetcode

题目1 最后一块石头的重量

题目链接
题目大意:
有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000

题目解析:
模拟题目操作,用优先队列,每次取出头部两个元素进行操作,如果元素不相同则把石头差放回队列。
代码比较简单:

class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> q;
        for (int i = 0; i < stones.size(); ++i) q.push(stones[i]);
        while (q.size() >= 2) {
            int first = q.top();
            q.pop();
            int second = q.top();
            q.pop();
            if (first - second) q.push(first - second);
        }
        return q.empty() ? 0 : q.top();
    }
}ac;

题目2 两地调度

题目链接
题目大意:
公司计划面试 2n 人。给你一个数组 costs ,其中 costs[i] = [aCosti, bCosti] 。第 i 人飞往 a 市的费用为 aCosti ,飞往 b 市的费用为 bCosti 。
返回将每个人都飞到 a 、b 中某座城市的最低费用,要求每个城市都有 n 人抵达。

示例 1:
输入:costs = [[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 a 市,费用为 10。
第二个人去 a 市,费用为 30。
第三个人去 b 市,费用为 50。
第四个人去 b 市,费用为 20。

最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。

提示:
2 * n == costs.length
2 <= costs.length <= 100
costs.length 为偶数
1 <= aCosti, bCosti <= 1000

题目解析:
如果不考虑复杂度,可以直接使用搜索的方式,每个人有2个选择,总共会有2^2n种选择,这个复杂度明显超过题目限制;
注意到每个人有2个选择,那么用dp[i][j]来表示前i个人,有j个选择a城市的最小费用,对于第i个人:
如果第i个人选择a城市,那么有dp[i][j]=dp[i-1][j-1] + aCost[i];
如果第i个人选择b城市,那么有dp[i][j]=dp[i-1][j] + bCost[i];

总的复杂度是O(N^2);

class Solution {
    int dp[110][110];
public:
    int twoCitySchedCost(vector<vector<int>>& costs) {
        int n = costs.size();
        for (int i = 1; i <= n; ++i) {
            dp[i][0] = dp[i - 1][0] + costs[i-1][1]; // bCost[i]
            dp[i][i] = dp[i - 1][i - 1] + costs[i-1][0]; // aCost[i]
            for (int j = 1; j < i; ++j) {
                dp[i][j] = min(dp[i - 1][j - 1] + costs[i - 1][0], dp[i - 1][j] + costs[i - 1][1]);
            }
        }
        return dp[n][n/2];
    }
}ac;

题目3 两个非重叠子数组的最大和

题目链接
题目大意:
给出非负整数数组 A ,返回两个非重叠(连续)子数组中元素的最大和,子数组的长度分别为 L 和 M。(这里需要澄清的是,长为 L 的子数组可以出现在长为 M 的子数组之前或之后。)

从形式上看,返回最大的 V,而 V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) 并满足下列条件之一:

0 <= i < i + L - 1 < j < j + M - 1 < A.length, 或
0 <= j < j + M - 1 < i < i + L - 1 < A.length.

示例 1:
输入:A = [0,6,5,2,2,5,1,9,4], L = 1, M = 2
输出:20
解释:子数组的一种选择中,[9] 长度为 1,[6,5] 长度为 2。
示例 2:
输入:A = [3,8,1,3,2,1,8,9,0], L = 3, M = 2
输出:29
解释:子数组的一种选择中,[3,8,1] 长度为 3,[8,9] 长度为 2。
示例 3:
输入:A = [2,1,5,6,0,9,5,0,3,8], L = 4, M = 3
输出:31
解释:子数组的一种选择中,[5,6,0,9] 长度为 4,[0,3,8] 长度为 3。

提示:
L >= 1
M >= 1
L + M <= A.length <= 1000
0 <= A[i] <= 1000

题目解析:
题目要求是找到两个子数组并且要求和最大,我们可以先简化题目要求;
假如只考虑找到一个最大子数组的情况,此时可以从左到右遍历数组,就可以得到每个位置结尾的最大子数组和left[i];在这个过程中,可以持续计算得到该位置往左所有left[i]的最大值maxLeft[i];
这样我们就可以O(N)的复杂度,在数组中找到某个长度的最大值。
同理,可以从右到左遍历,得到right[i]和maxRight[i]。

现在要寻找长度L和M的子数组,那么问题可以拆分为:
1、假设有位置k,那么[1, k]中会产生L数组,[k+1, n]会产生M数组;此时可以枚举k的位置;
2、LR的位置是反过来的,按照1的做法把L和R交换一下;
时间和空间复杂度都是O(N);

class Solution {
    int search(vector<int>& nums, int firstLen, int secondLen) {
        int n = nums.size(), sum = 0;
        vector<int> left(n), maxLeft(n);
        for (int i = 0; i < n; ++i) {
            sum += nums[i];
            if (i >= firstLen) {
                sum -= nums[i - firstLen];
            }
            left[i] = sum;
            if (i > 0) maxLeft[i] = maxLeft[i - 1];
            maxLeft[i] = max(maxLeft[i], left[i]);
        }
        vector<int> right(n), maxRight(n);
        sum = 0;
        for (int i = n - 1; i >= 0; --i) {
            sum += nums[i];
            if (i + secondLen <= n - 1) {
                sum -= nums[i + secondLen];
            }
            right[i] = sum;
            if (i < n - 1) maxRight[i] = maxRight[i + 1];
            maxRight[i] = max(maxRight[i], right[i]);
        }
        
        int ret = 0;
        for (int i = firstLen - 1; i + secondLen < n; ++i) {
            ret = max(ret, maxLeft[i] + maxRight[i + 1]);
        }
        return ret;
    }
public:
    int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
        return max(search(nums, firstLen, secondLen), search(nums, secondLen, firstLen));
    }
}leetcode;

题目4 每个元音包含偶数次的最长子字符串

题目链接
题目大意:
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。

示例 1:
输入:s = "eleetminicoworoep"
输出:13
解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = "leetcodeisgreat"
输出:5
解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。
示例 3:
输入:s = "bcbcbc"
输出:6
解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。

提示:
1 <= s.length <= 5 x 10^5
s 只包含小写英文字母。

题目解析:

从简单的开始思考,假如要求只有一个字母a出现偶数次;
那么如果数组中字母a出现偶数次,则直接满足;如果a出现奇数次,那么去掉最左边的a及左边的部分,或者去掉最右边a及右边的部分;(复杂度O(N) )
由此我们知道,肯定是去掉最初出现的字母a,或者最后出现的字母a。

现在要求变成字母a、o出现偶数次,能否延续上面的思路:去掉最左边或者最右边的某一些部分,使得剩下部分满足要求?
理论上是可行的,左边去掉部分,可能是奇数或者偶数个a,有可能是奇数或者偶数个o,右边同理;剩下的部分要求a和o都是偶数。

对于左边来说,去掉的部分有4种可能:偶数a偶数o,偶数a奇数o,奇数a偶数o,奇数a奇数o;
为了方便描述我们用0表示偶数,1表示奇数,那么上面的状态可以表示为00、01、10、11,刚好可以用数字0、1、2、3来表示这4个状态。
假如原始字符串,最开始的状态是state;最终剩下的字符串,状态应该是00,假如左边去掉字符串的状态是leftState,右边去掉字符串的状态是rightState,那么就有 leftState ^ rightState ^ state = 0;(这里采用异或操作符,可以用实际操作例子感受下)

当字母扩展为a、e、i、o、u之后,同样可以用上面的方式。

class Solution {
public:
    int findTheLongestSubstring(string s) {
        char aoe[] = "aeiou";
        int state = 0;
        int left[33] = {0}, right[33] = {0};
        for (int i = 0; i < s.length(); ++i) {
            char c = s[i];
            int k = -1;
            for (int j = 0; j < 5; ++j) {
                if (aoe[j] == c) {
                    k = j;
                    break;
                }
            }
            if (k >= 0) {
                state = state ^ (1 << k);
                if (state && !left[state]) {
                    left[state] = i + 1;
//                    cout << "left " << state << " " << i + 1 << endl;
                }
            }
        }
        
        state = 0;
        right[state] = s.length();
        for (int i = s.length() - 1; i >= 0; --i) {
            char c = s[i];
            int k = -1;
            for (int j = 0; j < 5; ++j) {
                if (aoe[j] == c) {
                    k = j;
                    break;
                }
            }
            if (k >= 0) {
                state = state ^ (1 << k);
                if (state && !right[state]) {
                    right[state] = i;
//                    cout << "right " << state << " " << i + 1 << endl;
                }
            }
        }
//        cout << "state " << state << endl;
        
        int ans = 0;
        for (int j = 0; j < 32; ++j) {
            int leftState = j;
            int rightState = j ^ state;
//            cout << leftState << " " << rightState << " " << s.length() - (s.length() - right[rightState]) - left[leftState] << endl;
            ans = max(ans, right[rightState] - left[leftState]);
        }
        
        return ans;
    }
}leetcode; 

题目5 删除子数组的最大得分

题目链接
题目大意:
给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的 得分 就是子数组各元素之 和 。

返回 只删除一个 子数组可获得的 最大得分 。

如果数组 b 是数组 a 的一个连续子序列,即如果它等于 a[l],a[l+1],...,a[r] ,那么它就是 a 的一个子数组。

示例 1:
输入:nums = [4,2,4,5,6]
输出:17
解释:最优子数组是 [2,4,5,6]
示例 2:
输入:nums = [5,2,1,2,5,2,1,2,5]
输出:8
解释:最优子数组是 [5,2,1] 或 [1,2,5]

提示:
1 <= nums.length <= 105
1 <= nums[i] <= 104

题目解析:
从数组中找到一个连续的区间,区间不能包括相同的数字,要求区间的数字和尽可能大。
从左到右遍历数组,维护一个区间[left, right],区间没有相同元素,我们用map<int, int> 来记录数组中出现的数字,sum记录数组的和;
假设遍历到数字a[i],如果map中没有数字a[i],则直接添加到map中,右区间右移right=right+1,sum=sum+a[i];
假设遍历到数字a[i],如果map中存在数字a[i],则左区间右移sum=sum-a[left],left=left+1,直到发现数字a[i],这样得到包括数字a[i]的区间[left, right]和区间和sum;

遍历过程中的最大和所在区间,就是答案。复杂度O(N)。

class Solution {
public:
    int maximumUniqueSubarray(vector<int>& nums) {
        int sum = 0, left = 0, right = 0;
        int ans = 0;
        unordered_map<int, int> mp;
        for (int i = 0; i < nums.size(); ++i) {
            if (mp[nums[i]]) {
                while (mp[nums[i]]) {
                    sum -= nums[left];
                    mp[nums[left]]--;
                    ++left;
                }
            }
            right += 1;
            sum += nums[i];
            ++mp[nums[i]];
            ans = max(ans, sum);
        }
        return ans;
    }
}leetcode;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,641评论 6 525
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,754评论 3 408
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 173,075评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,369评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,386评论 6 402
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,800评论 1 317
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,122评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,130评论 0 281
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,689评论 1 327
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,693评论 3 348
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,804评论 1 356
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,399评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,128评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,528评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,705评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,409评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,878评论 2 368

推荐阅读更多精彩内容