代码随想录二刷 Day01 | 704. 二分查找,35.搜索插入位置,34.在排序数组中查找元素的第一个和最后一个位置,69.x 的平方根,367.有效的完全平方数

题目与题解

参考资料:数组理论基础

Tips: 

1. 数组一般思维上不难,写起来比较考验代码功底

2. 数组是存放在连续内存空间上的相同类型数据的集合

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

3. 数组的元素是不能删的,只能覆盖

4. C++中二维数组在地址空间上是连续的, Java中不一定

704. 二分查找

题目链接:704. 二分查找

代码随想录题解:704. 二分查找

解题思路:

        这题已经是老朋友了,只要注意开闭区间的写法,就不会出错。这里采用左闭右闭的写法,比较好理解,也不容易出错

class Solution {
    public int search(int[] nums, int target) {
        int start = 0, end = nums.length-1;
        while (start <= end) {
            int mid = (end - start) / 2 + start;
            if (nums[mid] == target) return mid;
            else if (nums[mid] < target) start = mid + 1;
            else end = mid - 1;
        }
        return -1;
    }
}

注意点

        如果用左闭右开区间的写法,要修改的地方为:

  • end赋初始值的时候用nums.length
  • while循环里的条件为start < end
  • nums[mid] > target时,end=mid

 35.搜索插入位置

题目链接:35.搜索插入位置

代码随想录题解:​​​​​​​35.搜索插入位置

解题思路

        同样是要求在已经排序的数组里面进行搜索,复杂度为O(logn),显然是用二分算法。

        不过这题有可能搜索不到target值,这时候就需要找到插入的位置,在二分算法中会不断搜索mid并判断nums[mid]是不是符合要求的target,所以最后得到的mid其实就是插入位置,返回即可。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int start = 0, end = nums.length - 1;
        int mid = (end - start)/2 + start;
        while (start <= end) {
            if (nums[mid] < target) start = mid + 1;
            else if (nums[mid] > target) end = mid - 1;
            else return mid;
            mid = (end - start)/2 + start;
        }
        return mid;
    }
}

        随想录给出的方法跟704是一模一样的,不同点在于返回值用了right+1,这里分四种情况讨论

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
        return right + 1;
    }
};

如果循环退出的原因不是找到了nums[mid] = target,那么right必然等于left-1,此时找到的位置已经收束到了left右边,所以此时返回left和right+1都是可以的。 

注意点

        如果用我自己的写法,其实有一点擦边的风险,如果left=0,right=-1时,由于java代码的特性,-1/2+0=0,所以得到的插入值是正确的,但是如果-1/2=-1的话,就会得到错误结果。所以最好还是用普通二分法最后返回left或者right+1。

 34.在排序数组中查找元素的第一个和最后一个位置

题目链接:​​​​​​​34.在排序数组中查找元素的第一个和最后一个位置

代码随想录题解:​​​​​​​34.在排序数组中查找元素的第一个和最后一个位置

解题思路

        这题就是在上一题的基础上,多了一点条件:数组中存在重复元素,要求找到target的始末位置。由于数组已经排序,仍然可以用二分法实现,只不过需要进行两次二分查找。

        如果是查找左边界,那么当nums[mid] >= target时,都要继续往左边搜索,end= mid-1。

        查找结束后,此时end= start - 1,如果数字存在,与上一题一样,返回end+1即可,不过要注意可能存在找不到的情况,如果end+1溢出边界或nums[end+1]不等于target,直接返回-1数组。

        如果是查找右边界,那么当nums[mid] <= target时,都要继续往右边搜索,start= mid+1。

        查找结束后,同理,end=start-1,此时需要返回的是右边界,start-1,如果存在找不到的情况,也是start-1溢出边界或者nums[start-1]不等于target。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[]{-1,-1};
        int start = 0, end = nums.length-1;
        while (start <= end) {
            int mid = (end - start) / 2 + start;
            if (nums[mid] < target) start = mid + 1;
            else end = mid - 1;
        }
        if (end + 1 < nums.length && nums[end + 1] == target) {
            result[0] = end + 1;
        } else {
            return result;
        }
        start = 0;
        end = nums.length-1;
        while (start <= end) {
            int mid = (end - start) / 2 + start;
            if (nums[mid] <= target) start = mid + 1;
            else end = mid - 1;
        }
        if (start - 1 >= 0 && nums[start - 1] == target) {
            result[1] = start - 1;
        }
        return result;
    }
}

注意点

        这里写的复杂了一点,思路是一致的,如果是找左边界,最后就取end值,如果找右边界,最后取start值,像随想录一样分开用额外的函数代替会更好看一点,也能避免当数组里不存在结果的时候重复判断的情况。

class Solution {
    int[] searchRange(int[] nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一
        if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
        // 情况三
        if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
        // 情况二
        return new int[]{-1, -1};
    }

    int getRightBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }

    int getLeftBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else {
                left = middle + 1;
            }
        }
        return leftBorder;
    }
}

69.x 的平方根

题目链接:​​​​​​​69.x 的平方根

leetcode题解:​​​​​​​69.x 的平方根

解题思路

        同样用二分法做,开头是1,结尾为输入值x,然后判断mid*mid与x的大小关系,更新左右边界。思路跟35类似。

        但是mid*mid有溢出的风险,无法AC,需要另辟蹊径来做,要么是用long变量存储mid*mid,要么就用替代方式,判断x/mid和mid的关系,也能间接比较大小。

class Solution {
    public int mySqrt(int x) {
        int left = 1, right = x, ans = 0;
        while (left <= right) {
            int mid = (right - left)/2 + left;
            if (mid > x/mid) {
                right = mid - 1;
            } else {
                ans = mid;
                left = mid + 1;
            }
        }
        return ans;
    }
}

注意点

        如果用long,(long) mid*mid与x比较

        如果用x/mid,要注意mid等于0的情况,一开始定义左右边界就把它排除掉。

367.有效的完全平方数

题目链接:​​​​​​​367.有效的完全平方数

leetcode题解:​​​​​​​367.有效的完全平方数

解题思路

        这题其实在前一题的基础上,加了限制,要求是完全平方数,意味着它的平方根是整数,所以可能是找不到结果的,上一题是一定会有一个结果的,需要注意。

        如果找到了,mid*mid就等于target,直接返回true即可。否则直到退出循环都找不到,就返回false。        

class Solution {
    public boolean isPerfectSquare(int num) {
        int left = 0, right = num;
        while (left <= right) {
            int mid = left + (right - left)/2;
            long res = (long) mid * mid;
            if (res < num) {
                left = mid + 1;
            } else if (res > num) {
                right = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}

 注意点

        我一开始想沿着前一题的思路,用num/mid和mid的关系做比较,但是发现一个问题,如果要求的数是5,第一次得到的mid是2,5/2=2,直接取整了,所以出错,如果要用除法防止溢出的话,需要用double类型记录num/mid。

class Solution {
    public boolean isPerfectSquare(int num) {
        int left = 0, right = num;
        while (left <= right) {
            int mid = left + (right - left)/2;
            double res = (double) num/mid;
            if (mid < res) {
                left = mid + 1;
            } else if (mid > res) {
                right = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}

今日收获

        补了一下一刷没有做的二分法拓展题,有点多,移动元素等下次做吧。

        二分法相关题目注意的点有几个:

  • 二分法使用条件:已经排序好的数组,或对0-num之间的数字搜索,复杂度一般为O(logn)     
  • 求边界问题时,一般跳出循环时,end=start-1,根据需要取start或者end返回,最好找几个例子手动试一下,避免出错
  • 求乘法时,要注意溢出的问题,需要用long类型或者除法进行替代。
  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值