- 总结&&模版
- 23. 合并K个升序链表">23. 合并K个升序链表
- 1760. 袋子里最少数目的球">1760. 袋子里最少数目的球
- 5219. 每个小孩最多能分到多少糖果">5219. 每个小孩最多能分到多少糖果
- 395. 至少有 K 个重复字符的最长子串">395. 至少有 K 个重复字符的最长子串
- 5711. 有界数组中指定下标处的最大值">5711. 有界数组中指定下标处的最大值
- 74. 搜索二维矩阵">74. 搜索二维矩阵
- 33. 搜索旋转排序数组">33. 搜索旋转排序数组
- 81. 搜索旋转排序数组 II">81. 搜索旋转排序数组 II
- 153. 寻找旋转排序数组中的最小值">153. 寻找旋转排序数组中的最小值
- 154. 寻找旋转排序数组中的最小值 II">154. 寻找旋转排序数组中的最小值 II
总结&&模版
l的初始值由理论上的最小值决定,r的初始值可以由数值范围上界决定,也可以由数组最大值决定。
至于while结束的判定条件是用 < 还是<=,这点可以确定长期使用一种统一的方式。
<结束的条件就是left == right ,搜索的区间是[left,right),注意在更换上下界的时候不能都+ -
<=结束的条件就是left = right + 1,搜索区间是[left, right], 更换上下界的时候应当同时+ -
更换上下界时的区别也就是当前的值是已经搜索过还是没有搜索过。
建议还是统一用<的方式,判断二分左端点还是右端点再写,不容易出错。
二分查找的是一个值,他的左边满足某种性质,右边不满足该性质。
两个模版的差别再说找的是夹在中间的哪一个具体值。
//查找左端点(红色,端点的左边都满足红色性质,check红色性质)
int SL(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; //需要+1 防止死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
//查找右端点(绿色,端点的右边都满足绿色性质,check绿色性质)
int SR(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return r;
// 最后r=l
}
23. 合并K个升序链表
难度困难1142
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* a, ListNode* b){
if((!a) || (!b)) return a ? a : b;
ListNode* nhead = new ListNode(-1);
ListNode* pre = nhead;
ListNode* ap = a, *bp = b;
while(ap && bp){
if(ap->val < bp->val){
pre->next = ap;
ap = ap->next;
}else{
pre->next = bp;
bp = bp->next;
}
pre = pre->next;
}
pre->next = ap == nullptr ? bp : ap;
return nhead->next;
}
ListNode* merge(vector<ListNode*>& lists, int l, int r){
//只有一个元素的情况
if(l == r) return lists[l];
if(l > r) return nullptr;
int mid = l + r >> 1;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
return merge(lists, 0, lists.size() - 1);
}
};
1760. 袋子里最少数目的球
难度中等14
给你一个整数数组 nums
,其中 nums[i]
表示第 i
个袋子里球的数目。同时给你一个整数 maxOperations
。
你可以进行如下操作至多 maxOperations
次:
- 选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有正整数 个球。
- 比方说,一个袋子里有
5
个球,你可以把它们分到两个新袋子里,分别有1
个和4
个球,或者分别有2
个和3
个球。
- 比方说,一个袋子里有
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。
示例 1:
输入:nums = [9], maxOperations = 2
输出:3
解释:
- 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。
- 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。
装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。
示例 2:
输入:nums = [2,4,8,2], maxOperations = 4
输出:2
解释:
- 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。
装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。
示例 3:
输入:nums = [7,17], maxOperations = 2
输出:7
提示:
1 <= nums.length <= 105
1 <= maxOperations, nums[i] <= 109
class Solution {
typedef long long ll;
public:
bool check(int mid, vector<int>& nums, int maxOperations){
int res = 0;
for(auto& cur : nums){
//最小化最大值问题
//原问题:最多分maxOperations次,使得最终每堆个数不超过mid个
//cur / mid 上取整,等于(cur + mid - 1) / mid 下取整
//-1是最后一堆,不用分割的,已经成一堆
//转化为 求的是最少多少次,能使得球的个数小于等于mid
res += (cur + mid - 1) / mid - 1;
if(res > maxOperations) return false;
}
return true;
}
int minimumSize(vector<int>& nums, int maxOperations) {
//定制左右边界
ll l = 1, r = 1e9;
ll ans = 0;
while(l < r){
int mid = l + r >> 1;
//左边区间及本身满足
if(check(mid, nums, maxOperations)){
r = mid;
}else{
l = mid + 1;
}
}
return r;
}
};
5219. 每个小孩最多能分到多少糖果
395. 至少有 K 个重复字符的最长子串
难度中等430
给你一个字符串 s
和一个整数 k
,请你找出 s
中的最长子串, 要求该子串中的每一字符出现次数都不少于 k
。返回这一子串的长度。
示例 1:
输入:s = “aaabb”, k = 3
输出:3
解释:最长子串为 “aaa” ,其中 ‘a’ 重复了 3 次。
示例 2:
输入:s = “ababbc”, k = 2
输出:5
解释:最长子串为 “ababb” ,其中 ‘a’ 重复了 2 次, ‘b’ 重复了 3 次。
对于字符串 ss,如果存在某个字符 \textit{ch}ch,它的出现次数大于 00 且小于 kk,则任何包含 \textit{ch}ch 的子串都不可能满足要求。也就是说,我们将字符串按照 \textit{ch}ch 切分成若干段,则满足要求的最长子串一定出现在某个被切分的段内,而不能跨越一个或多个段。因此,可以考虑分治的方式求解本题。
class Solution {
public:
int check(const string s, int l, int r, int k){
//计数
vector<int> cnt(26, 0);
for(int i = l; i <= r; ++ i){
cnt[s[i] - 'a'] ++;
}
//得到当前二分的分界点
char split = 0;
for(int i = 0; i < 26; ++ i){
//不满足题目的条件
if(cnt[i] > 0 && cnt[i] < k){
split = 'a' + i;
break;
}
}
//没找到 那么整个都满足
if(split == 0){
return r - l + 1;
}
int i = l;
int res = 0;
while(i <= r){
//找到分界点的第一段 这段是不满足题意的
while(i <= r && s[i] == split){
i ++;
}
//临界情况 到最后了
if(i > r){
break;
}
//从现在开始满足直到下一个该数
int start = i;
while(i <= r && s[i] != split){
i ++;
}
int len = check(s, start, i - 1, k);
res = max(res, len);
}
return res;
}
int longestSubstring(string s, int k) {
int n = s.size();
return check(s, 0, n - 1, k);
}
};
5711. 有界数组中指定下标处的最大值
难度中等5
给你三个正整数 n
、index
和 maxSum
。你需要构造一个同时满足下述所有条件的数组 nums
(下标 从 0 开始 计数):
nums.length == n
nums[i]
是 正整数 ,其中0 <= i < n
abs(nums[i] - nums[i+1]) <= 1
,其中0 <= i < n-1
nums
中所有元素之和不超过maxSum
nums[index]
的值被 最大化
返回你所构造的数组中的 nums[index]
。
注意:abs(x)
等于 x
的前提是 x >= 0
;否则,abs(x)
等于 -x
。
示例 1:
输入:n = 4, index = 2, maxSum = 6
输出:2
解释:数组 [1,1,2,1] 和 [1,2,2,1] 满足所有条件。不存在其他在指定下标处具有更大值的有效数组。
示例 2:
输入:n = 6, index = 1, maxSum = 10
输出:3
提示:
1 <= n <= maxSum <= 109
0 <= index < n
using ll = long long;
class Solution {
public:
bool check(ll mid, ll n, ll index, ll maxSum){
//mid表示的是index处的数字,我们要算出这个位置的最大数字
ll res = 0;
//表示左右分别能放的数字个数 这里的index不是坐标 是数
ll lnum = index + 1, rnum = n - index;
//算左边和 包括mid本身
//如果能完美递减数列那么就放进去 等差数列公式计算/求和公式 左边mid + 1项
if(mid - lnum >= 1){
res += lnum * (mid + mid - lnum + 1) / 2;
}else{
//分类加起来
res += mid * (mid + 1) / 2 + lnum - mid;
}
//计算右边
if(mid - rnum >= 1){
res += rnum * (mid + mid - rnum + 1) / 2;
}else{
res += mid * (mid+ 1) / 2 + rnum - mid;
}
res -= mid;
return res <= maxSum;
}
int maxValue(int n, int index, int maxSum) {
ll l = 1, r = 1e9;
while(l < r){
ll mid = l + r + 1 >> 1;
if(check(mid, n, index, maxSum)){
l = mid;
}else{
r = mid - 1;
}
}
return l;
}
};
74. 搜索二维矩阵
难度中等391
编写一个高效的算法来判断m x n
矩阵中,是否存在一个目标值。该矩阵具有如下特性:每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 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:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size();
if(!n) return false;
int m = matrix[0].size();
if(!m) return false;
int l = 0, r = n * m - 1;
while(l <= r){
int mid = l + r >> 1;
int row = mid / m, col = mid % m;
if(target == matrix[row][col]){
return true;
}else if(target > matrix[row][col]){
l = mid + 1;
}else{
r = mid - 1;
}
}
return false;
}
};
33. 搜索旋转排序数组
难度中等1288
整数数组nums
按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums
在预先未知的某个下标k
(0 <= k < nums.length
)上进行了 旋转,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如,[0,1,2,4,5,6,7]
在下标3
处经旋转后可能变为[4,5,6,7,0,1,2]
。
给你 旋转后 的数组nums
和一个整数target
,如果nums
中存在这个目标值target
,则返回它的下标,否则返回-1
。
示例 1:
输入:nums = [4,5,6,7,0,1,2]
, target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2]
, target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums
中的每个值都 独一无二- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -10^4 <= target <= 10^4
进阶:你可以设计一个时间复杂度为O(log n)
的解决方案吗?class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
if(!n) return -1;
int l = 0, r = n - 1;
while(l <= r){
int mid = l + r >> 1;
if(nums[mid] == target){
return mid;
}
if(nums[0] <= nums[mid]){
if(target >= nums[0] && nums[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}else{
if(target <= nums[n - 1] && nums[mid] < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
}
return -1;
}
};
81. 搜索旋转排序数组 II
难度中等367
已知存在一个按非降序排列的整数数组nums
,数组中的值不必互不相同。
在传递给函数之前,nums
在预先未知的某个下标k
(0 <= k < nums.length
)上进行了 旋转 ,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如,[0,1,2,4,4,4,5,6,6,7]
在下标5
处经旋转后可能变为[4,5,6,6,7,0,1,2,4,4]
。
给你 旋转后 的数组nums
和一个整数target
,请你编写一个函数来判断给定的目标值是否存在于数组中。如果nums
中存在这个目标值target
,则返回true
,否则返回false
。
示例 1:
输入:nums = [2,5,6,0,0,1,2]
, target = 0
输出:true
示例 2:
输入:nums = [2,5,6,0,0,1,2]
, target = 3
输出:false
提示:
1 <= nums.length <= 5000
-104 <= nums[i] <= 104
- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -104 <= target <= 104
进阶:这是 搜索旋转排序数组 的延伸题目,本题中的
nums
可能包含重复元素。这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?
class Solution {
public:
bool search(vector<int>& nums, int target) {
int n = nums.size();
if(n == 1) return nums[0] == target;
int l = 0, r = n - 1;
while(l <= r){
//移动边界到不重复的地方
while(l < r && nums[l] == nums[l + 1]) ++ l;
while(l < r && nums[r] == nums[r - 1]) -- r;
int mid = l + r >> 1;
if(nums[mid] == target) return true;
if(nums[l] <= nums[mid]){
if(target >= nums[l] && nums[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}else{
if(target <= nums[n - 1] && nums[mid] < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
}
return false;
}
};
比较好的解决重复元素的方法
class Solution {
public:
bool search(vector<int>& nums, int target) {
int n = nums.size();
if(n == 1) return nums[0] == target;
int l = 0, r = n - 1;
while(l <= r){
int mid = l + r >> 1;
if(nums[mid] == target) return true;
if(nums[l] == nums[mid] && nums[mid] == nums[r]){
l ++;
r --;
}
else if(nums[l] <= nums[mid]){
if(target >= nums[l] && nums[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}else{
if(target <= nums[n - 1] && nums[mid] < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
}
return false;
}
};
153. 寻找旋转排序数组中的最小值
难度中等427
已知一个长度为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
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
示例 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] ,旋转 4 次得到输入数组。
示例 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(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] <= nums[r]){
r = mid;
}else if(nums[mid] > nums[r]){
l = mid + 1;
}
}
return nums[l];
}
};
其实就是二分的两种情况,一种是左包含,一种是右包含class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= nums[l]){
l = mid;
}else if(nums[mid] < nums[l]){
r = mid - 1;
}
}
return nums[l];
}
};
154. 寻找旋转排序数组中的最小值 II
难度困难277
就是带上重复元素的版本
与之前的解决方法类似class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] == nums[l] && nums[mid] == nums[r]){
++ l;
-- r;
}
else if(nums[mid] <= nums[r]){
r = mid;
}else{
l = mid + 1;
}
}
return nums[l];
}
};