代码随想录第一天|没完全掌握的题目34、283、844方便以后二刷有目标
Leetcode 704 二分查找
题目链接: 二分查找
自己的思路: 由于是有序数组,所以首先想到使用二分查找,通过取左右两边的中点依次判断与目标值的大小,逐渐缩小判断区间。二分查找的难点在于左右指针的初始点,while中的循环条件以及左右指针的更新规则,因为在二分查找的过程中是在[left,right]或者[left,right)中寻找元素,所以分左闭右开和左闭右闭两种。
左闭右闭
当情况为左闭右闭的时候,左指针毋庸置疑是从0索引开始,因为是右闭区间,右指针可以取到右端点,所以右指针的起始点应该为数组长度-1,而while中更新的规则,因为右指针可以取到右边界,所以可以设置left<=right为循环条件,也就是查找过程中right可以和left相等,而更新规则为:left=middle+1,right=middle-1,因为right可以取到middle,所以我们设置right的每次更新为right=middle-1;
左闭右闭
当情况为左闭右开的时候,左指针毋庸置疑是从0索引开始,因为是右开区间,右指针取不到右端点,所以右指针的起始点应该为数组长度,而while中更新的规则,因为右指针取不到右边界,所以可以设置left<right为循环条件,也就是查找过程中right不可以等于left,而更新规则为:left=middle+1,right=middle,因为right可以取不到middle,所以设置middle为右边开区间的部门;
在刚开始学的时候可以练一下两种写法,但是自己如果平时刷题或者笔试的时候还是以一种为主即可。
正确思路: 二分查找
左闭右闭
代码:
class Solution {
public int search(int[] nums, int target) {
//初始化左右指针为两个端点
int left = 0;
int right = nums.length - 1;
//左闭右闭,right可以取到右边
while(left <= right){
//防止数值溢出
int middle = left + (right-left)/2;
//左半区间没有目标值(包括middle)
if (nums[middle]<target) left = middle + 1;
//右半区间没有目标值(包括middle)
else if (nums[middle]>target) right = middle - 1;
//找到目标值
else return middle;
}
//整个数组没有目标值
return -1;
}
}
左闭右开
代码:
class Solution {
public int search(int[] nums, int target) {
//初始化左右指针为两个端点
int left = 0;
int right = nums.length;
//左闭右开,right取不到右边
while(left < right){
//防止数值溢出
int middle = left + (right-left)/2;
//左半区间没有目标值(包括middle)
if (nums[middle]<target) left = middle + 1;
//右半区间没有目标值(包括middle) 由于right取不到右边,所以设置right为middle
else if (nums[middle]>target) right = middle;
//找到目标值
else return middle;
}
//整个数组没有目标值
return -1;
}
}
复杂度分析:
时间复杂度:
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n))
n
n
n为数组的长度
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 35 搜索插入位置
题目链接: 搜索插入位置
自己的思路: 由于是有序数组,所以首先想到使用二分查找,如果找到元素,返回它的索引,如果找不到元素,则找第一个大于target的元素,插在其左边即可。如果在数组中找不到元素的话,那么最终right和left落的位置一定是,left在第一个大于该元素的位置,right在left左边一个位置,所以最终返回left即可
正确思路: 二分查找
左闭右闭
代码:
class Solution {
public int searchInsert(int[] nums, int target) {
//定义左右两个指针
int left = 0;
int right = nums.length - 1;
//左闭右闭
while(left<=right){
//取中点,防止溢出
int middle = left + (right - left)/2;
//右边没有,在左边找
if (nums[middle] > target) right = middle - 1;
//左边没有,在右边找
else if (nums[middle] < target) left = middle + 1;
//如果在数组中找到元素,返回索引即可
else return middle;
}
//如果找不到元素即返回第一个大于target的索引
return left;
}
}
复杂度分析:
时间复杂度:
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n))
n
n
n表示数组的长度
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 34 在排序数组中查找元素的第一个和最后一个位置
题目链接: 在排序数组中查找元素的第一个和最后一个位置
自己的思路: 因为题目中要求复杂度是
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n)),而且数组是非递减顺序的数组,所以我们第一个想到的就是二分查找,这里我采用两个二分查找来分别查找target的左边界和右边界,然后让他们两个组成一个数组返回即可。以寻找右边界为例进行讲解,首先和正常的左闭右闭二分法一样的讨论,当nums[middle]<target的时候缩小边界,使left=middle+1,当nums[middle]>target的时候缩小边界,使right=middle-1,不同的点并且最重要的点在于nums[middle]=target的时候,因为要寻找右边界,我们要尽可能地让左指针贴近右指针,也就是贴近右边界,所以我们要动左指针,让左指针left=middle+1,如果动右指针没法保证落在右边界,最后返回的时候要判断数据的有效性,是否不越界并且是否nums[right]的值是否等于target,如果都满足才返回right,否则返回-1。寻找左边界的分析方法同理。
正确思路: 二分查找
代码:
class Solution {
public int[] searchRange(int[] nums, int target) {
int length = nums.length;
if (length==0) return new int[]{-1,-1};
int left = searchleft(target,nums,length);
int right = searchright(target,nums,length);
return new int[]{left,right};
}
//寻找target的右边界
public int searchright(int target,int[] nums,int length){
//左右指针
int left = 0;
int right = length - 1;
while(left<=right){
//中点,防止溢出
int middle = (right-left)/2 + left;
//前面两个和二分查找类似
if (nums[middle]<target){
left = middle + 1;
}else if (nums[middle]>target){
right = middle - 1;
}else{
//注意这里,因为要找的是右边界,所以我们要尽可能地让left向right的位置靠近,所以这里动的
//是left,而不是right,如果动right的话不能保证是右边界
left = middle + 1;
}
}
//如果right合法返回right,为什么要返回right,因为跳出循环的时候,
//nums[right]=target,但是left在right的右边,不能满足nums[left]=target
return (right<0||nums[right]!=target)?-1:right;
}
//寻找target的左边界
//和上面寻找右边界同理
public int searchleft(int target,int[] nums,int length){
int left = 0;
int right = length - 1;
while(left<=right){
int middle = (right-left)/2 + left;
if (nums[middle]<target){
left = middle + 1;
}else if (nums[middle]>target){
right = middle - 1;
}else{
right = middle - 1;
}
}
return (left>length-1||nums[left]!=target)?-1:left;
}
}
复杂度分析:
时间复杂度:
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n))
n
n
n表示数组的长度
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 69 x 的平方根
题目链接: x 的平方根
自己的思路: 比较经典的二分查找,注意要把middle*middle转换成long避免溢出即可。
正确思路: 二分查找
代码:
class Solution {
public int mySqrt(int x) {
//定义初始左右边界
int left = 0;
int right = x;
//开始二分查找
while(left<=right){
//中点
int middle = (right-left)/2 + left;
if ((long)middle*middle<x){
left = middle + 1;
}else if ((long)middle*middle>x){
right = middle - 1;
}else{
return middle;
}
}
//因为最后跳出循环的时候left一定是大于right的,根据题目信息应该返回较小的那个
return right;
}
}
复杂度分析:
时间复杂度:
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n))
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 367 有效的完全平方数
题目链接: 有效的完全平方数
自己的思路: 比较经典的二分查找,注意要把middle*middle转换成long避免溢出即可。
正确思路: 二分查找
代码:
class Solution {
public boolean isPerfectSquare(int num) {
int left = 0;
int right = num;
while(left<=right){
int middle = (right-left)/2 + left;
if ((long)middle*middle<num){
left = middle + 1;
}else if ((long)middle*middle>num){
right = middle - 1;
}else{
return true;
}
}
return false;
}
}
复杂度分析:
时间复杂度:
O
(
log
(
n
)
)
\mathcal{O}(\log(n))
O(log(n))
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
Leetcode 27 移除元素
题目链接: 移除元素
自己的思路:双指针经典问题,可以定义一个快指针,一个慢指针,快指针在前面判断数组中的值是否等于val,如果不等于则把快指针的值赋给慢指针的位置,然后快慢指针都向前进一步;如果等于,快指针往前进一步,慢指针不动,这样前slow个元素就是移除val以后的数组了,也就是我们想要的。
正确思路:双指针
双指针解法
代码:
class Solution {
public int removeElement(int[] nums, int val) {
//定义慢指针
int slow = 0;
//快指针遍历,无论fast处的值等不等于val,快指针都前进一步
for (int fast=0;fast<nums.length;fast++){
//快指针的值如果不等于val,则将此处的值赋给慢指针位置,慢指针前进一步
if (nums[fast]!=val){
nums[slow] = nums[fast];
slow++;
}
}
//因为最后慢指针++了,所以返回前slow个元素即可
return slow;
}
}
复杂度分析:
时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n)
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 26 删除有序数组中的重复项
题目链接: 26
自己的思路:双指针经典问题,可以定义一个快指针,一个慢指针,快指针在前面判断数组中的值是否等于之前慢指针的值,如果不等于则把快指针的值赋给慢指针的位置,然后快慢指针都向前进一步;如果等于,快指针往前进一步,慢指针不动,这样前slow个元素就是移除val以后的数组了,也就是我们想要的。
正确思路:双指针
双指针解法
代码:
class Solution {
public int removeDuplicates(int[] nums) {
//定义慢指针,由于要判断慢指针前面一个位置的值作为临时变量,所以慢指针必须从1开始
int slow = 1;
//快指针也必须从1开始,无论如何快指针都前进一步
for (int fast=1;fast<nums.length;fast++){
//定义临时变量用来记录慢指针前面一个位置的值
int temp = nums[slow-1];
//如果快指针不等于之前慢指针的值,则把快指针的值赋给慢指针的位置,慢指针前进一步
if (nums[fast]!=temp){
nums[slow] = nums[fast];
slow++;
}
}
//由于最后slow++,所以直接返回slow即可,slow即为新数组的长度
return slow;
}
}
复杂度分析:
时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n)
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 283 移动零
题目链接: 移动零
自己的思路:双指针经典问题,可以定义一个快指针,一个慢指针,快指针在前面判断数组中的值是否0,如果值不为0,则交换两个指针的值,如果为0,快指针前进,这样可以把不为0的值都移到前面去,为0的值都移到后面去。
正确思路:双指针
双指针解法
代码:
class Solution {
public void moveZeroes(int[] nums) {
//定义两个指针
int slow = 0;
int fast = 0;
while(fast<nums.length){
//如果快指针的值不为0,就交换两个指针的值
if (nums[fast]!=0){
int temp = nums[slow];
nums[slow] = nums[fast];
nums[fast] = temp;
slow++;
fast++;
}else{
fast++;
}
}
}
}
复杂度分析:
时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n)
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
相似题型
Leetcode 844 比较含退格的字符串
题目链接: 比较含退格的字符串
自己的思路:因为’#‘可以去掉前面一个元素,所以我们可以选择使用两个指针i和j从后向前遍历两个字符串,并且定义两个变量sskip和tskip记录两个字符串里面’#‘的个数。以字符串s为例,当当前元素为’#‘时,那么sskip++,i–;当当前元素不为’#'且sskip大于0,那么sskip++,i–;否则直接跳出,然后判断当前的元素和t当前的元素是否相等,如果不相等,返回false,否则继续判断。
正确思路:双指针
双指针解法
代码:
class Solution {
public boolean backspaceCompare(String s, String t) {
//定义两个指针在两个字符串后面,从后向前遍历
int i = s.length()-1;
int j = t.length()-1;
//记录'#'的个数
int sskip = 0;
int tskip = 0;
//遍历两个字符串
while(i>=0||j>=0){
//遍历字符串s
while(i>=0){
//如果字符串为'#',则'#'个数+1,i--
if (s.charAt(i)=='#'){
sskip++;
i--;
//如果字符串不为'#',而且后面的'#'的个数还存在,那么就给它--,i--
}else if (sskip>0){
sskip--;
i--;
//如果字符串不为'#',而且sskip为0,直接跳出循环,后面类似
}else{
break;
}
}
while(j>=0){
if (t.charAt(j)=='#'){
tskip++;
j--;
}else if (tskip>0){
tskip--;
j--;
}else{
break;
}
}
//当i和j是都是合理索引的时候,判断两个字符串当前值
if (i>=0&&j>=0){
if (s.charAt(i)!=t.charAt(j)){
return false;
}
}else{ //如果两个里面至少有一个不是合理索引,如果其中一个i是合理索引,直接返回false
if (i>=0||j>=0){
return false;
}
}
i--;
j--;
}
//遍历完成,如果没有返回false,即为true
return true;
}
}
复杂度分析:
时间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1)