- 总结&&模版
- 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.length0 <= k <= 10^40 <= lists[i].length <= 500-10^4 <= lists[i][j] <= 10^4lists[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 <= 1051 <= maxOperations, nums[i] <= 109class 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是最后一堆,不用分割的,已经成一堆//转化为 求的是最少多少次,能使得球的个数小于等于midres += (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 == nnums[i]是 正整数 ,其中0 <= i < nabs(nums[i] - nums[i+1]) <= 1,其中0 <= i < n-1nums中所有元素之和不超过maxSumnums[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 <= 1090 <= index < nusing 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.lengthn == matrix[i].length1 <= m, n <= 100-104 <= matrix[i][j], target <= 104class 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^4nums中的每个值都 独一无二- 题目数据保证
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.length1 <= n <= 5000-5000 <= nums[i] <= 5000nums中的所有整数 互不相同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];}};
