声明:若未特殊标出,则默认是leedcode原题。
3、2208. 将数组和减半的最少操作次数:
class Solution
{
public:
int halveArray(vector& nums)
{
priority_queue heap;
double sum = 0.0;
for(auto x : nums)
{
heap.push(x);
sum += x;
}
sum /= 2.0;
int count = 0;
while(sum > 0)
{
double t = heap.top() / 2.0;
heap.pop();
sum -= t;
count++;
heap.push(t);
}
return count;
}
};
4、179. 最大数:
class Solution
{
public:
string largestNumber(vector& nums)
{
//优化:将所有数按字典序排序
vector strs;
for(auto x : nums)
{
strs.push_back(to_string(x));
}
sort(strs.begin(), strs.end(), [](const string& s1, const string& s2)
{
return s1 + s2 > s2 + s1;
});
string ret;
for(auto& s : strs) ret += s;
if(ret[0] == '0') return "0";
else return ret;
}
};
补充知识:全序关系满足:完全性,反对称性,传递性。
5、376. 摆动序列:
class Solution
{
public:
int wiggleMaxLength(vector& nums)
{
int n = nums.size(), len = 0, left = 0;
if(n < 2) return n;
for(int i = 0; i < n - 1; i++)
{
int right = nums[i + 1] - nums[i];
if(right == 0) continue;
if(0 >= left * right) len++;
left = right;
}
return len + 1;
}
};
6、300. 最长递增子序列:
①回顾dp的做法:
状态表示:dp[i]表示:以i的位置的元素为结尾的所有子序列中,最长递增子序列的长度。
状态转移方程:dp[i]=max(dp[j]+1)(j
②贪心优化:O(N^2)
存什么:所有长度为x的递增子序列中,最后一个元素的最小值。
存哪里:所有大于等于nums[i]的最小值的位置。
③二分优化:O(NlogN)
细节:边界情况。
class Solution
{
public:
int lengthOfLIS(vector& nums)
{
int n = nums.size();
vector ret;
ret.push_back(nums[0]);
for(int i = 1; i < n; i++)
{
if(nums[i] > ret.back()) ret.push_back(nums[i]);
else
{
// 二分插入位置
int left = 0, right = ret.size() - 1;
while(left < right)
{
int mid = (left + right) >> 1;
if(ret[mid] < nums[i]) left = mid + 1;
else right = mid;
}
ret[left] = nums[i];
}
}
return ret.size();
}
};
7、334. 递增的三元子序列:
法1:同上题。
法2:
class Solution
{
public:
bool increasingTriplet(vector& nums)
{
int a = nums[0], b = INT_MAX;
for(int i = 1; i < nums.size(); i++)
{
if(nums[i] > b) return true;
else if(nums[i] > a) b = nums[i];
else a = nums[i];
}
return false;
}
};
8、674. 最长连续递增序列:
解法:贪心+双指针。
class Solution
{
public:
int findLengthOfLCIS(vector& nums)
{
int ret = 1, n = nums.size();
for(int i = 0; i < n;)
{
int j = i + 1;
while(j < n && nums[j] > nums[j - 1]) j++;
ret = max(ret, j - i);
i = j; // 直接在循环中更新下一个位置的起点
}
return ret;
}
};
9、121. 买卖股票的最佳时机:
class Solution
{
public:
int maxProfit(vector& prices)
{
int ret = 0;
for(int i = 0, prevmin = INT_MAX; i < prices.size(); i++)
{
ret = max(ret, prices[i] - prevmin);
prevmin = min(prevmin, prices[i]);
}
return ret;
}
};
10、122. 买卖股票的最佳时机 II:
class Solution
{
public:
int maxProfit(vector& p)
{
// 实现方式1:
int ret = 0, n = p.size();
for(int i = 0; i < n; i++)
{
int j = i;
while(j + 1 < n && p[j + 1] >= p[j]) j++;
ret += p[j] - p[i];
i = j;
}
return ret;
// // 实现方式2:拆分成一天一天
// int ret = 0;
// for(int i = 1; i < p.size(); i++)
// {
// if(p[i] > p[i - 1])
// ret += p[i] - p[i - 1];
// }
// return ret;
}
};
11、1005. K 次取反后最大化的数组和:
分情况讨论:设整个数组中负数的个数是m个:
①m>k:把前k小负数,转化为正数。
②m==k:把所有的负数全部转化成正数。
③m
class Solution
{
public:
int largestSumAfterKNegations(vector& nums, int k)
{
int m = 0, n = nums.size(), minElem = INT_MAX;
for(auto x : nums)
{
if(x < 0) m++;
minElem = min(minElem, abs(x)); // 求绝对值最小的那个数
}
int ret = 0;
if(m >= k)
{
sort(nums.begin(), nums.end());
for(int i = 0; i < k; i++) ret += -nums[i];
for(int i = k; i < n; i++) ret += nums[i];
}
else
{
// 把所有的负数变成正数
for(auto x : nums) ret += abs(x);
if((k - m) % 2) ret -= minElem * 2;
}
return ret;
}
};
12、2418. 按身高排序:
解法一:创建二元组:
①创建一个数组pair
②对新的数组排序;
③按照顺序把名字提取出来即可。
解法二:利用哈希表存下映射关系
①先用哈希表存下映射关系<身高,名字>;
②对身高数组排序;
③根据排序后的结果,往哈希表里找名字即可。
解法三:对下标排序:
①创建一个下标数组;
②仅需对下标数组排序;
③根据下标数组排序后的结果,找到原数组的信息。
class Solution
{
public:
vector sortPeople(vector& names, vector& heights)
{
// 1、创建一个数组
int n = names.size();
vector index(n);
for(int i = 0; i < n; i++) index[i] = i;
// 2、对下标进行排序
sort(index.begin(), index.end(), [&](int i, int j)
{
return heights[i] > heights[j];
});
// 3、提取结果
vector ret;
for(int i : index) ret.push_back(names[i]);
return ret;
}
};
13、870. 优势洗牌:
算法原理:田忌赛马:
①如果比不过,就去拖累对面最强的那一个;
②如果能比过,那就直接比。
class Solution
{
public:
vector advantageCount(vector& nums1, vector& nums2)
{
int n = nums1.size();
// 1、排序
sort(nums1.begin(), nums1.end());
vector index2(n);
for(int i = 0; i < n; i++) index2[i] = i;
sort(index2.begin(), index2.end(), [&](int i, int j)
{
return nums2[i] < nums2[j];
});
//2、田忌赛马
vector ret(n);
int left = 0, right = n - 1;
for(auto x : nums1)
{
if(x > nums2[index2[left]]) ret[index2[left++]] = x;
else ret[index2[right--]] = x;
}
return ret;
}
};
14、409. 最长回文串:
class Solution
{
public:
int longestPalindrome(string s)
{
// 1、计数 - 用数组模拟哈希表
int hash[127] = { 0 };
for(auto ch : s) hash[ch]++;
// 2、统计结果
int ret = 0;
for(auto x : hash) ret += x / 2 * 2;
return ret == s.size() ? ret : ret + 1;
}
};
15、942. 增减字符串匹配:
算法原理:
①当遇到“I”:选择当前最小的那个数;
②当遇到“D”:选择当前最大的那个数。
class Solution
{
public:
vector diStringMatch(string s)
{
int left = 0, right = s.size();
vector ret;
for(auto ch : s)
{
if(ch == 'I') ret.push_back(left++);
else ret.push_back(right--);
}
ret.push_back(left); // 把最后一个数放进去
return ret;
}
};
16、455. 分发饼干:
class Solution
{
public:
int findContentChildren(vector& g, vector& s)
{
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int ret = 0, m = g.size(), n = s.size();
for(int i = 0, j = 0; i < m && j < n; i++, j++)
{
while(j < n && s[j] < g[i]) j++;
if(j < n) ret++;
}
return ret;
}
};
17、553. 最优除法:
算法原理:
解法一:暴力解法->递归->记忆化搜索->动态规划;
解法二:贪心:除了前两个数以外,其余的数全放在分子上即可。
class Solution
{
public:
string optimalDivision(vector& nums)
{
int n = nums.size();
if(1 == n) return to_string(nums[0]);
if(2 == n) return to_string(nums[0]) + "/" + to_string(nums[1]);
string ret = to_string(nums[0]) + "/(" + to_string(nums[1]);
for(int i = 2; i < n; i++) ret += "/" + to_string(nums[i]);
return ret + ")";
}
};
18、45. 跳跃游戏 II:
class Solution
{
public:
int jump(vector& nums)
{
int left = 0, right = 0, maxpos = 0, ret = 0, n = nums.size();
while(left <= right) // 以防跳不到n - 1的位置
{
if(maxpos >= n - 1) return ret;
// 遍历当前层,更新下一层的最右节点
for(int i = left; i <= right; i++) maxpos = max(maxpos, nums[i] + i);
left = right + 1;
right = maxpos;
ret++;
}
return -1; // 跳不到的情况
}
};
20、134. 加油站:
算法原理:
解法一:暴力解法->枚举:
①依次枚举所有的起点;
②从起点开始,模拟一遍加油的流程即可。
解法二:优化->找规律(贪心):O(N)
class Solution
{
public:
int canCompleteCircuit(vector& gas, vector& cost)
{
int n = gas.size();
for(int i = 0; i < n; i++)
{
int rest = 0; // 标记净收益
int step = 0;
for(; step < n; step++) // 枚举向后走的步数
{
int index = (i + step) % n; // 枚举向后走的步数
rest= rest + gas[index] - cost[index];
if(rest < 0) break;
}
if(rest >= 0) return i;
i = i + step;
}
return -1;
}
};
21、738. 单调递增的数字:
解法一:暴力枚举 O(NlogN)
①从大到小的顺序,枚举[n, 0]区间内的数字;
②判断数字是否是“单调递增的”。
解法二:贪心(找规律) O(N)
①如果高位单调递增的话,我们不去修改;
②从左往右,找到第一个递减的位置,从这个位置向前推,推到相同区域的最左端,使其减小1,后面的数全部修改成9。
class Solution
{
public:
int monotoneIncreasingDigits(int n)
{
string s = to_string(n); // 把数字转化为字符串
int i = 0, m = s.size();
// 找第一个递减的位置
while(i + 1 < m && s[i] <= s[i + 1]) i++;
if(i + 1 == m) return n; // 判断一下特殊情况
while(i - 1 >= 0 && s[i - 1] == s[i]) i--;
s[i]--;
for(int j = i + 1; j < m; j++) s[j] = '9';
return stoi(s);
}
};
22、991. 坏了的计算器:
解法一:正向推导。
解法二:正难则反(没有小数)
①end<=begin:begin-end次+1操作;
②end>begin:奇数时只能+1,偶数时可+1可除2(除法更优)。
class Solution
{
public:
int brokenCalc(int startValue, int target)
{
// 正难则反 + 贪心
int ret = 0;
while(target > startValue)
{
if(target % 2 == 0) target /= 2;
else target += 1;
ret++;
}
return ret + startValue - target;
}
};
23、56. 合并区间:
解法:
①先按照左端点排序(能够合并的区间都是连续的);
②如何合并?求并集。
class Solution
{
public:
vector> merge(vector>& intervals)
{
// 1、先按照左端点排序
sort(intervals.begin(), intervals.end());
// 2、合并区间
int left = intervals[0][0], right = intervals[0][1];
vector> ret;
for(int i = 1; i < intervals.size(); i++)
{
int a = intervals[i][0], b = intervals[i][1];
if(a <= right) // 有重叠部分
{
// 合并 - 求并集
right = max(right, b);
}
else // 没有重叠部分
{
ret.push_back({left, right}); // 加入到结果中
left = a;
right = b;
}
}
// 别忘了最后一个区间
ret.push_back({left, right});
return ret;
}
};
24、435. 无重叠区间:
解法:排序(左端点)+贪心策略。
①按照左端点排序;
②移除最少区间<==>保留更多区间。
class Solution
{
public:
int eraseOverlapIntervals(vector>& intervals)
{
// 1、先按照左端点排序
sort(intervals.begin(), intervals.end());
// 2、移除区间
int left = intervals[0][0], right = intervals[0][1];
int ret = 0;
for(int i = 1; i < intervals.size(); i++)
{
int a = intervals[i][0], b = intervals[i][1];
if(a < right) // 有重叠部分
{
ret++; // 删掉一个区间
right = min(right, b);
}
else // 没有重叠部分
{
// left = a;
right = b;
}
}
return ret;
}
};
25、452. 用最少数量的箭引爆气球:
解法:
①先按照左端点排序(能够重叠的区间都是连续的);
②提出贪心策略:一支箭应该引爆更多气球->将互相重叠的所有区间都拿出来引爆。
如何求出互相重叠的区间?求交集。
class Solution
{
public:
int findMinArrowShots(vector>& points)
{
// 1、先按照左端点排序
sort(points.begin(), points.end());
// 2、求互相重叠区间的数量
int right = points[0][1];
int ret = 1;
for(int i = 1; i < points.size(); i++)
{
int a = points[i][0], b = points[i][1];
if(a <= right) // 有重叠部分
{
// 合并 - 求并集
right = min(right, b);
}
else // 没有重叠部分
{
ret++; // 加入到结果中
right = b;
}
}
return ret;
}
};
26、397. 整数替换:
解法一:模拟(递归+记忆化搜索)。
class Solution
{
unordered_map hash; // 时间优化
public:
int integerReplacement(int n)
{
return dfs(n);
}
int dfs(long long n)
{
if(hash.count(n))
{
return hash[n];
}
if(n == 1)
{
hash[1] = 0;
return 0;
}
if(n % 2 == 0)
{
hash[n] = 1 + dfs(n / 2);
return hash[n];
}
else
{
hash[n] = 1 + min(dfs(n - 1), dfs(n + 1));
return hash[n];
}
}
};
解法二:贪心
补充:二进制
①偶数:二进制表示中最后一位0;
②奇数:二进制表示中最后一位1;
③/2操作:二进制表示中统一右移一位。
class Solution
{
unordered_map hash; // 时间优化
public:
int integerReplacement(int n)
{
int ret = 0;
while(n > 1)
{
// 分类讨论
if(n % 2 == 0)
{
n /= 2;
ret++;
}
else
{
if(n == 3)
{
ret += 2;
n = 1;
}
else if(n % 4 == 1)
{
n = n / 2;
ret += 2;
}
else
{
n = n / 2 + 1;
ret += 2;
}
}
}
return ret;
}
};
27、354. 俄罗斯套娃信封问题:
解法一:动态规划:(超时):
乱序->有序->按照左端点排序->最长递增子序列。
①状态表示:dp[i]表示:以i位置的信封为结尾的所有的套娃序列中,最长的套娃序列的长度。
②状态转移方程:max(dp[j]+1)
0<=je[j][0]&&e[i][1]>e[j][1]
③初始化:全初始化为1。
④填表顺序:从左往右。
⑤返回值:dp表里的最大值
class Solution
{
public:
int maxEnvelopes(vector>& e)
{
// 解法一:动态规划
// 预处理
sort(e.begin(), e.end());
int n = e.size();
vector dp(n, 1);
int ret = 1;
for(int i = 1; i < n; i++)
{
for(int j = 0; j < i; j++)
{
if(e[i][0] > e[j][0] && e[i][1] > e[j][1])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
ret = max(ret, dp[i]);
}
return ret;
}
};
解法二:重写排序+贪心+二分:
①重写排序
当左端点不同的时候:左端点从小到大排序。
当左端点相同的时候:右端点从大到小排序。
②最长递增子序列。
class Solution
{
public:
int maxEnvelopes(vector>& e)
{
// 解法二:
// 重写排序
sort(e.begin(), e.end(), [&](const vector& v1, const vector& v2)
{
return v1[0] != v2[0] ? v1[0] < v2[0] :v1[1] > v2[1];
});
// 贪心+二分
vector ret;
ret.push_back(e[0][1]);
for(int i = 1; i < e.size(); i++)
{
int b = e[i][1];
if(b > ret.back())
{
ret.push_back(b);
}
else
{
int left = 0, right = ret.size() - 1;
while(left < right)
{
int mid = (left + right) / 2;
if(ret[mid] >= b) right = mid;
else left = mid + 1;
}
ret[left] = b;
}
}
return ret.size();
}
};
28、1262. 可被三整除的最大和:
解法一:动态规划。
解法二:正难则反+贪心+分类讨论:
先把所有的数累加在一起->根据累加和,删除一些数。
①sum%3=0,不删:
(x:标记%3=1的尽可能小的数,y:标记%3=2的尽可能小的数)
②sum%3=1,max(sum-x1,sum-y1-y2)
③sum%3=2,max(sum-y1,sum-x1-x2)
class Solution
{
public:
int maxSumDivThree(vector& nums)
{
const int INF = 0x3f3f3f3f;
int sum = 0, x1 = INF, x2 = INF, y1 = INF, y2 = INF;
for(auto x : nums)
{
sum += x;
if(x % 3 == 1)
{
if(x < x1) x2 = x1, x1 = x;
else if(x < x2) x2 = x;
}
else if(x % 3 == 2)
{
if(x < y1) y2 = y1, y1 = x;
else if(x < y2) y2 = x;
}
}
// 分类讨论
if(sum % 3 == 0) return sum;
else if(sum % 3 == 1) return max(sum - x1, sum - y1 - y2);
else return max(sum - y1, sum - x1 - x2);
}
};
29、1054. 距离相等的条形码:
解法:贪心+模拟 O(N)
①每次处理一批相同的数。摆放的时候,每次隔一个格子。
②先处理出现次数最多的那个数,剩下的数顺序无所谓。
class Solution
{
public:
vector rearrangeBarcodes(vector& b)
{
unordered_map hash; // 统计每个数出现的频次
int maxVal= 0, maxCount= 0;
for(auto x : b)
{
if(maxCount < ++hash[x])
{
maxCount = hash[x];
maxVal = x;
}
}
int n = b.size();
vectorret(n);
int index = 0;
// 先处理出现次数最多的那个数
for(int i = 0; i < maxCount; i++)
{
ret[index] = maxVal;
index += 2;
}
// 处理剩下的数
hash.erase(maxVal);
for(auto& [x, y] : hash)
{
for(int i = 0; i < y; i++)
{
if(index >= n) index = 1;
ret[index] = x;
index += 2;
}
}
return ret;
}
};
30、767. 重构字符串:
解法:贪心+模拟 O(N)
①每次处理一批相同的字符。摆放的时候,每次隔一个格子。
②先处理出现次数最多的那个字符,剩下的数顺序无所谓。
class Solution {
public:
string reorganizeString(string s)
{
int hash[26] = { 0 }; // 统计每个字符出现的频次
char maxChar = ' ';
int maxCount= 0;
for(auto ch : s)
{
if(maxCount < ++hash[ch - 'a'])
{
maxChar = ch;
maxCount = hash[ch - 'a'];
}
}
int n = s.size();
// 判断特殊情况
if(maxCount > (n + 1) / 2) return "";
string ret(n, ' ');
int index = 0;
// 先处理出现次数最多的那个字符
for(int i = 0; i < maxCount; i++)
{
ret[index] = maxChar;
index += 2;
}
// 处理剩下的数
hash[maxChar - 'a'] = 0;
for(int i = 0; i < 26; i++)
{
for(int j = 0; j < hash[i]; j++)
{
if(index >= n) index = 1;
ret[index] = 'a' + i;
index += 2;
}
}
return ret;
}
};