LeetCode刷题记录(一)


坚持刷题,争取每周天更新

剑指 Offer 03. 数组中重复的数字

解法1:使用unordered_map

? 要找到任意一个,没有限定第一个,或者第k个,直接遍历一边数组,每次遍历时将元素插入到map中,直到map中出现重复值代表找到

class Solution {
public:
    int findRepeatNumber(vector& nums) {
    unordered_map map;//构造时bool默认为false
    for(int num:nums){//遍历
        if(map[num]) //如果在map中已经存在该元素
        {
            return num;
        }
        map[num] = true;//添加进去的元素设置为true
    }
    return -1;
    }
};

时空复杂度都为O(n)

解法二:

? 题目中的数字在一个长度为n的数组中,且数字也在0到n-1这个范围内,可以肯定的是一定有重复,且考虑将数组中每个元素的位置与下标对应,那么一定有两个元素的下标对应到了一起(因为有重复的)

如下2应该被映射到num[2],而num[4]存储的2页会被映射到num[2],此时就发生了冲突即num[i]==num[num[i]],即找到了该元素

class Solution {
public:
    int findRepeatNumber(vector& nums) {
        int i = 0;
        while(i < nums.size()) {
            if(nums[i] == i) {//如果该元素已经在正确的位置,继续向后
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i])//如果该元素需要归位的下标已经有同值元素,结束
                return nums[i];
            swap(nums[i],nums[nums[i]]);//交换到正确位置
        }
        return -1;
    }
};
//作者:jyd

此时时间O(n),空间O(1)

剑指 Offer 04. 二维数组中的查找

行递增,列递增,最小元素在左上角,最大元素在右下角,可以这样解决,从右上角出发向左,当该位置值大于target时向左,当该位置小于target时向下,为什么这么想呢?因为将该矩阵旋转45°就变成了一颗二叉搜索树了。

当然从左上角也可以走,就是稍微麻烦点

class Solution {
public:
    bool findNumberIn2DArray(vector>& matrix, int target) {
        if(!matrix.empty()){//判断vector是否为空
		
        int row = matrix.size()-1;//行数
        int col = matrix[0].size();//列数
        int i = 0;
        int j = col-1;
        while(i <= row && j >= 0){
            if(target==matrix[i][j]){
                return true;
            }
            else if(matrix[i][j] > target){//向左
                --j;
            }
            else{	//向下
                ++i;
            }
        }
        }
        return false;
    } 
};

时间复杂度O(m+n)m为行数,n为列数,空间复杂度O(1)

剑指 Offer 05. 替换空格

解法一:

? 再开辟一个string来保存结果,将原字符串值依次判断再添加到新串中

class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for(char c:s){
            if(c==' '){
                res+="%20";
            }else{
                res+=c;
            }
        }
        return res;
    }
};

时间上遍历一次O(n),空间上开辟了一个新的串O(n)

解法二:

? 不用开辟一个新的string,而是在已有的string后面再加上需要扩充的空间

? 需要扩充的空间为:空格数*2

? 然后再利用双指针,从后向前扫,普通字符直接修改,空格则连续修改3个位置

class Solution {
public:
    string replaceSpace(string s) {
    int length = s.length();
    int count = 0;//替换之后的长度
    for(int i=0;i= 0 && high > low)
    {
        if(s[low] == ' '){//如果是空格的话
            s[high--] = '0';
            s[high--] = '2';
            s[high--] = '%';
        }else{
            s[high--] = s[low];
        }
        --low;
    }
    return s;
    }
};

该方法应为没有开辟新的串所以空间上为O(1)

剑指 Offer 06. 从尾到头打印链表

方法一:

使用vector的insert方法,可以直接实现链表的头插法,但是缺点是运行的时间复杂度有点高,因为每次都要实现数组元素的移动

class Solution {
public:
    vector reversePrint(ListNode* head) {
        vectorres;
        while(head!=NULL){
            res.insert(res.begin(),head->val);//头插法
            head = head->next;
        }
        return res;
    }
};

时间O(n^2)空间O(n)

方法二:

使用了C++算法库中的reverse方法,直接实现逆置

class Solution {
public:
    vector reversePrint(ListNode* head) {
        vector res;
        while(head) {
            res.push_back(head->val);
            head = head->next;
        }
        reverse(res.rbegin(), res.rend());
        return res;
    }
};

复杂度:reverse()函数无返回值,时间复杂度O(n)

方法三:

只要设计到逆序,FILO这种问题自然想到用桟或者递归,本题将元素依次插入桟中,再出栈即实现了逆置

class Solution {
public:
    vector reversePrint(ListNode* head) {
        stacks;//定义一个桟
        while(head){
            s.push(head->val);//元素入栈
            head = head->next;
        }
        vectorres(s.size());//避免动态扩容的开销
        for(auto it=res.begin();it!=res.end();it++){
            *it = s.top();
            s.pop();
        }
        return res;
    }
};

出入桟都是O(1),总时间复杂度为O(n),空间上开辟了桟和vector也是O(n)

剑指 Offer 09. 用两个栈实现队列

桟是先进后出,队列是先进先出,利用两个桟分别完成一次入栈即可实现队列,如ABCD进SA,再DCBA进SB,此时SB的出栈顺序已经是一个队列的顺序了,需要注意的是,只有出栈时我们才需要将SA移动至SB

class CQueue {
public:
    stack stack1;
    stack stack2;
    CQueue() {}
    
    void appendTail(int value) {
        stack1.push(value);
    }
    
    int deleteHead() {
        if (stack1.empty()) return -1;
        
        while (!stack1.empty()){ // SA -> SB	模拟队列
            int tmp = stack1.top();
            stack1.pop();
            stack2.push(tmp);
        }
        // 删除栈顶元素(即队首出队)
        int res = stack2.top();
        stack2.pop();
        while (!stack2.empty()){ // SA <- SB	放回SA
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        return res;
    }
};

当碰到连续删除时,上面的代码每次都需要再把SB拷贝回SA,这时我们可以在插入时再考回SA

class CQueue {
public:
    stack stack1;
    stack stack2;
    CQueue() {}
    
    void appendTail(int value) {
        while (!stack2.empty()){ // 1 <- 2
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        stack1.push(value);
    }
    
    int deleteHead() {
        if (stack1.empty()&&stack2.empty()) return -1;
        while (!stack1.empty()){ // 1 -> 2
            int tmp = stack1.top();
            stack1.pop();
            stack2.push(tmp);
        }
        // delete head
        int res = stack2.top();
        stack2.pop();
        return res;
    }
};

时间复杂度:对于插入和删除操作,时间复杂度均为 O(1)O(1)。插入不多说,对于删除操作,虽然看起来是 O(n)的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 stack2 一次」,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)O(1)。

空间复杂度:O(n)O(n)。需要使用两个栈存储已有的元素。

剑指 Offer 10- I. 斐波那契数列

老生常谈的一道题了属于是,后一次的结果由前两次结果得出

解法一:我的解法

class Solution {
public:
    int fib(int n) {
        //递归
        if(n==0) return 0;
        if(n==1) return 1;
        int f1=0,f2=1,res;
        for(int i=2;i<=n;i++){
            res = (f1 + f2)% 1000000007;//后一次结果
            f1 = f2;	//分别更新前两次结果
            f2 = res;
        }
        return res ;
    }
};

!!!解法二:记忆化递归

一点理解:如果不使用记忆数组,那么我们上一次计算得到的值计算机其实并不会保留,再往下算时又需要再计算一次,这样就增加了许多重复的工作

当我们引入记忆数组后,上一次的值保存在cache[n-1],cache[n-2]中,再计算n时,直接根据n-1和n-2中的值可以直接得出结论

//递归解决子问题。但要注意的是递归时会产生大量重复计算,可能会超时,所以使用一个记忆数组避免重复计算。
class Solution {
public:
    int cache[101];
    int fib(int n) {
        if(n < 2){
            return n;
        }
        if(cache[n] != 0) return cache[n];
        cache[n] = (fib(n-1) + fib(n-2)) % (int)(1e9 + 7);
        return cache[n];
    }
};

时空复杂度都是O(n)

剑指 Offer 10- II. 青蛙跳台阶问题

和上面的fib基本一模一样

class Solution {
public:
    int numWays(int n) {
        if(n==0||n==1)    return 1;
        int res,step1=1,step2=1;
        for(int i=2;i<=n;i++){
        res = (step1+step2)%1000000007;
        step1 = step2;
        step2 = res;
        }
        return res;
    }
};

时间上O(n),空间上O(1)

剑指 Offer 11. 旋转数组的最小数字

本题主要想考察的就是二分的方法

解法一:直接for循环O(n)

class Solution {
public:
    int minArray(vector& numbers) {
        int min = -2147483647;
        min = numbers.front();
        for(auto it=numbers.begin();it!=numbers.end();++it){
            if((*it)

解法二:二分法

如果mid大于high代表mid的右边是从左边旋转过去的,最小的在右边故 low = mid +1,否则在左边

class Solution {
public:
    int minArray(vector& numbers) {
        int low=0 , high = numbers.size()-1;
        while(lownumbers[high]) low = mid + 1;
            else if (numbers[mid]

剑指 Offer 15. 二进制中1的个数

解法一:逐位想与再统计次数

理解:用C++的位运算,每次将该数右移一位(保证无符号),然后和1相与,结果为1代表该位为1,再统计个数

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res=0;
        for(int i=0;i<=31;i++){
            if( ((n>>i)&1)==1)  res++;
        }
        return res;
    }
};

解法二:

用n不断&n-1,直到为0即可统计出0的个数

为何可以这样算呢?原因很简单,每减一之后最右边的1就会被抵消,比如1在最低位,减1后变0,相与后抵消了最低位的1,假如1在次低位:10减1后为01 01与10相与后为00这样次低位的1页被抵消掉了。

这让我想起了补码的快速计算:从右向左找到第一个1,左侧取反右侧不变,因为这个1一定是低位进上来的

count=0
while(k){
        k=k&(k-1);
        count++;
}

流程大概是这样的

231. 2 的幂(和上面类似的一题)

解法一:

? 问题还是要全面的考虑,一开始的思路错的太离谱了,边界条件都没搞明白,好不容易整明白了,又没仔细看数据范围,测试数据中有负数的,所以一定要转成unsigned int才好使

class Solution {
public:
    bool isPowerOfTwo(int n) {
        int count=0;
        n = (unsigned)n;
        if(n>0){
            while(n){
                n = n&(n-1);//算出1的个数
                count++;
            }
        }
        if(count==1)    return true;
        return false;
    }
};

解法二:继续优化

解法一存在一个小问题,就是进行了太多次的判断,其实没必要,只需要进行一次判断就可以得出结论,只要n&(n-1)不全为0,则代表一定不是2的整数次幂

class Solution {
public:
    bool isPowerOfTwo(int n) {

    return n > 0 && (n & (n - 1)) == 0;

    }
};

为什么一次就能判断呢?因为如果你是二的次幂,那么你必定只有1个1,即与n-1相与一定为0