赞
踩
目录
题目
给定一个由字符串组成的数组strs,必须把所有的字符串拼接起来,返回所有拼接结果中,字典序最小的结果。
示例 1:
思路
贪心策略:
代码
class Solution { public: std::string lowestString(std::vector<std::string> strs){ if(strs.empty()){ return ""; } std::sort(strs.begin(), strs.end(), [](std::string &l , std::string & r){ return (l + r) < (r + l); }); std::string ans ; for (auto & str : strs) { ans += str; } return ans; } };
类似题目
题目
小明是一个销售员,客人在他的地方买了东西,付给了小明一定面值的钱之后,小明需要把多余的钱退给客人。客人付给了小明n,小明的东西的售价为m,小明能退回给客人的面额只能为[100,50,20,10,5,2,1]的组合。现在小明想要使找出去纸币张数最小,请返回这个最小值。
示例 1:
思路
策略:
代码
class Solution { private: std::vector<int> comb {100, 50, 20, 10, 5, 2, 1}; public: int coinProblem(int n, int m) { int mon = n - m; // 还需要找多少钱 int res = 0; // 一共找出去的钱的张数 for (int i : comb){ res += mon / i; // 当前面额最大可以找多少钱 mon = mon % i; // 还剩下多少钱需要找 } return res; } };
类似题目
题目
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
示例 2:
示例 3:
提示:
解析
分析:
思路:
代码
class Solution { public: int maxProfit(vector<int>& prices) { int res = 0; for (int i = 1; i < prices.size(); ++i) { if(prices[i] - prices[i - 1] > 0){ res += prices[i] - prices[i - 1]; } } return res; } };
题目
给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
示例 1:
示例 2:
示例 3:
思路
典型的 N:M类型问题,类似的问题还有,N个进程每一类型用时不同,M个相同CPU,求最少时间,本质上也是求最小资源使用。类似的可以参考后面题目<项目调度器>
分析:
解题步骤:
代码
class Solution { public: int numRescueBoats(vector<int>& people, int limit) { int N = people.size(); std::sort(people.begin(), people.end()); if(people[N - 1] > limit){ return -1; } int ans = 0, l = 0, r = N - 1, sum = 0; while (l <= r){ // 当 people [l] > limit/2 时,ans+= (r-l == 0 ? 1:r-l) if (people [l] > limit/2.) { ans+= (r-l == 0 ? 1:r-l); break; } // 当人数总数为奇数时处理边界条件 sum = l == r ? people[l] : people[l] + people[r]; if(sum > limit){ r--; }else{ l++; r--; } ans++; } return ans; } };
题目
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
示例 1:
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
示例 2:
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
诸如此类
示例 3:
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A
提示:
思路
分析:
可以分为两种情况:
步骤:
PS: 上述过程简化的公式可以为:
代码
class Solution{ static bool cmp(int &a, int &b){ return a > b; } public: int leastInterval(std::vector<char> &tasks, int n){ if(!n){ return tasks.size(); } std::vector<int> nums(26, 0); for(char tast : tasks){ nums[tast - 'A'] += 1; } std::sort(nums.begin(), nums.end(), cmp); int total = (n - 1) * (nums[0] - 1) + 1; for (int i = 1; i < nums.max_size(); ++i) { if(nums[i] == nums[0]){ total++; } } return total > tasks.size() ? total : tasks.size(); } };
题目
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
思路
PS: 这个题目改个名字就很简单了:判断一个数组波峰波谷的和
分析:
代码
class Solution { public: int wiggleMaxLength(vector<int>& nums) { if (nums.size() <= 1) return nums.size(); int curDiff = 0; // 当前一对差值 int preDiff = 0; // 前一对差值 int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值 for (int i = 0; i < nums.size() - 1; i++) { curDiff = nums[i + 1] - nums[i]; // 出现峰值 if ((curDiff > 0 && preDiff <= 0) || (preDiff >= 0 && curDiff < 0)) { result++; preDiff = curDiff; } } return result; } };
class Solution { public int wiggleMaxLength(int[] nums) { int n = nums.length; if (n < 2) { return n; } // 因为有边界节点(起点、终点)因此最少一个长度 int up = 1; int down = 1; for (int i = 1; i < n; i++) { if (nums[i] > nums[i - 1]) { up = down + 1; } if (nums[i] < nums[i - 1]) { down = up + 1; } } return Math.max(up, down); } }
题目
你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。
示例 1:
解释:
示例 2:
提示:
思路
题意:
分析:
大致流程:
举个例子:
PS: 这道题的算是“K个有序数组”的升级题目,也是一个比较隐晦的缝合题目,我们可以按照方法论的角度去想这个题目。
出现K个数组这样的题目的时候,首先应该想到“多指针”,经典题目是合并多个有序数组为一个有序数组。
而有序数组的题目中也有一个方法:滑动窗口。
所以这题就转化成了怎么在一个带有标记的数组中,找一个最小区间,找齐所有类型的标记。
下面是解题与优化步骤:
代码
class Solution { // 最小堆 struct NumGroup{ int num; //数值 int grp; //组号 int idx; NumGroup(int num, int grp, int idx){ this->num = num; this->grp = grp; this->idx = idx; } // 重载 operator> bool operator> (const NumGroup &other) const { return this->num > other.num; } }; public: vector<int> smallestRange(vector<vector<int>>& nums) { //因为每次都要找最小元素,所以维护一个最小堆比较合适,最小堆调整的时间复杂度只有logN std::priority_queue<NumGroup, std::vector<NumGroup>, std::greater<NumGroup>> minHeap; // 最小区间的边界范围 int winMin = 0, winMax = INT16_MAX; // 当前列表选择的数字里的最小值和最大值 int currMin = INT16_MAX, currMax = INT16_MIN; for (int i = 0; i < nums.size(); ++i) { currMax = std::max(currMax, nums[i][0]); // 因为最小堆中只能找出当前可选元素中的最小值,所以我们需要维护一个currMax找出其最大值 minHeap.emplace(NumGroup(nums[i][0], i, 0)); } while (true){ // 弹出来的就是目前的最小值 int grp = minHeap.top().grp; int idx = minHeap.top().idx; int num = minHeap.top().num; minHeap.pop(); // 当前形成的区间 currMin = num; currMax = currMax; //如果长度变小,那么更新答案区间 if(currMax - currMin < winMax - winMin){ winMax = currMax; winMin = currMin; } // 判断是否结束:如果起点(最小值无下一个元素)已经不可能在修改,那么跳出循环 if(idx == nums[grp].size() - 1){ break; } // 移动到当前最小元素的下一个元素 idx++; num = nums[grp][idx]; minHeap.emplace(NumGroup(num, grp, idx)); currMax = std::max(currMax, num); // 此时下一个元素它可能会形成新的终点 } return {winMin, winMax}; } };
题目
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
示例 1:
示例 2:
提示:
思路
题意:
分析:
举例:
代码
无优化的写法:
class Solution { public: int jump(vector<int>& nums) { if(nums.size() <= 1){ return 0; } int target = nums.size() - 1; int jump = 0; // 目前已经跳了几步 int currPos = 0; // 当前位置 while (currPos != target){ // 当前不位于终点 // 至少要跳一次 jump++; // 当前位置最远能跳到那里 int canPos = currPos + nums[currPos]; // 如果能到终点就跳终点 if(canPos >= target){ return jump; } // 从可跳范围中选择能跳最远的位置 int maxDistance = 0, maxPos = -1; for (int i = currPos; i <= canPos; ++i) { if(nums[i] > maxDistance){ maxDistance = nums[i]; maxPos = i; } } currPos = maxPos; } return jump; } };
上面的写法会超时,因为它多次重复判断了[可跳范围]。可以用动态规划的思想去做优化,有点像非递归的斐波拉切数列:
优化写法如下:
class Solution { public: int jump(vector<int>& nums) { if(nums.size() <= 1){ return 0; } int jump = 0; // 记录走的最大步数 int curDistance = 0; //以当前跳跃步数,能到的最远位置,比如: jump=1跳一次时,最远能到下标currJumpMax=2 int nextDistance = 0; //当前位置能到的最远位置 int target = (int)nums.size() - 1; for (int i = 0; i < nums.size(); ++i) { nextDistance = max(nums[i] + i, nextDistance); //找能跳的最远的 if (i == curDistance){//已经走到了当前跳跃步数的边界 if(curDistance != target){ //还没有到达终点 jump++;//我们不得不再跳一次 curDistance = nextDistance; //并记录当前跳跃步数能到的最远位置 if (nextDistance >= target) break; // 下一步的覆盖范围已经可以达到终点,结束循环 }else{ break; // 已经到达终点,不用做ans++操作了,直接结束 } } } return jump; } };
题目
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
示例 2:
提示:
思路
分析:
流程:
代码
class Solution { public: int candy(vector<int>& ratings) { int N = ratings.size(); if(N == 0){ return 0; } std::vector<int> left(N); left[0] = 1; for (int i = 1; i < N; ++i) { if(ratings[i] > ratings[i - 1]){ left[i] = left[i - 1] + 1; }else{ left[i] = 1; } } std::vector<int> right(N); right[N - 1] = 1; for (int i = N - 2; i >= 0; --i) { if(ratings[i] > ratings[i + 1]){ right[i] = right[i + 1] + 1; }else{ right[i] = 1; } } int ans = 0; for (int i = 0; i < N; ++i) { ans += std::max(left[i], right[i]); } return ans; } };
题目
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
两个字符串完全匹配才算匹配成功。
说明:
示例 1:
示例 2:
示例 3:
示例 4:
示例 5:
思路
先把这个题目分五个情况讲:
然后分情况讨论。
情况1:直接全词匹配,双指针 indexs、indexp,一个指针一个指针过,不相等 返回false
情况2:
双指针,遇到?indexs、indexp跳一个字符,后面的如果不相等,返回false
如果?后indexs没有字符也返回false,因为?不匹配空字符
情况3:
分析:
情况1+情况2,都是indexs++ && indexp++
所以重点就是情况3: 处理*的情况
情况3的解析方法:
直接想到的就是暴力回溯,一个字符一个字符去匹配,匹配不到位的话,从*位置的下一个继续开始做。
需要做的是记录*出现的位置,若后续不匹配,就直接回溯到*位置(indexSS、indexPP),从上次遍历的下一个位置(indexSS++、indexPP++)继续做,直到S结束或者遇到下一个*。
本质上就是贪心+回溯+动态规划的思想,获取最小规模的可行解;
贪心贪在哪里呢?
PS:情况3也可以做一个剪枝优化,比如说匹配模式串p *后到结尾或者到下一个*之间的字符串长度为N,当S剩余大小 < N,且未匹配到正确字符时,可直接返回false。
代码
class Solution { public: bool isMatch(string s, string p) { int indexp= 0; int indexSS = -1, indexPS = -1; // 用来回溯的锚点 for(int indexs =0;indexs<s.size();indexs++) { if (indexp< p.size() && (s[indexs] == p[indexp] || p[indexp] == '?')) { // case 1 && 2 indexp++; //此时p和s均为一对一匹配 } else if(indexp< p.size() && p[indexp] == '*') { // case 3: indexSS = indexs; indexPS = ++indexp; indexs--; //这里--的原因是下一步就会++,此时indexs还是原来的字符 } else if (indexSS != -1) { //发现字符串不匹配而且没有星号出现,但是indexSS不为-1说明可能是*匹配的字符数量不对 这时回溯 indexs = indexSS++; indexp= indexPS; } else { return false; } } // 边界处理,若S结束,P中只有*为true,其余为false if (indexp != p.size()) { while(indexp < p.size()) { if (p[indexp++] != '*') return false; } } return true; } };
题目
给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
说明: 请尽可能地优化你算法的时间和空间复杂度。
示例 1:
输入:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
输出:
[9, 8, 6, 5, 3]
示例 2:
输入:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
输出:
[6, 7, 6, 0, 4]
示例 3:
输入:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
输出:
[9, 8, 9]
思路
先说解法:多路归并,然后利用字典序做compare & 单调栈,提取字典序最大子串,然后截取K个元素
这个题目是一个典型的贪心算法题目,但是结合了其他的技术:单调栈、字典序,归并;
字典序比较好理解,这里简单的讲一下单调栈的含义。
单调栈
就是单调递增或单调减的栈,使用方法比较固定:就是要入栈的值,利用一个比较方法循环比较栈顶元素的值来,决定是不是让栈顶元素出栈,若满足出栈要求就弹出栈顶元素,继续和新的栈顶元素比较,直到不满足出栈要求,将该元素放入栈内,以维持整个栈的单调性。
可以参考:leetcode 038 每日温度
二路归并
归并,比较常见的是2路归并,但是也可以是N路归并,如果没有特殊条件或者要求的话,就是N个有序数组的指针元素一起比,把符合要求的数插入新数组,直到比较完全部的数组;
本题目的暴力解法比较容易想到,就是直接上全排列,按照顺序把所有的K个元素组合可能做一次排列,然后找到最大的。
可以使用全排列的,大多数都可以使用贪心去做优化,无非就是看贪心策略怎么找。
先降规模,如果只有一个数组,要取出i个数,组成字典序最大且有序,可以使用单调栈来完成。
如果是两个数组找最大字典序,那就是一个数组取i个用单调栈求出最大字典序,一个数组取出 K- i个用单调栈求出最大字典序,全排列找出所有组合然后以归并后的字典序作为compare ,求出最大字典序。
代码
class Solution { public: vector<int> maxNumber(const vector<int>& nums1, const vector<int>& nums2, int k) { int size_num1=nums1.size(); int size_num2=nums2.size(); int start=max(0,k-size_num2); int end=min(size_num1,k); vector<int> res(k,0); for(int i=start;i<=end;i++){ // 求第一个数组的i个长度的最大字典序 vector<int> one(GetMonStack(nums1,i)); // 求第二个数组的k- i个长度的最大字典序 vector<int> two(GetMonStack(nums2,k-i)); // 组合并比较,然后把当前最大结果缓存 vector<int> temp(MergeVector(one,two)); if(compare(temp,0,res,0)>0) res.swap(temp); } //输出最大的字典序 return res; } //求单调栈 vector<int> GetMonStack(vector<int> &nums,int length){ stack<int> s; int n=nums.size(); int drop_num=n-length; for(int i=0;i<n;++i){ while(!s.empty() && s.top()<nums[i] && drop_num>0){ s.pop(); --drop_num; } if(s.size()<length)s.push(nums[i]); else --drop_num; } return [](stack<int> ss){vector<int> res(ss.size(),0);int i=ss.size()-1;while(!ss.empty()){res[i--]=ss.top();ss.pop();}return res;}(s); } //合并两个数组 vector<int> MergeVector(vector<int> &one,vector<int> &two){ int size_one=one.size(),size_two=two.size(); if(!size_one) return two; if(!size_two) return one; int a=0,b=0; int n=size_one+size_two,i=0; vector<int> res(size_one+size_two,0); while(i<n){ if(compare(one,a,two,b)>0) res[i++]=one[a++]; else res[i++]=two[b++]; } return res; } //比较函数 int compare(vector<int>& one, int index1, vector<int>& two, int index2) { int x = one.size(), y = two.size(); while (index1 < x && index2 < y) { int tag = one[index1++] - two[index2++]; if (tag != 0) return tag; } return (x - index1) - (y - index2); } };
类似题
leetcode 316 - 去除重复字母
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。