难度简单0
给你一个仅由 0
和 1
组成的二进制字符串 s
。
如果子字符串中 所有的 0
都在 1
之前 且其中 0
的数量等于 1
的数量,则认为 s
的这个子字符串是平衡子字符串。请注意,空子字符串也视作平衡子字符串。
返回 s
中最长的平衡子字符串长度。
子字符串是字符串中的一个连续字符序列。
示例 1:
输入:s = "01000111"
输出:6
解释:最长的平衡子字符串是 "000111" ,长度为 6 。
示例 2:
输入:s = "00111"
输出:4
解释:最长的平衡子字符串是 "0011" ,长度为 4 。
示例 3:
输入:s = "111"
输出:0
解释:除了空子字符串之外不存在其他平衡子字符串,所以答案为 0 。
提示:
1 <= s.length <= 50
'0' <= s[i] <= '1'
优雅写法:
class Solution {
public int findTheLongestBalancedSubstring(String s) {
// 记录连续0的个数pre和连续1的个数cur
// 那么这个01串的长度就是2 * min(pre, cur)
var c = s.toCharArray();
int ans = 0, pre = 0, cur = 0, n = c.length;
for(int i = 0; i < n; i++){
++cur; //
if(i == c.length-1 || c[i] != c[i+1]){
if(c[i] == '1'){ // 在01串的末尾
ans = Math.max(ans, Math.min(pre, cur) * 2);
}
pre = cur; // 进入下一段, pre 上一个连续段的长度
cur = 0;
}
}
return ans;
}
}
class Solution {
public int findTheLongestBalancedSubstring(String s) {
int n = s.length();
int res = 0;
int i = 0;
while(i < n){
if(s.charAt(i) == '1'){
i++;
continue;
}
int tmp = i+1;
int cnt0 = 0, cnt1 = 0;
while(i < n && s.charAt(i) == '0'){
cnt0++;
i++;
}
while(i < n && s.charAt(i) == '1' && cnt1 < cnt0){
cnt1++;
i++;
}
if(cnt0 == cnt1) res = Math.max(res, cnt0+cnt1);
i = tmp;
}
return res;
}
}
难度中等0
给你一个整数数组 nums
。请你创建一个满足以下条件的二维数组:
nums
中的元素。返回结果数组。如果存在多种答案,则返回其中任何一种。
请注意,二维数组的每一行上可以存在不同数量的元素。
示例 1:
输入:nums = [1,3,4,1,2,3,1]
输出:[[1,3,4,2],[1,3],[1]]
解释:根据题目要求可以创建包含以下几行元素的二维数组:
- 1,3,4,2
- 1,3
- 1
nums 中的所有元素都有用到,并且每一行都由不同的整数组成,所以这是一个符合题目要求的答案。
可以证明无法创建少于三行且符合题目要求的二维数组。
示例 2:
输入:nums = [1,2,3,4]
输出:[[4,3,2,1]]
解释:nums 中的所有元素都不同,所以我们可以将其全部保存在二维数组中的第一行。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= nums.length
因为没有限制每一行最多放几个数,我们可以把当前所有不同的数都放一个进去。因此行数就是众数出现的次数。
class Solution {
public List<List<Integer>> findMatrix(int[] nums) {
int n = nums.length;
int[] cnt = new int[n+1];
for(int c : nums) cnt[c]++;
int maxnum = 0;
for(int c : cnt){
if(c > maxnum){
maxnum = c;
}
}
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < maxnum; i++){
res.add(new ArrayList<Integer>());
}
for(int i = 0; i < cnt.length; i++){
int cnum = cnt[i];
for(int j = 0; j < cnum; j++){
res.get(j).add(i);
}
}
return res;
}
}
难度中等1
有两只老鼠和 n
块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。
下标为 i
处的奶酪被吃掉的得分为:
reward1[i]
。reward2[i]
。给你一个正整数数组 reward1
,一个正整数数组 reward2
,和一个非负整数 k
。
请你返回第一只老鼠恰好吃掉 k
块奶酪的情况下,最大 得分为多少。
示例 1:
输入:reward1 = [1,1,3,4], reward2 = [4,4,1,1], k = 2
输出:15
解释:这个例子中,第一只老鼠吃掉第 2 和 3 块奶酪(下标从 0 开始),第二只老鼠吃掉第 0 和 1 块奶酪。
总得分为 4 + 4 + 3 + 4 = 15 。
15 是最高得分。
示例 2:
输入:reward1 = [1,1], reward2 = [1,1], k = 2
输出:2
解释:这个例子中,第一只老鼠吃掉第 0 和 1 块奶酪(下标从 0 开始),第二只老鼠不吃任何奶酪。
总得分为 1 + 1 = 2 。
2 是最高得分。
提示:
1 <= n == reward1.length == reward2.length <= 105
1 <= reward1[i], reward2[i] <= 1000
0 <= k <= n
比赛的时候光想记忆化搜索=>递推去了,应该先看数据范围,最差
n^2
,应该直接放弃这个解法–https://leetcode.cn/problems/mice-and-cheese/solution/tan-xin-ji-qi-zheng-ming-by-endlesscheng-u783/
套路, 通过比较奶酪被第一个老鼠吃还是被第二个老鼠吃,可以找到一些性质
比较两个物品
i
和j
被吃后的得分:如果
1
吃i
,2
吃j
比较大:r1[i] + r2[j] > r2[i] + r1[j]
不等式变形技巧:将下标相同的放一起
==>
r1[i] - r2[i] > r1[j] - r2[j]
令
d[i] = r1[i] - r2[i]
则
d[i] > d[j]
- 则按照
d[i] = r1[i] - r2[i]
从大到小排序,前k
个给第一只老鼠,余下的给第二只老鼠
经典贪心问题。先假设所有奶酪都是第二只老鼠吃掉的,那么得分为 sum(reward2)
。
现在我们要把 k 个奶酪改成第一只老鼠吃掉的。假设我们把第 i
个奶酪改成第一只老鼠吃掉,那么与第二只老鼠吃掉相比,得分的变化量为 reward1[i] - reward2[i]
。
为了让得分尽量大,我们需要让得分的变化量总和尽量大。因此选择前 k 大的变化量即可。
class Solution {
// 贪心 + 优先队列
// 统计一下每个奶酪上的差值
// 差值最大的前k个都给 鼠1 吃, 剩下的都给 鼠2 吃
public int miceAndCheese(int[] reward1, int[] reward2, int k) {
int n = reward1.length;
// 一开始假设都是第二只老鼠吃掉的
long ans = 0;
for(int x : reward2) ans += x;
// 计算每个奶酪的得分变化量
Integer[] diff = new Integer[n];
for(int i = 0; i < n; i++) diff[i] = reward1[i] - reward2[i];
// 选出前k大的得分变化量
Arrays.sort(diff, (a,b) -> b-a);
for(int i = 0; i < k; i++)
ans += diff[i];
return (int)ans;
}
}
class Solution {
int[] num1;
int[] num2;
int k;
int[][] cache; // 第i位置上还剩k口的最高得分
public int miceAndCheese(int[] reward1, int[] reward2, int k) {
// 第一只老鼠吃还是不吃
// 吃 +reword1[i], k=k-1
// 不吃 +reword2[i], k
this.num1 = reward1;
this.num2 = reward2;
this.k = k;
cache = new int[num1.length+1][k+1];
for(int i = 0; i < num1.length; i++) Arrays.fill(cache[i], Integer.MIN_VALUE);
return dfs(0, k);
}
public int dfs(int i, int k){
if(i == num1.length){
if(k == 0) return 0;
return Integer.MIN_VALUE/2;
}
if(k < 0 || k > num1.length-i) return Integer.MIN_VALUE/2;
if(cache[i][k] != Integer.MIN_VALUE) return cache[i][k];
int res = Integer.MIN_VALUE/2;
res = Math.max(num2[i] + dfs(i+1, k), num1[i] + dfs(i+1, k-1));
return cache[i][k] = res;
}
}
难度中等284
公司计划面试 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:
输入:costs = [[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]]
输出:1859
示例 3:
输入:costs = [[515,563],[451,713],[537,709],[343,819],[855,779],[457,60],[650,359],[631,42]]
输出:3086
提示:
2 * n == costs.length
2 <= costs.length <= 100
costs.length
为偶数1 <= aCosti, bCosti <= 1000
证明一下为什么要使cost[i][0] - cost[i][1]越小越好:
假设A团体去A市,B团体去B市,此时费用为a。
假设上述A团体去B市,B团体去A市,此时费用为b。
求a的最小值,相当于求b的最大值,又相当于要使b-a的值是所有组合中最大的。
b-a的值等于A团体所有人的cost[1]-cost[0]的和,加上B团体所有人的cost[0]-cost[1]的和。
b-a的值又等于A团体所有人的cost[1]-cost[0]的和,减去B团体所有人的cost[1]-cost[0]的和。
那么只要A团体所有人的cost[1]-cost[0]的和最大,B团体所有人的cost[1]-cost[0]的和最小,就可以使得b-a最大。
只要对所有人的cost[1]-cost[0]排个序,前n个人去A市,后n个人去B市,即可使得b-a最大,亦即a最小。
class Solution {
public int twoCitySchedCost(int[][] costs) {
int res = 0;
for(int i = 0; i < costs.length; i++){
res += costs[i][1]; // 假设都飞往b市
}
int[] diff = new int[costs.length];
for(int i = 0; i < costs.length; i++){
// 统计一下飞两地的差值
diff[i] = costs[i][0] - costs[i][1];
}
// 要求最低费用,将变化量从小到大排序
Arrays.sort(diff);
for(int i = 0; i < costs.length/2; i++){
// 前n个飞a市,计算变化量
res += diff[i];
}
return res;
}
}
难度困难2
给你一个整数 n
和一个在范围 [0, n - 1]
以内的整数 p
,它们表示一个长度为 n
且下标从 0 开始的数组 arr
,数组中除了下标为 p
处是 1
以外,其他所有数都是 0
。
同时给你一个整数数组 banned
,它包含数组中的一些位置。banned
中第 i 个位置表示 arr[banned[i]] = 0
,题目保证 banned[i] != p
。
你可以对 arr
进行 若干次 操作。一次操作中,你选择大小为 k
的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr
中唯一的 1
不会到达任何 banned
中的位置。换句话说,arr[banned[i]]
始终 保持 0
。
请你返回一个数组 ans
,对于 [0, n - 1]
之间的任意下标 i
,ans[i]
是将 1
放到位置 i
处的 最少 翻转操作次数,如果无法放到位置 i
处,此数为 -1
。
i
,ans[i]
相互之间独立计算。示例 1:
输入:n = 4, p = 0, banned = [1,2], k = 4
输出:[0,-1,-1,1]
解释:k = 4,所以只有一种可行的翻转操作,就是将整个数组翻转。一开始 1 在位置 0 处,所以将它翻转到位置 0 处需要的操作数为 0 。
我们不能将 1 翻转到 banned 中的位置,所以位置 1 和 2 处的答案都是 -1 。
通过一次翻转操作,可以将 1 放到位置 3 处,所以位置 3 的答案是 1 。
示例 2:
输入:n = 5, p = 0, banned = [2,4], k = 3
输出:[0,-1,-1,-1,-1]
解释:这个例子中 1 一开始在位置 0 处,所以此下标的答案为 0 。
翻转的子数组长度为 k = 3 ,1 此时在位置 0 处,所以我们可以翻转子数组 [0, 2],但翻转后的下标 2 在 banned 中,所以不能执行此操作。
由于 1 没法离开位置 0 ,所以其他位置的答案都是 -1 。
示例 3:
输入:n = 4, p = 2, banned = [0,1,3], k = 1
输出:[-1,-1,0,-1]
解释:这个例子中,我们只能对长度为 1 的子数组执行翻转操作,所以 1 无法离开初始位置。
提示:
1 <= n <= 105
0 <= p <= n - 1
0 <= banned.length <= n - 1
0 <= banned[i] <= n - 1
1 <= k <= n
banned[i] != p
banned
中的值 互不相同 。以后再看