题目列表
练习
410. 分割数组的最大值
875. 爱吃香蕉的珂珂
LCP 12. 小张刷题计划
1482. 制作 m 束花所需的最少天数
1011. 在 D 天内送达包裹的能力
240. 搜索二维矩阵 II
74. 搜索二维矩阵
162. 寻找峰值
349. 两个数组的交集
611. 有效三角形的个数
1027. 最长等差数列
2602. 使数组元素全部相等的最少操作次数
378. 有序矩阵中第 K 小的元素
153. 寻找旋转排序数组中的最小值
1004. 最大连续1的个数 III
2024.04.30
410. 分割数组的最大值
题目
给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。
设计一个算法使得这 k 个子数组各自和的最大值最小。
示例 1:
输入:nums = [7,2,5,10,8], k = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
示例 2:
输入:nums = [1,2,3,4,5], k = 2
输出:9
示例 3:
输入:nums = [1,4,4], k = 3
输出:4
提示:
- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 106
- 1 <= k <= min(50, nums.length)
思路
-
动态规划
状态转移方程:dp[i][j] = max(dp[l][j-1], sumPre[i] - sumPre[l]);
dp[i][j]
表示以下标i结尾,分割成j个子数组的和的最大值最小值。
初始化:因为求最小,所以填充最大。如果k=1就是数组不变,那就是所有元素和
遍历:按照从左到右填表
-
二分查找
思路本质就是去找到满足条件的最小值,能二分的基础是,如果x满足条件,那么比x(大OR小)的也满足条件
核心点有三个 -
左边界是啥
-
右边界是啥
-
能不能快速验证(各种题的核心区别)
答案
// 动态规划
class Solution {
public int splitArray(int[] nums, int k) {
int length = nums.length;
// k+1是因为k是个数,不是下标,有个对应关系,否则处理时候需要-1费脑子
int[][] dp = new int[length][k + 1];
// 初始化
for (int i = 0; i < length; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
dp[0][1] = nums[0];
int[] sumPre = new int[length];
sumPre[0] = nums[0];
// 前缀和数组为了快速计算某一个range的和
for (int i = 1; i < length; i++) {
sumPre[i] = sumPre[i - 1] + nums[i];
}
for (int i = 1; i < length; i++) {
dp[i][1] = sumPre[i];
}
// dp[i][j] = max(dp[l][j-1], sumPre[i] - sumPre[l]);
// 有限固定k,即按照上图,从上往下
for (int i = 2; i < k + 1; i++) {
// 从上往下,所以j=i-1
for (int j = i - 1; j < length; j++) {
// 遍历所有的可能,i - 2是因为至少要分割成i段,除去最后的一个,和自身要有的一段,剩下的每段至少有一个元素
// 举个栗子,i = 5,表示要分割成5段,除去l < j最后一个元素,自己这一段,那么至少还要有三段,也就是三个元素,这个时候l = 5-2 = 3
// 减去的2就是自己这一段和最后一个元素那一段
for (int l = i - 2; l < j; l++) {
dp[j][i] = Math.min(dp[j][i], Math.max(dp[l][i - 1], sumPre[j] - sumPre[l]));
}
}
}
return dp[length - 1][k];
}
}
// 二分查找
class Solution {
public int splitArray(int[] nums, int k) {
int left = 0, right = 0;
for (int i = 0; i < nums.length; i++) {
// 因为是求最大值的最小,所以left最小也得是元素中的最大值,即按照元素个数分割
left = Math.max(left, nums[i]);
// 最大值就是分割成一个数组,那就是所有数字的和
right += nums[i];
}
// 二分
while (left <= right) {
int middle = left + (right - left)/2;
if (check(nums, middle, k)) {
// 满足要找所有满足的值的最小值,所以满足之后继续向左
right = middle - 1;
} else {
left = middle + 1;
}
}
// 最后两个指针重合的时候,如果重合在要找的值上,则left不动。
// 重合在要找的值左边,left要右移一位
// 不会重合在要找的值右边,考虑两个元素时候,只有两种情况
// 两个都满足,且左边是最低要求。左边不满足,右边满足。
// 两个都满足,且左边是最低要求。(为什么左边最低要求,因为如果满足,左边是不动的,右边会动,假设第一次重合在一个不是最小的满足值,则right变小,left不变。最坏的情况就是上来left就是满足的,由于left一直不变,所以一定能被保有) middle就是left(left + (left + 1))/2 == left,所以,不会重合在值右边
// 左边不满足,右边满足。middle是left,所以right不变
return left;
}
// 每道题不同的核心点
public boolean check(int[] nums, int x, int m) {
int cnt = 1;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
// x是预期的目标值(数组求和的最大值),如果超了,那就加一。而且由于这个元素没算,所以要重新赋值给sum
if (sum > x) {
sum = nums[i];
cnt++;
}
}
// 看看满不满足m个的条件
return cnt <= m;
}
}
875. 爱吃香蕉的珂珂
题目
珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。
珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。
示例 1:
输入:piles = [3,6,7,11], h = 8
输出:4
示例 2:
输入:piles = [30,11,23,4,20], h = 5
输出:30
示例 3:
输入:piles = [30,11,23,4,20], h = 6
输出:23
提示:
- 1 <= piles.length <= 104
- piles.length <= h <= 109
- 1 <= piles[i] <= 109
思路
同上一题的二分
答案
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int right = 0;
for (int i = 0; i < piles.length; i++) {
// 最快就是每一堆吃一次
right = Math.max(right, piles[i]);
}
// 最慢就1个一个吃
int left = 1;
while (left <= right) {
// 放越界,其实没必要
int middle = left + (right - left) / 2;
if (check(piles, h, middle)) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
public boolean check(int[] nums, int h, int k) {
long cnt = 0;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
// 到底吃几个小时,如果num <= k那就是1小时,所以num+k -1,减一防止num=k时候变成2
cnt += (num + k - 1) / k;
}
return cnt <= h;
}
}
LCP 12. 小张刷题计划
题目
为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。
在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。
我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。
示例 1:
输入:time = [1,2,3,3], m = 2
输出:3
解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。
示例 2:
输入:time = [999,999,999], m = 4
输出:0
解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。
限制:
- 1 <= time.length <= 10^5
- 1 <= time[i] <= 10000
- 1 <= m <= 1000
思路
同之前
答案
class Solution {
public int minTime(int[] time, int m) {
int left = 0, right = 0;
for (int t : time) {
// 最坏就每天只能1,所以要求和
right += t;
}
while (left <= right) {
int middle = (left + right) / 2;
if (check(time, m, middle)) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
public boolean check(int[] nums, int m, int k) {
int cnt = 1;
int max = 0;
int sum = 0;
for (int num : nums) {
sum += num;
max = Math.max(max, num);
// 这个体现,每天的一次求助。因为过程中不可能直到最大值,所以最后算
if (sum > max + k) {
cnt++;
// 没算上,所以重新赋值
sum = num;
// 没算上,所以重新赋值
max = num;
}
}
return cnt <= m;
}
}
1482. 制作 m 束花所需的最少天数
题目
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例 1:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3
示例 2:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。
示例 3:
输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。
示例 4:
输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束
示例 5:
输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9
提示:
- bloomDay.length == n
- 1 <= n <= 10^5
- 1 <= bloomDay[i] <= 10^9
- 1 <= m <= 10^6
- 1 <= k <= n
思路
同之前,细节写在代码里
答案
class Solution {
public int minDays(int[] bloomDay, int m, int k) {
if (bloomDay.length / k < m) {
return -1;
}
int left = Integer.MAX_VALUE, right = 0, max = 0;
for (int i : bloomDay) {
// 最少是每个花期都一样,都是最个数
left = Math.min(left, i);
// 最大就是等到最长那个
max = Math.max(i, max);
}
right = max;
while (left <= right) {
int middle = (left + right) / 2;
if (check(bloomDay, m, k, middle)) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 这里最后left可能没找到
return left > max ? -1 : left;
}
public boolean check(int[] bloomDay, int m, int k, int x) {
int cnt = 0;
int c = 0;
for (int i : bloomDay) {
// x是天数,进入if说明花开了
if (i <= x) {
c++;
// 连续数量满足k,就形成了一束花
if (c == k) {
c = 0;
cnt++;
if (cnt >= m) {
return true;
}
}
} else {
// 没进入重置连续数量
c = 0;
}
}
return cnt >= m;
}
}
1011. 在 D 天内送达包裹的能力
题目
传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], days = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
提示:
- 1 <= days <= weights.length <= 5 * 104
- 1 <= weights[i] <= 500
思路
同之前
答案
class Solution {
public int shipWithinDays(int[] weights, int days) {
int max = 0, sum = 0;
for (int i : weights) {
max = Math.max(max, i);
sum += i;
}
// 要想完成传送,运输能力一定得能运最大包裹
// 最快就是一次全运完,所以求和
int left = max, right = sum;
while (left <= right) {
int middle = (left + right) / 2;
if (check(weights, days, middle)) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
public boolean check(int[] weights, int days, int weight) {
int cnt = 1;
int sum = 0;
for (int i : weights) {
sum += i;
// 和第一题一样
if (sum > weight) {
cnt++;
sum = i;
}
}
return cnt <= days;
}
}
240. 搜索二维矩阵 II
题目
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例 1:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= n, m <= 300
- -109 <= matrix[i][j] <= 109
- 每行的所有元素从左到右升序排列
- 每列的所有元素从上到下升序排列
- -109 <= target <= 109
思路
一列一列搜,加一点截断优化
答案
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
int mLeft = 0, mRight = n - 1;
for (int i = 0; i < n; i++) {
// 截断优化
if (matrix[0][i] > target || matrix[m-1][i] < target) {
continue;
}
mLeft = 0;
mRight = m - 1;
while (mLeft <= mRight) {
int middle = (mLeft + mRight) / 2;
int value = matrix[middle][i];
if (target == value) {
return true;
} else if (target > value) {
mLeft = middle + 1;
} else {
mRight = middle - 1;
}
}
}
return false;
}
}
74. 搜索二维矩阵
题目
给你一个满足下述两条属性的 m x n 整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 100
- -104 <= matrix[i][j], target <= 104
思路
同上,区别是,这个是连续的,所以先横着或者竖着搜,在交叉搜
答案
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
int left = 0, right = m - 1;
// 先竖着找
while (left <= right) {
int middle = (left + right) / 2;
int element = matrix[middle][0];
if (element == target) {
return true;
} else if (element < target) {
left = middle + 1;
} else {
right = middle - 1;
}
}
// 找不到会找到第一个比目标值大的,所以这里做一个减一。找到就直接返回了
int mIndex = left - 1;
if (mIndex < 0 || mIndex > m - 1) {
return false;
}
left = 0;
right = n - 1;
while (left <= right) {
int middle = (left + right) / 2;
int element = matrix[mIndex][middle];
if (element == target) {
return true;
} else if (element < target) {
left = middle + 1;
} else {
right = middle - 1;
}
}
return false;
}
}
162. 寻找峰值
题目
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
示例 1:
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
提示:
- 1 <= nums.length <= 1000
- -231 <= nums[i] <= 231 - 1
- 对于所有有效的 i 都有 nums[i] != nums[i + 1]
思路
实际上是找上升沿的点,如果nums[middle] > nums[middle + 1]
则表示右侧是下降沿,所以right过来
答案
class Solution {
public int findPeakElement(int[] nums) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = left + (right - left) / 2;
// 兜底,因为认为边界为负无穷,所以认为middle大于右边
if (middle == nums.length - 1 || nums[middle] > nums[middle + 1]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
}
349. 两个数组的交集
题目
给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
提示:
- 1 <= nums1.length, nums2.length <= 1000
- 0 <= nums1[i], nums2[i] <= 1000
思路
排序双指针就不说了,直接排序+二分
答案
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
// 为了在多的里面二分,可以快一点
if (m < n) {
return intersection(nums2, nums1);
}
sorted(nums1, 0, nums1.length - 1);
sorted(nums2, 0, nums2.length - 1);
int[] result = new int[n];
int index = 0;
int left = 0;
for (int i = 0; i < n; i++) {
int current = nums2[i];
int right = nums1.length - 1;
// 提前搜索结束
if (left > right) {
break;
}
boolean find = false;
while (left <= right) {
int middle = (left + right) / 2;
int element = nums1[middle];
// 这里是为了避免重复元素,找到最右边的那个
if (current == element) {
left = middle + 1;
// 为了标记是否找到了,并且避免重复
find = true;
} else if (current < element) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 这里是为了避免重复元素
if (find && nums1[right] == current) {
result[index++] = current;
left = right + 1;
}
}
return Arrays.copyOf(result, index);
}
// 锻炼下快排
public void sorted(int[] nums, int left, int right) {
if (left > right) {
return;
}
int min = left, mid = min + 1, max = right;
int target = nums[min];
while (mid <= max) {
int element = nums[mid];
if (element < target) {
swap(nums, min++, mid++);
} else if (element == target) {
mid++;
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
public void swap(int[] nums, int m, int n) {
int t = nums[m];
nums[m] = nums[n];
nums[n] = t;
}
}
611. 有效三角形的个数
题目
给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。
示例 1:
输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3
示例 2:
输入: nums = [4,2,3,4]
输出: 4
提示:
- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 1000
思路
- 整体思路是拿着两个边,找满足条件的第三个边的数量
- 在找第三个边的时候,可以通过排序+二分加快查找速度
答案
class Solution {
public int triangleNumber(int[] nums) {
if (nums.length < 3) {
return 0;
}
// 排序
sorted(nums, 0, nums.length - 1);
int result = 0;
for (int i = 0; i < nums.length; i++) {
// 第一个边
int first = nums[i];
// 不重复
for (int j = i + 1; j < nums.length - 1; j++) {
// 第二个边
int second = nums[j];
// 找第三个边
int left = j + 1;
int right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
int current = nums[middle];
// 找到满足条件的最大边长,这里已经满足,所以继续找能满足的,所以往大了找
if (first + second > current) {
left = middle + 1;
} else {
right = middle - 1;
}
}
result += (right - j);
}
}
return result;
}
// 练习下排序
public void sorted(int[] nums, int left, int right) {
if (left > right) {
return;
}
int element = nums[left];
int min = left, mid = min + 1, max = right;
while (mid <= max) {
int current = nums[mid];
if (current < element) {
swap(nums, min++, mid++);
} else if (current == element) {
mid++;
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
public void swap(int[] nums, int origin, int target) {
int t = nums[origin];
nums[origin] = nums[target];
nums[target] = t;
}
}
1027. 最长等差数列
题目
思路
答案
2602. 使数组元素全部相等的最少操作次数
题目
给你一个正整数数组 nums 。
同时给你一个长度为 m 的整数数组 queries 。第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:
- 将数组里一个元素 增大 或者 减小 1 。
请你返回一个长度为 m 的数组 answer ,其中 answer[i]是将 nums 中所有元素变成 queries[i] 的 最少 操作次数。
注意,每次查询后,数组变回最开始的值。
示例 1:
输入:nums = [3,1,6,8], queries = [1,5]
输出:[14,10]
解释:第一个查询,我们可以执行以下操作:
-将 nums[0] 减小 2 次,nums = [1,1,6,8] 。
-将 nums[2] 减小 5 次,nums = [1,1,1,8] 。
-将 nums[3] 减小 7 次,nums = [1,1,1,1] 。
第一个查询的总操作次数为 2 + 5 + 7 = 14 。
第二个查询,我们可以执行以下操作:
-将 nums[0] 增大 2 次,nums = [5,1,6,8] 。
-将 nums[1] 增大 4 次,nums = [5,5,6,8] 。
-将 nums[2] 减小 1 次,nums = [5,5,5,8] 。
-将 nums[3] 减小 3 次,nums = [5,5,5,5] 。
第二个查询的总操作次数为 2 + 4 + 1 + 3 = 10 。
示例 2:
输入:nums = [2,9,6,3], queries = [10]
输出:[20]
解释:我们可以将数组中所有元素都增大到 10 ,总操作次数为 8 + 1 + 4 + 7 = 20 。
提示:
- n == nums.length
- m == queries.length
- 1 <= n, m <= 105
- 1 <= nums[i], queries[i] <= 109
思路
- 排序,然后用queries中的元素挨个找,然后计算差值
答案
class Solution {
public List<Long> minOperations(int[] nums, int[] queries) {
sorted(nums, 0, nums.length - 1);
long[] sums = new long[nums.length + 1];
long sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
sums[i + 1] = sum;
}
List<Long> result = new ArrayList<>(queries.length);
for (int query : queries) {
long r = 0;
int index = findMaxMin(nums, query);
// 小于部分的和
r = (long) index * query - sums[index];
// 大于等于部分的和
r += sums[nums.length] - sums[index] - (long)(nums.length - index) * query;
result.add(r);
}
return result;
}
// 找到大于和小于目标值的分界线,返回第一个大于目标值的数
public int findMaxMin(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
int current = nums[middle];
if (target == current) {
return middle;
} else if (target < current) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
// 锻炼排序
public void sorted(int[] nums, int left, int right) {
if (left > right) {
return;
}
int element = nums[left];
int min = left, mid = min + 1, max = right;
while (mid <= max) {
int target = nums[mid];
if (target == element) {
mid++;
} else if (element > target) {
swap(nums, min++, mid++);
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
public void swap(int[] nums, int o, int t) {
int temp = nums[o];
nums[o] = nums[t];
nums[t] = temp;
}
}
378. 有序矩阵中第 K 小的元素
题目
给你一个 n x n
矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2)
的解决方案。
示例 1:
输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
示例 2:
输入:matrix = [[-5]], k = 1
输出:-5
提示:
- n == matrix.length
- n == matrix[i].length
- 1 <= n <= 300
- -109 <= matrix[i][j] <= 109
- 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
- 1 <= k <= n2
进阶:
- 你能否用一个恒定的内存(即 O(1) 内存复杂度)来解决这个问题?
- 你能在 O(n) 的时间复杂度下解决这个问题吗?这个方法对于面试来说可能太超前了,但是你会发现阅读这篇文章( this paper )很有趣。
思路
答案
class Solution {
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length;
int left = matrix[0][0];
int right = matrix[n - 1][n - 1];
while (left <= right) {
int middle = left + (right - left) / 2;
// 这里不直接返回是因为有可能找到多个满足的。比如示例1,13、14都满足,但是14不在矩阵里
if (check(matrix, k, middle, n) == 0) {
right = middle - 1;
} else if (check(matrix, k, middle, n) < 0) {
// 不满足就调大一点。不满足说明middle太小了,调大点
left = middle + 1;
} else {
right = middle - 1;
}
}
return left;
}
// 看下如果结果是middle能不能满足条件
public int check(int[][] matrix, int k, int mid, int n) {
int i = 0;
int j = n - 1;
int num = 0;
while (i < n && j >= 0) {
if (matrix[i][j] <= mid) {
num += j + 1;
i++;
} else {
j--;
}
}
return num == k ? 0 : num < k ? -1 : 1;
}
}
153. 寻找旋转排序数组中的最小值
题目
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
- 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
- n == nums.length
- 1 <= n <= 5000
- -5000 <= nums[i] <= 5000
- nums 中的所有整数 互不相同
- nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转
思路
答案
class Solution {
public int findMin(int[] nums) {
int n = nums.length - 1;
int left = 0, right = n;
while (left < right) {
int middle = (left + right) >> 1;
if (nums[middle] < nums[right]) {
right = middle;
} else {
left = middle + 1;
}
}
return nums[left];
}
}
1004. 最大连续1的个数 III
题目
给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
- 1 <= nums.length <= 105
- nums[i] 不是 0 就是 1
- 0 <= k <= nums.length
思路
前缀和统计0的数量
只要范围内0的数量小于k就是长度
因为不知道以哪个结尾,所以要遍历下
答案
class Solution {
public int longestOnes(int[] nums, int k) {
int n = nums.length;
int[] sums = new int[n + 1];
int sum = 0;
for (int i = 0; i < n; i++) {
sum += (1 - nums[i]);
sums[i + 1] = sum;
}
int result = 0;
for (int r = 0; r < n; r++) {
int left = 0;
int right = r;
while (left <= right) {
int middle = (left + right) >> 1;
// 二分找到合适的位置
if (sums[r + 1] - sums[middle] > k) {
left = middle + 1;
} else {
right = middle - 1;
}
}
result = Math.max(result, r - left + 1);
}
return result;
}
}