leetcode算法入门
第一天(二分查找)
704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
思路:1.初始时,设立两个指针left,right分别指向数组最左和最右元素的下标。
2.当left<=right时(说明仍然有工作数组),设置i指针,值为left和right的均值,即指向工作数组的中间位置,并判断所指向元素的值和目标值的大小关系:
①若小于,说明目标值在i指针的右边,故将工作数组变为[i+1,right],重复步骤2
②若大于,说明目标值在i指针的左边,故将工作数组变为[left,i-1],重复步骤2
③若等于,说明找到了,直接返回i值
3.当left>right时,说明已无工作数组,即找不到,所以返回-1。
代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left,right;//分别用于记录左右下标
left = 0; //初始时指向最左元素小标
right = nums.size()-1; //初始时指向最右元素下标
while(left<=right){ //存在工作数组时
int i = (left+right)/2; //工作指针指向工作数组中间位置
if(nums[i]<target){ //说明值在工作指针右边,于是更新工作数组
left = i+1;
}
else if(nums[i]>target){ //说明值在工作数组左边,更新工作数组
right = i-1;
}
else //值刚好在工作指针所在处
return i;
}
return -1; //当left>right时,说明整个数组都找不到该值
}
};
需要注意的是,这里是以调用的方式传入一维vector,好处是在方便直接获取数组的长度:nums.size()。如果要在自己的编译器上面判断代码是否正确,需要按如下方式为vector赋值:
int main(){
int b[]={-1,0,3,5,9,12}; //先定义一个数组
vector<int> nums (b,b+6); //把这个数组b的第1到第6个元素的值放入vector
int tar = 9;
Solution Sol; //定义一个Solution实例
int c = Sol.search(nums,tar); //使用Sol实例的search对象,并传入对应参数(注意是nums而不是b),得到返回值
printf("%d",c);
}
278. 第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
示例 2:
输入:n = 1, bad = 1
输出:1
提示:
1 <= bad <= n <= 2^31 - 1
思想:
1.n个版本中,前x个是正确的,后面的n-x个是错误的,那么第一个错误的版本就是第x-1个。如果从第一个依次往后判断(每次都判断当前位置i和后一位置i+1),找到第i个是正确,第i+1个错误,那么return i+1;若第一个就错误,返回1。这种类似于顺序查找,当然可以,但时间复杂度太高。
2.本题类似于二分查找(左边数全部小于它,右边数全部大于他),为了找某个位置,这个位置左、右两边的某种性质是不一样的,当然具体问题具体分析,我们以二分查找思想来考虑。
思路:
1.初始时,设立两个指针left,right分别指向数组最左和最右元素的序号(1和n)。
2.当left<right时(说明仍然有工作数组),设置middle指针,值为left和right的均值,即指向工作数组的中间位置,并判断所指向版本是不是错误的:
①若错误:说明middle可能是第1、2、3…个错误的,那么第一个错误的版本一定在区间[left,middle]内,故令right=middle,重复步骤2。
②若正确:说明第一个错误的版本一定在区间[middle+1,right]内,故令left=middle,重复步骤2。
注意:经过不断地循环,最终一定是在越发缩小的错误版本区间内找到了第一个错误的版本(middle所指),若循环条件是left<=right,这时将不断地执行if语句,无法退出循环,故循环条件是left<right。
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int left,right;
left = 1;
right = n;
int middle;
while(left<right){
middle = left + (right - left)/2; //这样做运行时不会产生一个right+middle的数,从而避免溢出
if(isBadVersion(middle)){
right = middle;
}
else{
left = middle + 1;
}
}
return left;
}
};
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为无重复元素的升序排列数组
-104 <= target <= 104
思路:1.初始时,设立两个指针left,right分别指向数组最左和最右元素的序号(1和n)。
2.当left<=right时(说明仍然有工作数组),设置middle指针,值为left和right的均值,即指向工作数组的中间位置,并判断所指向元素值和目标值大小情况:
①若大于:说明目标值一定在区间[left,middle-1],即令right=middle-1,重复步骤2。
②若小于:说明目标值一定在区间[middle+1,right],即令left=middle+1,重复步骤2。
③若等于:返回middle
3.若目标值不在数组中,2中的循环执行到最后将退出循环,根据经验(自己手写模拟),此时left的值是目标值将被顺序插入的位置。
代码:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int middle;
int left = 0;
int right = nums.size() - 1;
while(left <= right){
middle = left + (right - left)/2;
if(nums[middle]>target){
right = middle - 1;
}
else if(nums[middle]<target){
left = middle + 1;
}
else
return middle;
}
return left;
}
};
第二天(双指针)
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序
进阶:
请你设计时间复杂度为 O(n) 的算法解决本问题
思想:已经告知是双指针题目了,说明肯定要设立两个指针(初始一般都是分别指向最左、右侧元素)
指向数组最左、右侧元素。因为数组是按照非递减顺序排序的(且有正有负),所以平方后最大的数要么就是最左侧的数,要么就是最右侧的数。故可以将最两个指针所指元素的绝对值进行比较,选择更大的元素的平方数并按照逆序的顺序放入结果数组中,同时更新指针(left++或right–),重复循环直到没有工作数组。
思路:1.初始时,设立两个指针left,right分别指向数组最左和最右元素的下标(0和nums.size()-1),同时设置指针k指向结果数组当前空闲的最后位置。
2.当left<=right时(说明仍然有工作数组),在绝对值情况下判断nums[left]和nums[right]的大小情况:
①若大于或等于(已经只要求对平方数排序,所以可以等于):将nums[left]的平方数放入结果数组result[k],同时k左移一位,并将left指针右移一位(即left++),重复步骤2。
②若小于:将nums[right]的平方数放入结果数组result[k],同时k左移一位,并将right指针左移一位(即right–);重复步骤2。
③当退出循环时,说明所有排序已经结束,return result即可。
代码:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int left,right;
left=0;
right=nums.size()-1;
int k = right;
vector<int> result(nums.size());
while(left<=right){
if(abs(nums[left])>abs(nums[right])){
result[k--] = nums[left]*nums[left];
left++;
}
else{
result[k--] = nums[right]*nums[right];
right--;
}
}
return result;
}
};
189. 轮转数组轮转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105
进阶:
尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
思想:将长度为n的数组右轮转k个位置,可以发现,轮转后,原数组的后k个元素在结果数组的前k个位置,原数组的前n-k个元素在结果数组的后n-k个位置。所以可以直接对整个数组进行翻转,先保证两个“整体”间位置正确。之后发现,两个整体内的元素位置和正确位置是相反的,故再依次对两个整体内的元素进行翻转,为方便操作,最好先定义一个翻转函数。
class Solution {
public:
void reverse(vector<int>& nums, int start, int end) { //定义一个翻转函数
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size(); //若右轮转次数大于数组长度,则进行取余操作,避免进行无用的轮转,增加时间开销
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};
第三天(双指针)
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
思想:将数组分为3个区域,左边为处理好的区域,中间为0区域,右边为待处理区域。
通过right指针不断寻找待处理的非零元素并放在处理好的区域的后一位,设置left指针始终指向处理好区域的后一位来帮助操作(即有了一次交换后,再将left后移一位)
思路:
初始时设置两个指针left和right指向第一位,right指针判读当前元素是否是非零元素,若是则与left指针的元素交换并将left指针右移一位(这样在下次交换时能将下一个待处理的非零元素放在正确位置),若不是则将right指针右移,继续寻找。
代码:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size(), left = 0, right = 0;
while (right < n) {
if (nums[right]) {
swap(nums[left], nums[right]);
left++;
}
right++;
}
}
};