目录(每日一题)
1748. 唯一元素的和
给你一个整数数组 nums 。数组中唯一元素是那些只出现恰好一次 的元素。
请你返回 nums 中唯一元素的和 。
示例:
输入:nums = [1,2,3,2]
输出:4
解释:唯一元素为 [1,3] ,和为 4
提示:
- 1 <= nums.length <= 100
- 1 <= nums[i] <= 100
解题思路:使用哈希表记录元素出现的次数,再把出现次数为一的元素相加。
public int sumOfUnique(int[] nums) {
// 统计次数,再相加
int[] set = new int[101];
int ans = 0;
for (int num : nums) {
set[num]++;
}
for(int i = 1;i<=100;i++){
if(set[i] == 1){
ans += i;
}
}
return ans;
}
1405. 最长快乐字符串
如果字符串中不含有任何 ‘aaa’,‘bbb’ 或 ‘ccc’ 这样的字符串作为子串,那么该字符串就是一个「快乐字符串」。
给你三个整数 a,b ,c,请你返回 任意一个 满足下列全部条件的字符串 s:
- s 是一个尽可能长的快乐字符串。
- s 中 最多 有a 个字母 ‘a’、b 个字母 ‘b’、c 个字母 ‘c’ 。
- s 中只含有 ‘a’、‘b’ 、‘c’ 三种字母。
如果不存在这样的字符串 s ,请返回一个空字符串 “”。
示例:
输入:a = 1, b = 1, c = 7
输出:"ccaccbcc"
解释:"ccbccacc" 也是一种正确答案。
提示:
- 0 <= a, b, c <= 100
- a + b + c > 0
解题思路:贪心思想,将最多的一种字符添加进结果字符串,若不符合,添加次多的,直到不能添加为止。
public String longestDiverseString(int a, int b, int c) {
StringBuilder res = new StringBuilder();
int[][] arr = new int[][]{{0,a},{1,b},{2, c}};
while (true) {
// 按字符的次数从大到小排序
Arrays.sort(arr, (p1, p2) -> p2[1] - p1[1]);
boolean hasNext = false;
// 循环判断能不能添加
for (int[] pair : arr) {
if (pair[1] <= 0) {
break;
}
int m = res.length();
// 不符合规则,跳过
if (m >= 2 && res.charAt(m - 2) == 'a'+pair[0] &&
res.charAt(m - 1) == 'a'+pair[0]) {
continue;
}
hasNext = true;
// 添加字符,次数减一
res.append((char)(pair[0]+'a'));
pair[1]--;
break;
}
if (!hasNext) {
break;
}
}
// 返回结果
return res.toString();
}
1001. 网格照明
在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于关闭状态。
给你一个由灯的位置组成的二维数组 lamps ,其中 lamps[i] = [rowi, coli] 表示 打开 位于 grid[rowi][coli] 的灯。即便同一盏灯可能在 lamps 中多次列出,不会影响这盏灯处于打开状态。
当一盏灯处于打开状态,它将会照亮自身所在单元格以及同一 行、同一 列和两条对角线上的所有其他单元格。
另给你一个二维数组queries,其中queries[j] = [rowj, colj] 。对于第 j 个查询,如果单元格 [rowj, colj] 是被照亮的,则查询结果为 1 ,否则为 0 。在第 j 次查询之后 [按照查询的顺序] ,关闭 位于单元格 grid[rowj][colj] 上及相邻 8 个方向上(与单元格 grid[rowi][coli] 共享角或边)的任何灯。
返回一个整数数组 ans 作为答案, ans[j] 应等于第 j 次查询 queries[j] 的结果,1表示照亮,0表示未照亮。
示例:
输入:n = 5, lamps = [[0,0],[4,4]], queries = [[1,1],[1,0]]
输出:[1,0]
解释:最初所有灯都是关闭的。在执行查询之前,打开位于 [0, 0] 和 [4, 4] 的灯。
第0次查询检查grid[1][1] 是否被照亮(蓝色方框)。该单元格被照亮,所以 ans[0] = 1。然后,关闭红色方框中的所有灯。
第 1 次查询检查 grid[1][0] 是否被照亮(蓝色方框)。该单元格没有被照亮,所以 ans[1] = 0 。然后,关闭红色矩形中的所有灯。
解题思路:使用哈希表来记录被点亮的灯,使用四个哈希表分别存储被照亮的行,列,左对角线,右对角线。再使用一个哈希表存储被点亮灯的位置,以便再关闭时,快速查找。对角线使用截矩作为代指。
class Solution {
//定义一个方向数组 8个方向 上下左右 左上 左下 右上 右下
int[][] dirs = new int[][]{{0,0},{0,-1},{0,1},{-1,0},{-1,-1},{-1,1},{1,0},{1,-1},{1,1}};
public int[] gridIllumination(int n, int[][] lamps, int[][] queries) {
// row - 行亮灯情况
// col - 列亮灯情况
// left - 左对角线亮灯情况
// right - 右对角线亮灯情况
Map<Integer, Integer> row = new HashMap<>();
Map<Integer, Integer> col = new HashMap<>();
Map<Integer, Integer> left = new HashMap<>();
Map<Integer, Integer> right = new HashMap<>();
long N = n;
// set 保存点亮的灯
Set<Long> set = new HashSet<>();
for(int[] lamp : lamps){
// 一次遍历,初始化所有灯的亮灯情况,并存入对应哈希表~
int x = lamp[0];
int y = lamp[1];
int a = x + y;
int b = x - y;
// 二维化一维,存储到哈希表中
if(set.contains(x * N + y)){
continue;
}
// 分别点亮对应的行,列,对角线
increment(row, x);
increment(col, y);
increment(left, a);
increment(right, b);
// 点亮的灯加入集合中
set.add(x * N + y);
}
int m = queries.length;
int[] ans = new int[m];
for(int i = 0; i < m; i++){
int[] q = queries[i];
int x = q[0];
int y = q[1];
int a = x + y;
int b = x - y;
// 判断网格为止是否点亮
if(row.containsKey(x) || col.containsKey(y) ||
left.containsKey(a) || right.containsKey(b)){
ans[i] = 1;
}
// 判断邻近的8个为止是否有点亮的灯,关闭灯
for(int[] d : dirs){
int nx = x + d[0], ny = y + d[1];
int na = nx + ny, nb = nx - ny;
if(nx < 0 || nx >= n || ny < 0 || ny >= n){
continue;
}
if(set.contains(nx * N + ny)){
// 关闭点亮的灯,并关闭照亮的区域
set.remove(nx * N + ny);
decrement(row, nx); decrement(col, ny);
decrement(left, na); decrement(right, nb);
}
}
}
return ans;
}
void increment(Map<Integer, Integer> map, int key) {
map.put(key, map.getOrDefault(key, 0) + 1);
}
void decrement(Map<Integer, Integer> map, int key) {
if (map.get(key) == 1) {
map.remove(key);
}else{
map.put(key, map.get(key) - 1);
}
}
}
2006. 差的绝对值为 K 的数对数目
给你一个整数数组nums和一个整数 k,请你返回数对 (i, j) 的数目,满足 i < j 且 |nums[i] - nums[j]| == k 。
|x| 的值定义为:
- 如果 x >= 0 ,那么值为 x 。
- 如果 x < 0 ,那么值为 -x 。
提示:
- 1 <= nums.length <= 200
- 1 <= nums[i] <= 100
- 1 <= k <= 99
示例:
输入:nums = [1,2,2,1], k = 1
输出:4
解释:差的绝对值为 1 的数对为:
- [1,2,2,1]
- [1,2,2,1]
- [1,2,2,1]
- [1,2,2,1]
解题思路:使用哈希表,将所有数字出现的次数做出统计,将每一对差值为k的两个数的频数相乘。
public int countKDifference(int[] nums, int k) {
int ans = 0;
int[] cut = new int[101];
// 记录出现的频数
for(int i:nums){
cut[i]++;
}
for(int i = 0;i+k <= 100; i++){
// 将每一个差值为k,出现的次数相乘
ans += cut[i]*cut[i+k];
}
return ans;
}
1447. 最简分数
给你一个整数 n ,请你返回所有 0 到 1 之间(不包括 0 和 1)满足分母小于等于 n 的最简分数 。分数可以以任意顺序返回。
提示:
- 1 <= n <= 100
示例:
输入:n = 4
输出:["1/2","1/3","1/4","2/3","3/4"]
解释:"2/4" 不是最简分数,因为它可以化简为 "1/2" 。
解题思路:暴力法,将所有分母小于n的分数遍历,考虑是不是最简分数,是最简分数则加入结果集中。
public List<String> simplifiedFractions(int n) {
List<String> ans = new ArrayList<>();
for(int i = 2;i <= n;i++){
for(int j = 1;j < i;j++){
// 分子,分母的最小公倍数为1,则为最简分数。
if(gcd(i,j) == 1){
ans.add(j+"/"+i);
}
}
}
return ans;
}
// 求最小公倍数
public int gcd(int a,int b) {
return b != 0 ? gcd(b, a % b):a;
}
1984. 学生分数的最小差值
给你一个下标从 0 开始的整数数组 nums,其中nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。
从数组中选出任意 k 名学生的分数,使这 k 个分数间最高分和最低分的差值达到最小化。返回可能的最小差值 。
示例:
输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
- [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
可能的最小差值是 2
提示:
- 1 <= k <= nums.length <= 1000
- 0 <= nums[i] <= 105
解题思路:对数组排序,然后使用滑动窗口,窗口大小为 k,求得窗口中最大值和最小值的差。
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int ans = Integer.MAX_VALUE;
for (int i = 0; i + k - 1 < nums.length; i++) {
ans = Math.min(ans, nums[i + k - 1] - nums[i]);
}
return ans;
}
1020. 飞地的数量
给你一个大小为 m x n 的二进制矩阵grid,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。
一次移动是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过grid的边界。
返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。
示例:
输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
提示:
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 500
- grid[i][j] 的值为 0 或 1
解题思路:从边界上的 1 开始进行BFS,将访问到的1变成0,最后统计网格中1的数量。
public int numEnclaves(int[][] grid) {
int m = grid.length, n = grid[0].length;
// 遍历边界上的1,进行 BFS
for(int i = 0;i < m;i++){
if(grid[i][0] == 1){
bfs(grid,i,0);
}
if(grid[i][n-1] == 1){
bfs(grid,i,n-1);
}
}
for(int j = 1;j < n;j++){
if(grid[0][j] == 1){
bfs(grid,0,j);
}
if(grid[m-1][j] == 1){
bfs(grid,m-1,j);
}
}
// 最后统计出网格中1的数量
int ans = 0;
for(int i = 0;i < m;i++){
for(int j = 0;j<n;j++){
if(grid[i][j] == 1){
ans++;
}
}
}
return ans;
}
public static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public void bfs(int[][] grid,int i,int j){
// 将遍历到的1变成0
grid[i][j] = 0;
// 向四周扩散
for(int di = 0;di<4;di++){
int newi,newj;
newi = dirs[di][0]+i;
newj = dirs[di][1]+j;
if(newi < 0 || newi >= grid.length ||
newj < 0 || newj >= grid[0].length || grid[newi][newj] == 0){
continue;
}
bfs(grid,newi,newj);
}
}
1189. “气球” 的最大数量
给你一个字符串 text,你需要使用 text 中的字母来拼凑尽可能多的单词 “balloon”(气球)。
字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 “balloon”。
示例:
输入:text = "loonbalxballpoon"
输出:2
提示:
- 1 <= text.length <= 104
- text 全部由小写英文字母组成
解题思路:统计出所有字母的数量,然后再统计balloon单词中各个字母的数量,字母 ‘l’ 和字母 ‘o’ 的数量 /2 ,字母的最小数量就是组成 balloon 单词的个数。
public int maxNumberOfBalloons(String text) {
int[] alphabet = new int[26];
for(int i =0;i<text.length();i++){
alphabet[text.charAt(i)-'a']++;
}
// balloon
int ans = alphabet['a'-'a'];
ans = Math.min(ans,alphabet['b'-'a']);
ans = Math.min(ans,alphabet['n'-'a']);
ans = Math.min(ans,alphabet['l'-'a']/2);
ans = Math.min(ans,alphabet['o'-'a']/2);
return ans;
}
540. 有序数组中的单一元素
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。
示例:
输入: nums = [3,3,7,7,10,11,11]
输出: 10
提示:
- 1 <= nums.length <= 105
- 0 <= nums[i] <= 105
解题思路:要想达到O(log N) 时间复杂度,使用二分查找。初始时,二分查找的左边界是 0,右边界是数组的最大下标。每次取左右边界的平均值 mid 作为待判断的下标,根据 mid 的奇偶性决定和左边或右边的相邻元素比较:
- 如果 mid 是偶数,则比较 nums[mid] 和 nums[mid+1] 是否相等;
- 如果 mid 是奇数,则比较 nums[mid−1] 和 nums[mid] 是否相等。
如果上述比较相邻元素的结果是相等,则 mid < x,调整左边界(left = mid +1),否则 mid ≥ x,调整右边界(right = mid)。调整边界之后继续二分查找,直到确定下标 x 的值。
技巧使用 mid^1,可以根据mid的奇偶性,确定和左边或右边的相邻元素比较。
public int singleNonDuplicate(int[] nums) {
int left = 0;
int right = nums.length - 1;
while(left < right){
int center = left+(right - left)/2;
if(nums[center] == nums[center^1]){
left = center + 1;
}else{
right = center;
}
}
return nums[left];
}
1380. 矩阵中的幸运数
给你一个 m * n 的矩阵,矩阵中的数字各不相同 。请你按任意顺序返回矩阵中的所有幸运数。
幸运数是指矩阵中满足同时下列两个条件的元素:
- 在同一行的所有元素中最小
- 在同一列的所有元素中最大
示例:
输入:matrix = [[1,10,4,2],[9,3,8,7],[15,16,17,12]]
输出:[12]
解释:12 是唯一的幸运数,因为它是其所在行中的最小值,也是所在列中的最大值。
提示:
- m == mat.length
- n == mat[i].length
- 1 <= n, m <= 50
- 1 <= matrix[i][j] <= 105
- 矩阵中的所有元素都是不同的
解题思路:模拟法,先找到每一行中最小的元素,再判断是不是当前列的最大元素。
public List<Integer> luckyNumbers (int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
List<Integer> ans = new ArrayList<Integer>();
// 同一行元素最小,同一列元素最大
for(int i = 0;i< m;i++) {
int min = 0;
// 找到同一行中最小元素的位置
for(int j = 0;j < n;j++) {
if(matrix[i][j] < matrix[i][min]) {
min = j;
}
}
// 判断min是不是同一列最大的元素
boolean f = true;
for(int j = 0;j < m; j++) {
if(matrix[j][min] > matrix[i][min]) {
f = false;
break;
}
}
// 是则加入集合
if(f) {
ans.add(matrix[i][min]);
}
}
return ans;
}