二分查找与双指针
常见的算法模板
查找一个数的模板框架
思维框架:
1.定义搜索区间
2.根据搜索区间定义循环结束条件
3.取中间元素和目标元素对比
4.根据比较结果收缩区间,舍弃非法解
数组中有无重复元素,两种情况下解法有区别,判断条件特别对待
二分查找
public int binarySearch(int[] nums, int target) {
// 左右都闭合的区间 [l, r]
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
if (nums[mid] < target)
// 搜索区间变为 [mid+1, right]
left = mid + 1;
if (nums[mid] > target)
// 搜索区间变为 [left, mid - 1]
right = mid - 1;
}
return -1;
}
寻找数组最左边满足条件的值
public int binarySearchLeft(int[] nums, int target) {
// 搜索区间为 [left, right]
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
}
if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
}
if (nums[mid] == target) {
// 收缩右边界,寻找新备胎,没找到,最后left收缩左边界也会返回此备胎处
right = mid - 1;
}
}
// 检查是否越界
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
寻找数组最右边满足条件的值
public int binarySearchRight(int[] nums, int target) {
// 搜索区间为 [left, right]
int left = 0
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
}
if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
}
if (nums[mid] == target) {
// 收缩左边界
left = mid + 1;
}
}
// 检查是否越界
if (right < 0 || nums[right] != target)
return -1;
return right;
}
二维数组查找
题目
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
解法:从左下角元素开始寻找
模板如下:
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m = len(matrix)
if m == 0:
return False
n = len(matrix[0])
x = m - 1
y = 0
while x >= 0 and y < n:
if matrix[x][y] > target:
x -= 1
elif matrix[x][y] < target:
y += 1
else:
return True
return False
二分查找练习题
857.爱吃香蕉的珂珂
珂珂喜欢吃香蕉。这里有 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
300.最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
354.俄罗斯套娃信封
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
示例 1:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1
17.08马戏团人塔
有个马戏团正在设计叠罗汉的表演节目,一个人要站在另一人的肩膀上。出于实际和美观的考虑,在上面的人要比下面的人矮一点且轻一点。已知马戏团每个人的身高和体重,请编写代码计算叠罗汉最多能叠几个人。
示例:
输入:height = [65,70,56,75,60,68] weight = [100,150,90,190,95,110]
输出:6
解释:从上往下数,叠罗汉最多能叠 6 层:(56,90), (60,95), (65,100), (68,110), (70,150), (75,190)
双指针解题模板
包含三种类型:快慢指针–两指针步长不同
左右端点指针–两指针分别头尾
固定间距指针–两指针步长间距相等
1.快慢指针
l = 0
r = 0
while 没有遍历完
if 一定条件
l += 1
r += 1
return 合适的值
2.左右端点指针
l = 0
r = n - 1
while l < r
if 找到了
return 找到的值
if 一定条件1
l += 1
else if 一定条件2
r -= 1
return 没找到
3.固定间距指针
l = 0
r = k
while 没有遍历完
自定义逻辑
l += 1
r += 1
return 合适的值
快慢指针
通常用来判断链表是否有环
快慢指针练习题
80.删除排序数组中的重复项
删除排序数组中的重复项,使得相同数字最多出现K次。例如最多出现两次的示例情况
示例 1:
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。
最多出现一次的代码模板:
// 时间复杂度O(n) 空间复杂度O(1)
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int index = 0;
for(int i = 1;i < nums.length;i++){
if(nums[index] != nums[i]){
index++;
nums[index] = nums[i];
}
}
return index + 1;
}
最多出现两次的代码模板:
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null) return 0;
if(nums.length<=2) return nums.length;
int index =1;
for(int i =2;i<nums.length;i++){
if(nums[i]!=nums[index-1]){
index++;
nums[index]=nums[i];
}
}
return index+1;
}
}
最多出现K次的代码模板:
public int removeDuplicates(int[] nums,int k) {
if(nums == null) return 0;
if(nums.length <= k) return nums.length;
// 1.定义[0,index] 是修改后的满足要求的数组区间,这里已经把0 1 2 ...k- 1 ,共k个数 放进去了
int index = k - 1;
// 2.判断终止条件
for(int i = k;i < nums.length;i++){
// 3.指针移动条件
if(nums[i] != nums[index-k+1]){ //找出指针移动规律是关键
index++;
nums[index] = nums[i];
}
}
// 4.判断返回值
return index + 1;
}
26.删除有序数组中的重复项
给你一个有序数组 nums
,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null) return 0;
int index = 0;
for(int i=1;i<nums.length;i++){
if(nums[index] != nums[i]){
index++;
nums[index] = nums[i];
}
}
return index+1;
}
}
141.环形链表
使用快慢指针找环,一个一次移动一个节点,一个一次移动两个节点,若有环,两指针一定会相遇。
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
142.环形链表2
判断链表是否有环,并返回进入环的节点。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
//获取首次相遇时候,slow的位置
while(fast!= null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
//判断没环的情况,防止空指针
if(fast == null || fast.next == null) return null;
//快指针重新出发,相遇位置就是入口位置,两指针的行程关系
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
202.快乐数
判断一个数是不是快乐数。此题中快慢指针用来跳出循环。
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
输入:n = 2
输出:false
class Solution {
public boolean isHappy(int n) {
int slow = n;
int fast = jisuan(n);
while(slow!=fast){
slow = jisuan(slow);
fast =jisuan(jisuan(fast));
if(slow == 1) return true;
}
return slow==1;
}
public static int jisuan(int x){
int result =0;
int res = 0;
while(x>0){
res = x%10;
x = x/10;
result+=res*res;
}
return result;
}
}
左右端点指针练习题
16.最接近的三数之和
给定一个包括 n 个整数的数组 nums
和 一个目标值 target
。找出 nums
中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
713.乘积小于K的子数组
给定一个正整数数组 nums
。找出该数组内乘积小于 k
的连续的子数组的个数。
示例 1:
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
本博客所有文章除特别声明外,大部分为学习心得,欢迎与博主联系讨论