代码随想录-数组-二分查找

注意

本篇内容来自代码随想录leetcode,仅供自己记录与学习使用,如有侵权,请立即联系我。
感谢码农前辈们的付出。

数组知识

数组:存放在连续存储空间上的相同数据类型的集合,可以很方便的通过下标索引的方式获取到下标对应的数据

数组下标从0开始
数组内存空间是连续的

因为数据的内存空间是连续的,所以我们在增加或删除元素的时候,需要移动其他元素的地址

注意C++中vector和array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组(?)

数组的元素是不能删的,只能覆盖一维数组的长度可以发生变化吗

二维数组(先行后列)在内存中的空间地址是连续的吗?C++中的二维数组是连续分布的

#include <iostream>
using namespace std;

int array[2][3]={{0, 1, 2},{3, 4, 5}}; //数组的声明方式
cout << &array[0][0] << " " << &array[0][1] << " " << &array[0][2];
cout << &array[1][0] << " " << &array[1][1] << " " << &array[1][2];
//输出结果: 0x115fa28 0x115fa2c 0x115fa30
//          0x115fa34 0x115fa38 0x115fa3c(地址长短由谁决定?)
// 一个int型数据占4个字节大小

Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作由虚拟机完成

二分查找

给定一个n个元素有序(升序的)整形数组nums和一个目标值,写一个函数搜索其中的target,如果目标值存在则返回下标,否则返回-1
Q: 如果有重复应该如何处理?

提示:1. 可以假设nums中的所有元素是不重复的
2. n将在[1,10000]之间(有何影响)
3. nums的每个元素都将在[-9999, 9999]之间(有何影响)

先写个暴搜,丢掉升序条件

Q: 对于无序数组,怎样的查找方式能取得更快的效果?
先排序,再查找吗?
Q: 在遍历vetcor中元素时,使用引用更慢?为什么

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int index = 0;
        for (int num:nums)          // for(int &num:nums) 更慢!
        {   
            if(num==target)
                return index;
            index++;
        }
        return -1;
    }
};

我写的二分查找

感觉其中的难点问题有两个
一是停止条件的确定
二是左右指针更新值的确定
我写的二分查找,相比于暴力搜索,并未取得更高的内存与速度优势。当然暴力搜索的时间复杂度为 O ( n ) O(n) O(n),二分查找的时间复杂度应该在 O ( log ⁡ n ) O(\log n) O(logn)
Q: leetcode为什么在运行过后一次,再次运行时能取得更快的速度? 是对中间数据有缓存吗?
刷新页面能解决这个问题,刷新似乎也解决不了这个问题哈哈哈
Q: 为什么时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),而不是 n 0.5 n^{0.5} n0.5

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int mid = (n-1)/2; 
        int left = 0, right = n-1;
        while(left < right)
        {
            if(target > nums[mid])
                left = mid+1;
            else if(target < nums[mid])
                right = mid;
            else
                break;   
            mid = (left+right)/2;
        }
        // 写完上面的逻辑,发现对于长度为一的数组无法处理
        if (nums[mid]==target)              
            return mid;
        else
            return -1;    
    }
};

代码随想录

循环不变量,每一次对边界的处理都需要坚持根据区间的定义来做
改善地方:1. mid的获取方式
···
int middle = left + ((right - left) >> 1); // 防止溢出 等同于(left + right)/2
···
2. mid第一次获取也可以放在循环中进行
3. right指针的定义不同,mid的定义也不同,我写了个两不像
4. 他们写的怎么这么快!!!!倒是挺好记的哈哈哈,为什么我记不住! 就记住要左闭右开,而且把这个思想给贯彻到底

// 版本二

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};
  1. 他们的左闭右闭也记一下
// 版本一
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 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; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

x的平方根

给你一个非负整数 x x x,计算并返回 x x x的算数平方根,结果只保留整数部分
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

提示: 0 < = x < = 2 31 − 1 0 <= x <= 2^{31} - 1 0<=x<=2311

在面试中,面试官让我手撕过这道题,面试官要求保留结果一定的位数,我当时说已一定步长去搜索,结果当然是g,他们连感谢信都不发我,生气!,但是我觉得面试官很有水平,他是做网络虚拟化的,我好像下来了解过,但是现在忘了,记得他的手撕环境上写的 Talk is cheap, show me the code,啊,我感觉我很cheap。

Q: warning: operator ‘>>’ has lower precedence than ‘+’; ‘+’ will be evaluated first [-Wshift-op-parentheses]
8 | int middle = left + (right - left) >> 1;
如果说 >> 优先级低于 + ,上式就不能运行了吗? 为什么 不能最后算 >>1 呢

Q:Line 25: Char 5: error: non-void function does not return a value in all control paths [-Werror,-Wreturn-type]
这是编译器检查出的错误吗,要求所有分支都有返回值,若分支从不会被运行呢?虽然此处已经被运行了,这里待检验

我写的

  1. 磕磕绊绊写出来的,针对各种报错问题加了很多if,总体思想是基于二分法,对每一个middle判断其是否是正确的平方根,或在平方根的一步之内。
  2. middle 使用long int 类型是因为测试用例中有一个非常大的数2147395599,在做middlemiddle乘法时,会超出int的数据范围。故而有这个问题middlemiddle的数据结果会暂存在于middle数据类型相同的临时变量中,是这样吗?
  3. 算法和空间复杂度都处在垫底水平 哈哈
class Solution {
public:
    // 定义target在一个左闭右开的区间中 [right, left)
    int mySqrt(int x) {
        int left = 0;
        int right = x;
        while(left < right){
            long int middle = left + ((right - left) >> 1);
            if(middle * middle > x)
            {
                if((middle - 1) < 0)
                    return middle;  
                if(((middle-1)*(middle-1)) < x)
                    return middle - 1;
                right = middle;
            }
            else if(middle * middle < x)
            {
                if((middle+1)*(middle+1) > x)
                    return middle;
                else if(((middle+1)*(middle+1)) == x)
                    return middle + 1;
                left = middle + 1;
            }
            else 
                return middle;
        }
        return 0;
    }
};
优化?

感觉除了计算上能优化一下,不算乘法算加法外没什么可以优化的地方?不知道大神们是怎么写的

官方题解

先看一下人家写的二分查找

写的真好:x平方根的整数部分ans是满足 k 2 ≥ x k^2\geq x k2x的最大 k k k值,因此我们可以对k进行二分查找,从而得到答案。
Q: 大于等于 是 \geq,小于等于是 \leq

  1. 羡慕呀,我也想写出这样的代码
  2. 感觉这样的二分好像更方便 // 定义target在左闭右闭的区间里[left, right]
  3. 这里使用了long long做类型转换,学到了,但是这里使用long好像也行,要看 x 2 x^2 x2的范围了,复习一下int数据类型长度吧,long的长度和操作系统有关,所以使用long long应该是没有一点问题的。
    在这里插入图片描述
// 优雅
class Solution {
public:
    int mySqrt(int x) {
        int l = 0, r = x, ans = -1;
        while(l <= r){
            int mid = l + (r-l)/2;
            if((long long)mid * mid <= x)
            {
                ans = mid;
                l = mid + 1;
            }
            else
                r = mid - 1;
        }
        return ans;
    }
};
袖珍计算器算法

不允许使用任何内置指数函数和算符,用指数函数exp和对数函数ln来代替平方根函数,真行啊,就直接算
x 0.5 = e l n x ∗ 0.5 = e 1 2 l n x x^{0.5} = e^{lnx}*0.5 =e^{\frac{1}{2}lnx} x0.5=elnx0.5=e21lnx
然后他喊我注意,指数函数和对数函数的返回值都是浮点数,运算过程中会存在误差。(关于浮点数运算我一概不知哈哈哈,平时都只用int),听他们的意思是会算小,所以在使用袖珍计算器计算得到结果后,要找出ans与ans+1到底谁是正确答案。
依旧非常优雅
Q: log这底数如何设置啊?
啊,只有两种log()和log10(),如果需要自定义底数的话需要换底,以m为底n的对数,是 l o g ( n ) / l o g ( m ) log(n)/log(m) log(n)/log(m),有些许麻烦哦。
在头文件中定义
Q:这里如果不对0进行判断,0也是ok的呀,log(0)是负无穷大(等等,负无穷是多少,leetcode里输出是-inf),然后e负无穷趋近0,被int一取成0了,是这样的吗?
不是,如图,e完就零了
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int mySqrt(int x) {
        if(x==0){
            return 0;
        }
        int ans = exp(0.5*log(x));
        return (long long)(ans + 1)*(ans + 1) <= x ? ans+1:ans;
    }
};
牛顿迭代法不想看哈哈哈
来个网友的这内联汇编

就是在C语言中可以直接使用汇编语言的代码段,常用于加速一些关键计算

// 我也想用汇编干他们,可惜我好像只想得起来mov了
int mySqrt(int x){
    double s = x;
    __asm__ (
            "movq %1, %%xmm0;"
            "sqrtsd %%xmm0, %%xmm1;"
            "movq %%xmm1, %0"
            :"+r"(s)
            :"r"(s)
            );
    return s;
}

搜索插入位置

给定一个排序数组和一个目标值,在数组中找到该目标值,并返回其索引。如果目标值不存在于数组中,则返回它应该被顺序插入的位置。
使用时间复杂度为 O ( l o g n ) O(log n) O(logn)的算法。

提示: nums无重复元素,升序排列的数组

Q: 通过引用方式传递vector容器?有何优势?

int searchInsert(vector<int>& nums, int target);

Q:比普通的二分查找更进一步,面对更加广泛的情况,那么如果我找到了它应该被顺序插入的位置,再对数组进行插入,也是一件麻烦事情啊!

直接借鉴代码随想录吧

自己的理解:前面是正常的二分查找,如果找到了就返回target值在升序数组中的索引下标middle。否则就没有找到,前一步是left=right,不论target与nums[middle]谁大谁小,都会导致left>right,且left=right+1,所以应该插入的位置是右边的位置,即left的位置或者是right+1的位置。

代码随想录首先对所有的情况进行了分析,抛开这道题,感觉这种列举所有情况的思路,我之前觉得情况太多,太过繁杂,如果分析的话会使用很多if,一定有一种方式能够绕开它,来获得一种通式解法;而当我面临选择困境时,友人帮我分析情况时,也列举了所有可能的情况,当时的我觉得十分受用,可能在生活中,在繁杂的生活中,不存在任何一种通式的解决办法,摆在我们面前的也就几个选择罢了,那么代码会比生活更复杂吗?我不这么认为,那么多使用几个if不行吗?哈哈哈~

回到正题,可能的插入位置有四种

  1. 插入位置0
  2. 插入数组中两数之间
  3. 在数组中找到了目标值
  4. 插入数组末尾
    在这里插入图片描述
暴力解法

暴力解题 不一定时间消耗就非常高,关键看实现的方式,就像是二分查找时间消耗不一定就很低,是一样的。

非常nice的解题方式
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        for(int i = 0; i < nums.size(); ++i){
            // 处理三种情况
            // 1. 插入位置0,此时nums[0]>target
            // 2. 目标值插入数组中间i,此时nums[i]>target
            // 3. 目标值在nums中找到,此时nums[i]=target
            if(nums[i]>=target){
                return i;
            }
        }
        // 对应第四种情况,目标值大于nums中所有值,应放在数组最后
        // 或者nums为空数组,那么插入在0的位置
        return nums.size();
    }
};

二分查找

以后大家只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。
同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
然后在二分查找的循环中,坚持循环不变量的原则,很多细节问题,自然会知道如何处理了。

时间复杂度: O ( l o g n ) O(log n) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)
Q: 什么是循环不变量,以下分析均来自左侧链接?
在计算机科学中,循环不变量(loop invariant),是一组在循环体内每次迭代均保持为真某种性质,通常被用来证明程序或算法的正确性
在这里插入图片描述

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        // 左闭右闭
        int left = 0;
        int right = nums.size() - 1;
        int ans;
        while(left <= right){
            int middle = left + ((right - left) >> 1);
            if(nums[middle] < target)
            {   
                left = middle + 1;
            }
            else if (nums[middle] > target)
            {
                right = middle - 1;
            }
            else 
                 return middle;
        }
        return left;
    }
};

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

给定一个按照非递减(非递减?那就是不是递减就可以?那瞎胡排不也行吗?)顺序排列的整数数组nums,和一个目标值target。找出target在nums中开始和结束的位置。

如果数组中不存在target,返回[-1,-1]。
设计时间复杂度为 O ( l o g n ) O(logn) O(logn)的算法。

实例:nums = [5,7,7,8,8,10] ,target = 8
输出:[3,4]

这个题也在面试中间见过,原来它属于这里哈哈,关键是可以用二分查,查到之后嘞,二分不一定会查到开头吧,可能查到其中任何一个下标吧,那么之后呢,如何确定这个小窗的范围呢?

提示: − 1 0 9 -10^9 109<=nums[i]<= 1 0 9 10^9 109
− 1 0 9 -10^9 109<=target[i]<= 1 0 9 10^9 109 有什么用暂时不想想?

先写一个暴搜吧

感觉暴搜也是有方法和技巧的,一味直接上,搞不出来。
分析一下各种情况吧,没找到直接返回[-1,-1],这个写到最后吧
借助下标遍历整个nums,若nums[i] = target, 记录i作为left
k从1开始,若i+k大于nums.size()-1,即找到末尾了,返回[i, nums.size()-1]
若i+k不大于nums.size()-1 同时 n u m s [ i + k ] ≠ t a r g e t nums[i+k]\neq target nums[i+k]=target,则结束,返回区间[i, i+k-1]

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        for(int i = 0; i < nums.size(); ++i){
            if(nums[i]==target)
            {
                for(int k = 1; i + k<nums.size(); ++k)
                {
                    if(nums[i+k]!=target)
                        return {i, i+k-1};
                }
                return {i,(int)nums.size()-1};
            }    
        }
        return {-1, -1};
    }
};

写出来了,时间复杂度 O ( N ) O(N) O(N)
Q:nums.size()-1 写成这样时,会被报这个错
Line 12: Char 27: error: non-constant-expression cannot be narrowed from type ‘size_type’ (aka ‘unsigned long’) to ‘int’ in initializer list [-Wc++11-narrowing]
12 | return {i,nums.size()-1};
| ^~~~~~~~~~~~~
Line 12: Char 27: note: insert an explicit cast to silence this issue
12 | return {i,nums.size()-1};
| ^~~~~~~~~~~~~
| static_cast( )
1 error generated.
看起来是说nums.size()的返回值是unsigned long,而数字1的默认类型是int,在做减法运算时,结果的类型会统一成较小的类型,编译器推荐我使用一个显式的类型转换static_cast(),那么问题来了static_cast()与直接在变量前面加(int)有什么区别?
经过检索,后者是C风格的类型转换符,前者是C++中的类型转换符,相比于后者,前者更加明确和安全(不想研究了,略)

首先,我本地调试的结果如下:
在这里插入图片描述
可以看到nums.size()的类型可能跟随系统有所不同,但不同类型用于逻辑比较运算符就相安无事。

想一想咋二分吧

我感觉别无他法,先找到一个位置,再在附近进行搜索吧,但是感觉又十分麻烦,若是这样,二分查找的作用就是缩小了暴力搜索的范围

当然,首先没找到就返回[-1,-1]
其次若找到了是否target值一定在[left, right]的包围中? 是的
再以left和right为左右区间暴搜吗? 太让人头大了 写着看吧
逻辑还是较为清楚的,同时我也尽量重复利用了left和right,哭哭,想要会员来帮我分析一下时间复杂度,这样写还是不是 O ( l o g n ) O(log n) O(logn)了,但是肯定比直接暴搜低吧。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){
            int middle = left + ((right - left) >> 1);
            if(nums[middle] > target)
                right = middle - 1;
            else if(nums[middle] < target)
                left = middle + 1;
            else{
                // we get target here
                for(; left <= right; ++left){
                    if(nums[left] == target)
                        break;
                }
                for(;left<=right; --right){
                    if(nums[right] == target)
                        break;
                }
                return {left, right};
            }
        }
        // 未找到 
        return {-1, -1};
    }
};

看看代码随想录的大哥们是如何优雅的吧

分了三种情况
情况一:target在数组范围的右边或者左边
情况二:target在数组范围中,但是数组中不存在target
情况三:target在数组范围中,且数组中存在target

原来是直接写两个二分,分别搜索target在数组中的左边界和右边界,关键是我不知道二分这样用啊,如何找到左边界和右边界呢?

确定好:计算出来的右边界是x的右边界,左边界同理。

寻找左边界,就要在 n u m s [ m i d d l e ] = = t a r g e t nums[middle] == target nums[middle]==target的时候更新right
寻找右边界,就要在 n u m s [ m i d d l e ] = = t a r g e t nums[middle] == target nums[middle]==target的时候更新left

  1. 感觉看完之后下回还是写不出来。但我知道了使用二分法可以对含重复元素的非递减数组进行处理,搜索到其中值的范围,单纯的二分法应用场景太少了。
  2. 对于二分法的灵活应用,寻找左边界,就要在 n u m s [ m i d d l e ] = = t a r g e t nums[middle] == target nums[middle]==target的时候更新right,这个逻辑有点拧,但还是非常迷人的。和前面x的平方根里面leetcode的官方解法很像。并且确实快啊!
  3. 合并先不看了,小脑瓜顶不住了。
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int rightBorder = getRightBorder(nums, target);
        int leftBorder = getLeftBorder(nums, target);

        // 对应情况一
        if(rightBorder == -2 || leftBorder == -2) return {-1, -1};
        // 对应情况三
        if(rightBorder - leftBorder > 1) return {leftBorder+1, rightBorder -1};
        // 对应情况二
        return {-1,-1};
    }
private:
    // 二分查找,寻找target的右边界(不包含target)
    int getRightBorder(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -2;
        while(left <= right){
            int middle = left + ((right - left) >> 2);
            if(nums[middle] > target){
                right = middle - 1;
            }
            else{   // 包含了两种情况,一种是nums[middle] < target
                    // 另外一种是nums[middle] = target
                    // 如果rightBorder未被幅值,则说明整个nums中的元素都大于target
                    // 此时对应情况一中的一种情况,即target位于数组范围的左边
                left = middle + 1;
                rightBorder = left;     
                // 明明是寻找右边界,却使用左边界赋值
                // 感觉逻辑上不是很通顺啊
                // 这样理解退出while循环前一步,left=right=middle,
                // 若此时nums[left] = target或者nums[left] < target(在数组范围右侧)
                // 则left = right + 1 = middle + 1, 对应范围的右边界
                // 或者我们考虑nums = {2,2,2,2},target =2这种情况,
                // left会从零开始,逐渐自增1,直到=right,最后=right+1=4
                // 这样我们就找到了右边界,现在考虑nums={1,3,4,5},target=2
                // 此时rightBorder会是多少? 此时left =rightBorder= 1,这应该会与右边界的判断一同作为条件
                // 来对情况二进行判断
            }
        }
        return rightBorder;
    }

    // 对称的,我们寻找target的左边界(不包含target)
    // 如果leftBorder没有被赋值,则表示target在数组范围的右边,对应情况一的中的第二种情况
    int getLeftBorder(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -2;
        while(left <= right){
            int middle = left + ((right - left) >> 2);
            if(nums[middle] < target)
                left = middle + 1;
            else{   
                     // 对应两种情况,一是nums[middle] = target
                     // 二是nums[middle] > target
                right = middle - 1;
                leftBorder = right;
                // 现在考虑nums={1,3,4,5},target=2
                // 此时leftBorder是多少,此时right = leftBorder = 0
                // 果然和rightBorder联合起来
                // 因为leftBorder和rightBorder都不包括target
                // 所以若target在数组范围内并存在,则其首先其二者都不为-2
                // 其次rightBorder与leftBorder之间距离大于1

            }
        }
        return leftBorder;
    }
};

有效的完全平方数

给你一个正整数num,如果num是一个完全平方数,则返回true,否则返回false。

要求不能使用任何内置的库函数

提示: 1 < = n u m < = 2 31 − 1 1<=num<=2^{31}-1 1<=num<=2311

直接二分搜吧

梦回面试,我当时在算这个东西,我对每个num都从1开始搜不是很笨,所以当时我在思考 x > \sqrt x> x >什么和 x < \sqrt x< x <什么,从这个下限开始搜,就节省了一些,虽然肯定起不到砍一刀的威能但是也能帮帮忙。先写二分吧。

class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0;
        int right = num;
        while(left <= right){
            int middle = left + ((right - left) >> 1);
            if((long long)middle*middle > num){
                right = middle - 1;
            }
            else if((long long)middle*middle < num){
                left = middle + 1;
            }
            else
                return true;
        }
        return false;
    }
};

写完了,让我想想我一直想干的事情,拿一张纸搞一下好像有点low吼,去函数模拟器吧。
在这里插入图片描述
可以看到我们首先需要对1进行一个特殊处理,然后用这两个函数分别当上限和下限,试试。
不对这个 x x x还是很大啊,能不能搞个 x \sqrt x x 在点 ( 1 , 1 ) (1,1) (1,1)处的切线,用我仅存的数学知识计算一下 y = 0.5 x + 0.5 y=0.5x+0.5 y=0.5x+0.5
在这里插入图片描述
这样就不用对1进行特殊处理了吧,试试

class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = log(num)+1;
        int right = 0.5*num+0.5;
        while(left <= right){
            int middle = left + ((right - left) >> 1);
            if((long long)middle*middle > num){
                right = middle - 1;
            }
            else if((long long)middle*middle < num){
                left = middle + 1;
            }
            else
                return true;
        }
        return false;
    }
};

若对1进行特殊处理,可以使用2处的切线,会更快。
OK,这样肯定更快。

官方答案

我已经想到了,袖珍计算器不行了吧,或者能否判断一个数有没有小数呢?若能应该可行,不管了,直接看吧。

使用内置的库函数

根据完全平方根的性质,我们只需要判断num的平方根 x x x是否为整数即可。对于不能判断浮点数的值是否为整数的语言,可以通过向下取整后平方回去看和num是否相等。太优雅了!

class Solution {
public:
    bool isPerfectSquare(int num) {
        int x = (int)sqrt(num);
        return x*x == num;
    }
};

有点搞不懂,为什么要在sqrt前加个强转。

暴力

如果num为完全平方数,那么一定存在正整数 x x x满足 x × x = n u m x\times x=num x×x=num。于是我们可以于是我们可以从 1 开始,从小到大遍历所有正整数,寻找是否存在满足 x×x=num 的正整数 x。在遍历中,如果出现正整数 x 使 x×x>num,那么更大的正整数也不可能满足 x×x=num,不需要继续遍历了。

class Solution {
public:
    bool isPerfectSquare(int num) {
        long x = 1,square = 1;
        while(square <= num){
            if(square == num)
                return true;
            ++x;
            square = x*x;
        }
        return false;
    }
};
跳过牛顿迭代法啦啦

感想

终于完成了二分查找,花了很多时间,但是没关系,相信我会越做越快的。加油!

VScode环境配置 - 待完善呜呜呜搞不定

  1. 安装VScode

  2. 为了在Windows上安装GCC,你需要安装MinGW
    直接从官网下载超慢的
    link

环境变量

环境变量 (environment variables) 是在操作系统中用来指定操作系统运行环境的一些参数。环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所使用到的信息。
Windows 和 DOS 操作系统中的 path 环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到 path 中指定的路径去找。用户通过设置环境变量,来更好的运行进程。

系统变量针对所有用户起作用,为了安全起见,一般配置用户环境变量。
用户变量只对当前用户起作用,不建议为了省事而配置系统环境变量。
link

  1. link

  2. 更改 launch.json文件

Ctrl+Shift+b

参考文献

循环不变量 :https://segmentfault.com/a/1190000041715631
代码随想录: https://www.programmercarl.com/
Leetcode: https://leetcode.cn/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值