2679. 矩阵中的和
给你一个下标从 0 开始的二维整数数组 nums 。一开始你的分数为 0 。你需要执行以下操作直到矩阵变为空:
矩阵中每一行选取最大的一个数,并删除它。如果一行中有多个最大的数,选择任意一个并删除。
在步骤 1 删除的所有数字中找到最大的一个数字,将它添加到你的 分数 中。
请你返回最后的 分数 。
示例 1:
输入:nums = [[7,2,1],[6,4,2],[6,5,3],[3,2,1]]
输出:15
解释:第一步操作中,我们删除 7 ,6 ,6 和 3 ,将分数增加 7 。下一步操作中,删除 2 ,4 ,5 和 2 ,将分数增加 5 。最后删除 1 ,2 ,3 和 1 ,将分数增加 3 。所以总得分为 7 + 5 + 3 = 15 。
class Solution {
// 2、建堆 优先级队列
public int matrixSum(int[][] nums) {
int n = nums.length;
int m = nums[0].length;
PriorityQueue<Integer>[] priorityQueue = new PriorityQueue[n];
for (int i = 0; i < n; i++) {
priorityQueue[i] = new PriorityQueue<Integer>((a, b) -> b - a);
for (int j = 0; j < m; j++) {
priorityQueue[i].offer(nums[i][j]);
}
}
int score = 0;
for (int j = 0; j < m; j++) {
int max = 0;
for (int i = 0; i < n; i++) {
// 删除每一行的最大值即堆顶元素
max = Math.max(max, priorityQueue[i].poll());
}
score += max;
}
return score;
}
// 1、每一行排序,添加比较的每一列最大值
public int matrixSum1(int[][] nums) {
int n = nums.length;
int m = nums[0].length;
for (int i = 0; i < n; i++) {
Arrays.sort(nums[i]);
}
int score = 0;
for (int j = 0; j < m; j++) {
// 列
int max = 0;
for (int i = 0; i < n; i++) {
max = Math.max(max, nums[i][j]);
}
score += max;
}
return score;
}
}
2600. K 件物品的最大和
袋子中装有一些物品,每个物品上都标记着数字 1 、0 或 -1 。
给你四个非负整数 numOnes 、numZeros 、numNegOnes 和 k 。
袋子最初包含:
numOnes 件标记为 1 的物品。
numZeroes 件标记为 0 的物品。
numNegOnes 件标记为 -1 的物品。
现计划从这些物品中恰好选出 k 件物品。返回所有可行方案中,物品上所标记数字之和的最大值。
示例 1:
输入:numOnes = 3, numZeros = 2, numNegOnes = 0, k = 2
输出:2
解释:袋子中的物品分别标记为 {1, 1, 1, 0, 0} 。取 2 件标记为 1 的物品,得到的数字之和为 2 。
可以证明 2 是所有可行方案中的最大值。
public int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) {
// 1 可全取
// 取全部 1 和 部分 0
// 取全部 1, 0 和部分 -1
if (k <= numOnes) {
return k;
} else if (k <= numOnes + numZeros) {
// numOnes < k && k < numOnes + numZeros
return numOnes;
} else {
return numOnes - (k - numOnes - numZeros);
}
}
445. 两数相加 II
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
while (l1 != null) {
stack1.add(l1.val);
l1 = l1.next;
}
while (l2 != null) {
stack2.add(l2.val);
l2 = l2.next;
}
int carry = 0; // 记得进位
ListNode ans = null;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
int val1 = stack1.isEmpty() ? 0: stack1.pop();
int val2 = stack2.isEmpty() ? 0 : stack2.pop();
int sum = val1 + val2 + carry;
int nextVal = sum % 10;
carry = sum / 10;
ListNode nextCur = new ListNode(nextVal); // 下一个节点
nextCur.next = ans;
ans = nextCur; // 新的 head
}
return ans;
}
}
3. 无重复字符的最长子串
添加链接描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
class Solution {
// 滑动窗口
// key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0;
Map<Character, Integer> map = new HashMap<>();
for (int end = 0, start = 0; end < n; end++) {
if (map.containsKey(s.charAt(end))) {
start = Math.max(start, map.get(s.charAt(end)));
}
ans = Math.max(ans, end - start + 1);
map.put(s.charAt(end), end + 1); // 下次 start 更新的位置
}
return ans;
}
}
1017. 负二进制转换
添加链接描述
给你一个整数 n ,以二进制字符串的形式返回该整数的 负二进制(base -2)表示。
注意,除非字符串就是 “0”,否则返回的字符串中不能含有前导零。
示例 1:
输入:n = 2
输出:“110”
解释:(-2)2 + (-2)1 = 2
class Solution {
// 基数为 -2,负二进制的余数可能有0,1,-1,要保证余数为正
public String baseNeg2(int n) {
if (n == 0) {
return "0";
}
StringBuilder ans = new StringBuilder();
while (n != 0) {
int remain = Math.abs(n % (-2));
ans.append(remain == 0 ? "0" : "1");
n = (n - remain) / (-2);
}
return ans.reverse().toString();
}
}
2404. 出现最频繁的偶数元素
添加链接描述
给你一个整数数组 nums ,返回出现最频繁的偶数元素。
如果存在多个满足条件的元素,只需要返回 最小 的一个。如果不存在这样的元素,返回 -1 。
示例 1:
输入:nums = [0,1,2,2,4,4,1]
输出:2
解释:
数组中的偶数元素为 0、2 和 4 ,在这些元素中,2 和 4 出现次数最多。
返回最小的那个,即返回 2 。
class Solution {
public static int mostFrequentEven(int[] nums) {
int max = 0; // 最频繁次数
int ans = -1;
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
if (num % 2 == 0) {
map.put(num, map.getOrDefault(num, 0) + 1);
if (max < map.get(num)) {
max = map.get(num); // 更新出现最频繁 次数
ans = num; // 更新出现最频繁 元素
} else if (max == map.get(num)) {
// 次数相同时 结果为最小的元素
ans = Math.min(ans, num);
}
}
}
return ans;
}
}
831. 隐藏个人信息
添加链接描述
给你一条个人信息字符串 s ,可能表示一个 邮箱地址 ,也可能表示一串 电话号码 。返回按如下规则 隐藏 个人信息后的结果:
电子邮件地址:
一个电子邮件地址由以下部分组成:
一个 名字 ,由大小写英文字母组成,后面跟着
一个 ‘@’ 字符,后面跟着
一个 域名 ,由大小写英文字母和一个位于中间的 ‘.’ 字符组成。‘.’ 不会是域名的第一个或者最后一个字符。
要想隐藏电子邮件地址中的个人信息:
名字 和 域名 部分的大写英文字母应当转换成小写英文字母。
名字 中间的字母(即,除第一个和最后一个字母外)必须用 5 个 "*****"
替换。
电话号码:
一个电话号码应当按下述格式组成:
电话号码可以由 10-13 位数字组成
后 10 位构成 本地号码
前面剩下的 0-3 位,构成 国家代码
利用 {'+', '-', '(', ')', ' '}
这些 分隔字符 按某种形式对上述数字进行分隔
要想隐藏电话号码中的个人信息:
移除所有 分隔字符
隐藏个人信息后的电话号码应该遵从这种格式:
"***-***-XXXX"
如果国家代码为 0 位数字
"+*-***-***-XXXX"
如果国家代码为 1 位数字
"+**-***-***-XXXX"
如果国家代码为 2 位数字
"+***-***-***-XXXX"
如果国家代码为 3 位数字
“XXXX” 是最后 4 位 本地号码
示例 1:
输入:s = "[email protected]"
输出:"l*****[email protected]"
解释:s 是一个电子邮件地址。
名字和域名都转换为小写,名字的中间用 5 个 * 替换。
class Solution {
// 2、
static String[] country = {
"", "+*-", "+**-", "+***-"};
public String maskPII(String s) {
int atIndex = s.indexOf("@");
StringBuilder stringBuilder = new StringBuilder();
if (atIndex > 0) {
// 电子邮件地址
stringBuilder.append(s.charAt(0) + "*****" + s.substring(atIndex - 1));
return stringBuilder.toString().toLowerCase();
} else {
// 电话号码
s = s.replaceAll("[^0-9]", "");
stringBuilder.append(country[s.length() - 10] + "***-***-" + s.substring(s.length() - 4));
return stringBuilder.toString();
}
}
// 1、
public static String maskPII1(String s) {
int n = s.length();
// 1、分析是邮件地址还是电话号码
boolean flg = false;
for (int i = n - 1; i >= 0; i--) {
if (s.charAt(i) == '.') {
flg = true;
break;
}
}
StringBuilder stringBuilder = new StringBuilder();
// 2、电子邮件地址
if (flg) {
int index = s.indexOf("@"); // 获取 @ 下标,拼接首尾字母,@ 到结尾
stringBuilder.append(s.charAt(0) + "*****" + s.charAt(index - 1) + s.substring(index));
return stringBuilder.toString().toLowerCase();
}
// 3、电话号码
int cnt = 0;
for (int i = 0; i < n; i++) {
// 统计电话号码位数
if (Character.isDigit(s.charAt(i))) {
cnt++;
}
}
if (cnt == 10) {
// 国家代码 0 位
stringBuilder.append("***-***-");
} else if (cnt == 11) {
// 国家代码 1 位
stringBuilder.append("+*-***-***-");
} else if (cnt == 12) {
// 国家代码 2 位
stringBuilder.append("+**-***-***-");
} else if (cnt == 13) {
// 国家代码 3 位
stringBuilder.append("+***-***-***-");
}
StringBuilder sb = new StringBuilder(); // 后四位号码 可能有符号 '-'
int digitCnt = 0;
for (int i = n - 1; i >= 0; i--) {
if (Character.isDigit(s.charAt(i))) {
sb.append(s.charAt(i));
digitCnt++;
if (digitCnt == 4) {
break;
}
}
}
stringBuilder.append(sb.reverse());
return stringBuilder.toString();
}
}
2367. 算术三元组的数目
添加链接描述
给你一个下标从 0 开始、严格递增 的整数数组 nums 和一个正整数 diff 。如果满足下述全部条件,则三元组 (i, j, k) 就是一个 算术三元组 :
i < j < k ,
nums[j] - nums[i] == diff 且
nums[k] - nums[j] == diff
返回不同 算术三元组 的数目。
示例 1:
输入:nums = [0,1,4,6,7,10], diff = 3
输出:2
解释:
(1, 2, 4) 是算术三元组:7 - 4 == 3 且 4 - 1 == 3 。
(2, 4, 5) 是算术三元组:10 - 7 == 3 且 7 - 4 == 3 。
class Solution {
// 2、三指针 O(N) 下标只增不减
public int arithmeticTriplets(int[] nums, int diff) {
int cnt = 0;
int n = nums.length;
for (int i = 0, j = 1, k = 2; i < n - 2 && j < n - 1 && k < n; i++) {
j = Math.max(j, i + 1);
while (j < n - 1 && nums[j] - nums[i] < diff) {
// 对于当前 x,找 x + diff
j++;
}
if (j >= n - 1 || nums[j] - nums[i] > diff) {
// 当前 x,没有满足的三元组
continue;
}
k = Math.max(k, j + 1);
while (k < n && nums[k] - nums[j] < diff) {
k++;
}
if (k < n && nums[k] - nums[j] == diff) {
// 满足的三元组加一
cnt++;
}
}
return cnt;
}
// 2、哈希 O(N) 数组严格递增,没有重复
// 等差数列 -> x, x+diff, x+2*diff
public int arithmeticTriplets2(int[] nums, int diff) {
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int cnt = 0;
for (int x : nums) {
if (set.contains(x + diff) && set.contains(x + 2 * diff)) {
cnt++;
}
}
return cnt;
}
// 1、暴力枚举
public int arithmeticTriplets1(int[] nums, int diff) {
int cnt = 0;
int n = nums.length;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[j] - nums[i] != diff) {
continue;
}
for (int k = j + 1; k < n; k++) {
if (nums[k] - nums[j] == diff) {
cnt++;
}
}
}
}
return cnt;
}
}
1637. 两点之间不包含任何点的最宽垂直区域
添加链接描述
给你 n 个二维平面上的点 points ,其中 points[i] = [xi, yi] ,请你返回两点之间内部不包含任何点的 最宽垂直区域 的宽度。
垂直区域 的定义是固定宽度,而 y 轴上无限延伸的一块区域(也就是高度为无穷大)。 最宽垂直区域 为宽度最大的一个垂直区域。
请注意,垂直区域 边上 的点 不在 区域内。
示例 1:
输入:points = [[8,7],[9,9],[7,4],[9,7]]
输出:1
解释:红色区域和蓝色区域都是最优区域。
class Solution {
// 按横坐标排序,依次求出所有相邻点的横坐标距离,返回最大值 (与 y 轴无关)
public int maxWidthOfVerticalArea(int[][] points) {
Arrays.sort(points, (a, b) -> a[0] - b[0]);
int max = 0;
for (int i = 1; i < points.length; i++) {
max = Math.max(max, points[i][0] - points[i - 1][0]);
}
return max;
}
}
1641. 统计字典序元音字符串的数目
添加链接描述
给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。
字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。
示例 1:
输入:n = 1
输出:5
解释:仅由元音组成的 5 个字典序字符串为 [“a”,“e”,“i”,“o”,“u”]
示例 2:
输入:n = 2
输出:15
解释:仅由元音组成的 15 个字典序字符串为
[“aa”,“ae”,“ai”,“ao”,“au”,“ee”,“ei”,“eo”,“eu”,“ii”,“io”,“iu”,“oo”,“ou”,“uu”]
注意,“ea” 不是符合题意的字符串,因为 ‘e’ 在字母表中的位置比 ‘a’ 靠后
class Solution {
// 2、C(n+4,4)
public int countVowelStrings(int n) {
return (n + 4) * (n + 3) * (n + 2) * (n + 1) / 24;
}
// 1、动态规划
public int countVowelStrings3(int n) {
int[] dp = new int[5];
Arrays.fill(dp, 1);
for (int i = 1; i < n; i++) {
for (int j = 1; j < 5; j++) {
dp[j] += dp[j - 1];
}
}
// n = 5 ——> [1, 5, 15, 35, 70]
return Arrays.stream(dp).sum();
}
// dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
// [1, 1, 1, 1, 1]
// [1, 2, 3, 4, 5]
// [1, 3, 6, 10, 15]
// [1, 4, 10, 20, 35]
// [1, 5, 15, 35, 60]
public int countVowelStrings2(int n) {
int[][] dp = new int[n][5];
for (int i = 0; i < 5; i++) {
dp[0][i] = 1;
}
for (int i = 1; i < n; i++) {
// dp[i][0] = dp[i - 1][0];
dp[i][0] = 1;
for (int j = 1; j < 5; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return Arrays.stream(dp[n - 1]).sum();
}
public int countVowelStrings1(int n) {
int a = 1, e = 1, i = 1, o = 1, u = 1;
for (int k = 1; k < n; k++) {
a = a + e + i + o + u;
e = e + i + o + u;
i = i + o + u;
o = o + u;
u = u;
}
return a + e + i + o + u;
}
}
2395. 和相等的子数组
添加链接描述
给你一个下标从 0 开始的整数数组 nums ,判断是否存在 两个 长度为 2 的子数组且它们的 和 相等。注意,这两个子数组起始位置的下标必须 不相同 。
如果这样的子数组存在,请返回 true,否则返回 false 。
子数组 是一个数组中一段连续非空的元素组成的序列。
示例 1:
输入:nums = [4,2,4]
输出:true
解释:元素为 [4,2] 和 [2,4] 的子数组有相同的和 6 。
class Solution {
public boolean findSubarrays(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int i = 1; i < nums.length; i++) {
int sum = nums[i - 1] + nums[i];
if (set.contains(sum)) {
return true;
}
set.add(sum);
}
return false;
}
}
1574. 删除最短的子数组使剩余数组有序
给你一个整数数组 arr ,请你删除一个子数组(可以为空),使得 arr 中剩下的元素是 非递减 的。
一个子数组指的是原数组中连续的一个子序列。
请你返回满足题目要求的最短子数组的长度。
示例 1:
输入:arr = [1,2,3,10,4,2,3,5]
输出:3
解释:我们需要删除的最短子数组是 [10,4,2] ,长度为 3 。剩余元素形成非递减数组 [1,2,3,3,5] 。
另一个正确的解为删除子数组 [3,10,4] 。
class Solution {
// 双指针 枚举左端点,移动右端点
public int findLengthOfShortestSubarray(int[] arr) {
int n = arr.length;
int r = n - 1;
// 从右端点开始,arr[r] 最大,r 到达非递减的位置
while (r > 0 && arr[r - 1] <= arr[r]) {
r--;
}
// arr 为非递减数组,不需要删除
if (r == 0) {
return 0;
}
// 需要删除的是 [0-r) 之间,最多 [0-r) 都需要删除
int ans = r;
for (int l = 0; l == 0 || arr[l - 1] <= arr[l]; l++) {
// 当前 l 与左边非递减,判断是否与 arr[r] 非递减
while (r < n && arr[l] > arr[r]) {
r++;
}
// 需要删除的是 [l-r) 之间
ans = Math.min(ans, r - l - 1);
}
return ans;
}
}