每日算法刷题计划Day17 5.29:leetcode复习滑动窗口+二分搜索5道题+二分答案1道题,用时1h20min

分享丨【算法题单】二分算法(二分答案/最小化最大值/最大化最小值/第K小)- 讨论 - 力扣(LeetCode)

第一轮基础(不含基础题困难题目,不含进阶部分、思维拓展部分和其他部分)

思想:
1.

一.二分查找

模版套路

1.套路

c++:

// 返回最小的满足nums[i]>=targert的下标
int lower_bound(vector& nums, int target) {
	int n = nums.size();
	int res = n; // 初始值为n,插到最后面
	int left = 0, right = n - 1;
	while (left <= right) {
		int mid = left + ((right - left) >> 1);
		// 大的是下标
		if (nums[mid] >= target) {
			res = mid;
			right = mid - 1;
		} else
			left = mid + 1;
	}
	// 如果是找等于target的下标再单独判断
	if(res==n || nums[res]!=target) return -1;
	return res;
}
2.题目描述

1(学习).给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]
2.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置
3.给定一个 n 个元素有序的(升序) 整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1
4.给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 targetletters 里至少有两个不同的字符。返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个字符。
5(学习).给你一个按 非递减顺序 排列的数组 nums ,返回正整数数目和负整数数目中的最大值。

  • 换句话讲,如果 nums 中正整数的数目是 pos ,而负整数的数目是 neg ,返回 posneg二者中的最大值。
3.学习经验

(1)nums非递减顺序排列
(2)res初始值为n,表示插入到最后
(3)lower_bound函数找最小的满足nums[i]>=target的下标位置,所有元素比target小就插到n处
(4)可以再调用lower_bound找target+1的下标,然后-1就是target的结束位置

1.1 基础

1. 34.在排序数组中查找元素的第一个和最后一个位置(中等,学习)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

思想

1.给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]
2.二分查找只能找一个位置,找最小的满足nums[i]>=target的下标位置,即开始位置
3.因为是非递减顺序,所以target位置连续,所以target结束位置即为target+1下标位置-1(学习思想)
4.注意res初始值为n,表示插入到最后

代码

c++:
1.lower_bound找最小的满足nums[i]>=targert的下标

class Solution {
public:
    // 返回最小的满足nums[i]>=targert的下标
    int lower_bound(vector& nums, int target) {
        int n = nums.size();
        int res = n; // 初始值为n,插到最后面
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 大的是下标
            if (nums[mid] >= target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
    vector searchRange(vector& nums, int target) {
        int n = nums.size();
        int start = lower_bound(nums, target);
        // 不存在
        if (start == n || nums[start] != target)
            return {-1, -1};
        // 存在,因为是有序的,所以target位置连续,所以找target+1位置,-1就是end位置
        int end = lower_bound(nums, target + 1) - 1;
        return {start, end};
    }
};

2.upper_bound找最小的满足nums[i]>targert的下标

class Solution {
public:
    // 返回最小的满足nums[i]>targert的下标
    int upper_bound(vector& nums, int target) {
        int n = nums.size();
        int res = n; // 初始值为n,插到最后面
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 大的是下标
            if (nums[mid] > target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
};

注意:
1.结果返回vector,可以写成{ }形式,用于列表初始化,元素个数不限,但是确定

2. 35.搜索插入位置(简单)

35. 搜索插入位置 - 力扣(LeetCode)

思想

1.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
2.上面的lower_bound函数

代码

c++:

class Solution {
public:
    int searchInsert(vector& nums, int target) {
        int n = nums.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] >= target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
};
3. 704.二分查找(简单)

704. 二分查找 - 力扣(LeetCode)

思想

1.给定一个 n 个元素有序的(升序) 整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1
2.跟前面两题不同在于"如果目标值存在返回下标,否则返回 -1,所以再返回res前要再单独判断不满足条件的两种情况:

  • 在最后:res=n
  • 不再最后,才能访问nums[res],再判断其是否等于target
代码

c++:

class Solution {
public:
    int search(vector& nums, int target) {
        int n = nums.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] >= target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        if (res == n || nums[res] != target)
            return -1;
        return res;
    }
};
4. 744.寻找比目标字母大的最小字母(简单)

744. 寻找比目标字母大的最小字母 - 力扣(LeetCode)

思想

1.给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 targetletters至少有两个不同的字符。返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个字符。
2.区别在于找大于 target 的最小的字符,而不是大于等于,为upper_bound

代码

c++:

class Solution {
public:
    char nextGreatestLetter(vector& letters, char target) {
        int n = letters.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (letters[mid] > target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        if (res == n)
            return letters[0];
        return letters[res];
    }
};
5. 2529.正整数和负整数的最大计数(简单,学习lower_bound和upper_bound混用)

2529. 正整数和负整数的最大计数 - 力扣(LeetCode)

思想

1.给你一个按 非递减顺序 排列的数组 nums ,返回正整数数目和负整数数目中的最大值。

  • 换句话讲,如果 nums 中正整数的数目是 pos ,而负整数的数目是 neg ,返回 posneg二者中的最大值。
    2.我的思想是转换为找0的初始位置start和结束位置end,从而[0,start)为负数,共start个数,但是正整数数要分类讨论(调试改正):
  • end位置为0,[end+1,n)是正整数,共n-end-1个数
  • end位置不为0,[end,n)是正整数,共n-end个数
    3.学习使用上面的upper_bound函数,找>targert的最小位置end,就不用分类讨论了,保证[end,n)是正整数,共n-end个数
代码

c++:
1.我的代码

class Solution {
public:
    int lower_bound(vector& nums, int target) {
        int n = nums.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] >= target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
    int maximumCount(vector& nums) {
        int n = nums.size();
        int start = lower_bound(nums, 0), end = lower_bound(nums, 0 + 1);
        int neg = start; //[0,start)
        int pos = 0;
        if (end == n || nums[end] != 0)
            pos = n - end; //[end,n)
        else
            pos = n - end - 1; //[end+1,n)
        return max(neg, pos);
    }
};

2.变成upper_bound:

class Solution {
public:
    int lower_bound(vector& nums, int target) {
        int n = nums.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] >= target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
    int upper_bound(vector& nums, int target) {
        int n = nums.size();
        int res = n;
        int left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 修改处1
            if (nums[mid] > target) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
    int maximumCount(vector& nums) {
        int n = nums.size();
        int start = lower_bound(nums, 0), end = upper_bound(nums, 0); //修改处2
        int neg = start;   //[0,start)
        // 修改处3
        int pos = n - end; //[end,n);
        return max(neg, pos);
    }
};

二.二分答案

“花费一个 log 的时间,增加了一个条件。” —— 二分答案(如何理解?)
问:如何把二分答案与数组上的二分查找联系起来?
答:假设答案在区间 [2,5] 中,我们相当于在一个虚拟数组 [check(2),check(3),check(4),check(5)]二分找第一个(或者最后一个)值为 true 的 check(i)。

模版套路

1.套路

c++:


2.题目描述
3.学习经验

(1)

2.1 求最小

题目求什么,就二分什么。

1.套路

c++:

class Solution {
public:
    // check函数
    bool check(vector& nums, int threshold, int m) {
        long long sum = 0;
        for (const auto x : nums) {
            // 向上取整(学习+m-1调整/向下取整)
            sum += (x + m - 1) / m;
            // 提前结束
            if (sum > threshold)
                return false;
        }
        return true;
    }
    int smallestDivisor(vector& nums, int threshold) {
        int n = nums.size();
        // 左边界为1,因为除数m不能为0
        int left = 1, right = INT_MIN;
        // 右边界取最大元素
        for (const auto x : nums)
            right = max(right, x);
        int res = right;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 满足条件再继续找更小的
            if (check(nums, threshold, mid)) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
};
2.题目描述

1.给你一个整数数组 nums 和一个正整数 threshold ,你需要选择一个正整数(二分答案) 作为除数,然后将数组里每个数都除以它,并对除法结果求和。请你找出能够使上述结果小于等于阈值 threshold(条件) 的除数中 最小 的那个。

3.学习经验

(1)分为两个部分:

  • 二分搜索所求最小的数:难点在于仔细考虑左边界left和右边界的right的取值,如果check()函数为true说明还可能存在更小的,right=mid-1向左侧更小的搜索
  • check函数判断这个数是否满足条件:可以提前退出返回false
    (2)符合单调性,即二分搜索的数越小,统计量越大,会超过条件限制,反过来就是二分搜索的数越大,统计量越小,越能满足条件(即越大越合法),所以存在一个最小的数恰好满足条件
1. 1283.使结果不超过阈值的最小除数(中等,学习)

1283. 使结果不超过阈值的最小除数 - 力扣(LeetCode)

思想

1.给你一个整数数组 nums 和一个正整数 threshold ,你需要选择一个正整数作为除数,然后将数组里每个数都除以它,并对除法结果求和。请你找出能够使上述结果小于等于阈值 threshold 的除数中 最小 的那个。
2.学习向上取整的写法,通过加上m-1将它转换为/的向下取整:(x+m-1)/m
3.单调性分析:
假设除数为 m。
根据题意,每个数除以 m 再上取整,元素和为
∑ i = 0 n − 1 ⌈ n u m s [ i ] m ⌉ \sum_{i=0}^{n-1}\lceil \frac{nums[i]}{m} \rceil i=0n1mnums[i]
由于 m 越大,上式越小,有单调性,可以二分答案
最小的满足 ∑ i = 0 n − 1 ⌈ n u m s [ i ] m ⌉ ≤ t h r e s h o l d \sum_{i=0}^{n-1}\lceil \frac{nums[i]}{m} \rceil \leq threshold i=0n1mnums[i]threshold的m就是答案

代码

c++:

class Solution {
public:
    // check函数
    bool check(vector& nums, int threshold, int m) {
        long long sum = 0;
        for (const auto x : nums) {
            // 向上取整(学习+m-1调整/向下取整)
            sum += (x + m - 1) / m;
            // 提前结束
            if (sum > threshold)
                return false;
        }
        return true;
    }
    int smallestDivisor(vector& nums, int threshold) {
        int n = nums.size();
        // 左边界为1,因为除数m不能为0
        int left = 1, right = INT_MIN;
        // 右边界取最大元素
        for (const auto x : nums)
            right = max(right, x);
        int res = right;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            // 满足条件再继续找更小的
            if (check(nums, threshold, mid)) {
                res = mid;
                right = mid - 1;
            } else
                left = mid + 1;
        }
        return res;
    }
};

你可能感兴趣的:(算法,算法,leetcode,职场和发展)