• LeetCode刷题记录(一)


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

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

    解法1:使用unordered_map

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

    class Solution {
    public:
        int findRepeatNumber(vector<int>& nums) {
        unordered_map<int , bool> 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<int>& 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<vector<int>>& 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<length;i++){
            if(s[i]==' ')   count++;
        }
        int new_length = length + count*2;
        s.resize(new_length);//重新扩容
        int low = length;
        int high = new_length;  //指向最高位
        while(low >= 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<int> reversePrint(ListNode* head) {
            vector<int>res;
            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<int> reversePrint(ListNode* head) {
            vector<int> 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<int> reversePrint(ListNode* head) {
            stack<int>s;//定义一个桟
            while(head){
                s.push(head->val);//元素入栈
                head = head->next;
            }
            vector<int>res(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<int> stack1;
        stack<int> 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<int> stack1;
        stack<int> 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<int>& numbers) {
            int min = -2147483647;
            min = numbers.front();
            for(auto it=numbers.begin();it!=numbers.end();++it){
                if((*it)<min)   min = (*it);
            }
            return min;
        }
    
    };
    

    解法二:二分法

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

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

    剑指 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

  • 相关阅读:
    Intellij IDEA Java web 项目搭建
    Spring的AOP原理
    为何有DAO与Service层?为何先搞Dao接口在搞DaoImpl实现?直接用不行吗?
    DAO层,Service层,Controller层、View层协同工作机制
    浅谈service、DAO层引入(转)
    java书籍推荐转
    Java点滴-List<Integer> list; 中尖括号的意思
    Composer更新慢的终极解决方案-转
    laravel门面DB返回数组配置
    laravel构建联合查询
  • 原文地址:https://www.cnblogs.com/Truedragon/p/15915393.html
Copyright © 2020-2023  润新知