Leetcode刷题1:初级算法

本文深入探讨了C++编程中的多种算法实现,包括字符串反转、最大利润问题、移动零、加一、存在重复、两个数组的交集、有效数独、三数之和、去重复数字、数组旋转等,通过实际代码示例解析了双指针、哈希表、直方图等技术的应用。

同另一篇文章,不过markdown用起来更方便。

1.1 反转字符串

知识点:就是双指针,对称进行交换,当p>q时结束。很简单。
坑:c++ STL库掌握不牢。
vector矢量的end()迭代器不是最后一个元素的指针,end()-1才是。

void reverseString(vector<char>& s) {
    if(s.size()<1) return;
    reverse(s.begin(),s.end());
}

void reverseString1(vector<char>& s) {
    if(s.size()<1) return;

    auto iter = s.begin();
    //auto iter2 = s.end();//错误,需要-1
    auto iter2 = s.begin()+s.size()-1;
    while(iter<=iter2){
        auto temp=*iter;
        *iter=*iter2;
        *iter2=temp;

        iter++;
        iter2--;
    }
}

void reverseString2(vector<char>& s) {
    int p=0;
    int q=s.size()-1;
    while(p<=q){
        char temp = s[p];
        s[p]=s[q];
        s[q]=temp;

        p++;
        q--;
    }
}

1.2 最大利润问题

记一笔,发现自己用了网易和微信两个leetcode账号;以后还是用微信号吧。

言归正传:3分钟想出算法;最大利润其实很简单,求连续两数的差分序列,将所有正数求和即得最大利润。

int maxProfit(vector<int>& prices) {
    int maxProfit = 0;
    if(prices.size()<1) return maxProfit;
    for(int i=0,j=i+1;i<prices.size()-1;i++,j++){
        if(prices[j]-prices[i]>0){
            maxProfit+=prices[j]-prices[i];
        }
    }
    return maxProfit;
}

今日就仅仅刷了一道题。
2020/2/29 晚

1.3 移动零

写这上边似乎有点浪费时间。还是记一下语法知识吧。

双指针;一个负责遍历,一个用于记录非零元素的个数k;将k个非零元素直接赋值到最前面,随后将k以后的元素全赋值为0。

    void moveZeroes(vector<int>& nums) {
        if(nums.size()<1) return;
        int q=0;
        for(int p=0;p<nums.size();p++){
            if(nums[p]!=0){
                nums[q]=nums[p];
                q++;
            }
        }

        for(;q<nums.size();q++){
            nums[q]=0;
        }
    }

暴力法:遇到0则使用erase擦除,并在最后面补0会导致超时。
注意:erase擦除之后会自动指向下一个元素的位置,如果擦除的是最后一个元素,则擦除后会指向end();此时若再++则会越界,出现也指针。下面写法正确,没擦除则移动,擦除则不再移动。但以下代码依然超时。

void moveZeroes1(vector<int>& nums) {
	if(nums.size()<1) return;
	for(auto iter=nums.begin();iter<nums.end();){
	    if(*iter==0){
	        nums.erase(iter);
	        nums.push_back(0);
	    }else iter++;
	}
}

1.4 加一

主要就是考虑进位问题。也就是说从末位开始进位flag=1,如果该位再产生进位,则flag不变,将该位设为0;否则将该位加flag就完了,并且再将flag设为0,break;即只要没产生进位,则退出,并且flag=0未能进位而结束。直到判断完第一位,如果第一位产生进位,则第一位将被设置成0;flag仍然为1,此后while结束。判断第一位是否产生进位,是的话在结果里面先弹入1,再接上改变后的数。Over,还是调了10分钟的。
注意:涉及到迭代器要倒着走完到第一位,此时不建议采用迭代器写法。
2020/3/3

 vector<int> plusOne(vector<int>& digits) {
        vector<int> result;
        if(digits.size()<1) return result;
        
        // 判断有无进位,进位则将该位设为0,进位设为1;
        // 若无进位,则加1;并将flag设为0;直到走到第一位     
        int i = digits.size() -1;
        int flag=1;
        while(i>=0){
            if(digits[i]+  flag <10){
                digits[i] += flag;
                flag=0;
                break;
            }else{
                digits[i] = 0;
            }
            i--;
        }
        
        if(flag==1){
            result.push_back(1);
        }
        for(auto ele:digits){
            result.push_back(ele);
        }
        return result;
    }

1.5 是否存在重复

EASY! 哈希map解决一切问题。不多比比直接上代码。

bool containsDuplicate(vector<int>& nums) {
    if(nums.size()<2) return false;
    // 建立一个哈希表,key为序号,value为值
    map<int,int> hash;
    for(int i=0;i<nums.size();i++){
        if( hash.find(nums[i])!=hash.end() ){
            // 找到了,则存在重复
            return true;
        }else {
            // 没找到,插入
            //hash[nums[i] ]++;
            hash.insert(pair<int,int>(nums[i],i));
        }
    }
    return false;
}

1.6 两个数组的交集

解答:暴力的搜索法。分别对两个数组建立直方图hist1,hist2;在hist2中查找hist1中的每一项,并求该项个数的最小值,输入结果即可。
感觉我是真的再怎么努力也赶不上YZT大神了,必须要清楚地认识到自己的境况。但是接下来的几个月,必须打起十二分精神来备战。
注意:统计直方图的map插入操作 map[key]++ 就够了。得仔细学一下STL库。
以下为解法,大概写了3分钟,基础不牢调了5分钟。

    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> result;
        // 1-构建两个直方图哈希表,
        // 针对nums1中的每一个元素,直接在nums2中进行查找,如果找到则返回该数即其个数
        map<int,int> hist1 = getHistgrom(nums1);
        map<int,int> hist2 = getHistgrom(nums2);
        
        map<int,int>::iterator iter = hist1.begin();
        for(;iter!=hist1.end();iter++){
            auto findIter = hist2.find( iter->first );
            if( findIter != hist2.end() ){
                int count = (iter->second<=findIter->second) ? iter->second : findIter->second;
                for(int i=0;i< count;i++){
                    result.push_back( iter->first );
                }  
            }
        }   
        return result;
    }
    
    // 统计数组直方图
    map<int,int> getHistgrom(vector<int> nums){
        map<int,int> hash;
        for(auto ele:nums){
            hash[ele]++;
        }
        return hash;    
    }

写点儿牢骚话…

读研这一年半,感觉自己学的东西太多太杂了,倒是没能有非常擅长之领域,若非要说,怕是传统的图像处理了。做过MFC,不系统的学了c++,做过安卓开发,懂一点儿JAVA皮毛和android app开发,学过keras做过DL也做过无人机控制与SITL仿真,文章也看了不少。但是到头来吧,可以说一无所获,比较失败,学过的诸多知识慢慢被遗忘,对于以后毫无裨益。究其原因,纵然实验室课题繁多,可感觉还是自己的时间没规划好,剩下的时间不多,系统的学习一下若干个方面,选一方向钻研,要有深度而不再追求广度了。

  • C++;
  • 数据结构与算法;
  • ROS开发;
  • 沾点DL与SLAM
  • 图像处理老本行。
    不管走到哪一步,心态平和尽力就好,就这样吧。

2020/3/4晚于家中

1.7 有效数独

我的方法很简单粗暴,三个条件逐步判断。将每一行、每一列、每一格子的有效数字转化成数组,然后判断该数组是否存在重复,存在重复则无效。
注意:70行代码还是写了挺久的,主要是对字符串转数字用得少,查了半天用了个ss的方法,atoi失效,暂时不知道为啥(const??)。反正这题还是不难的。不过我这写法时间复杂度应该不低,老clear数组效率不咋地,可以用int[9],然后把’.'设置成0或者-1这样子,修改一下查重复函数。

    bool isValidSudoku(vector<vector<char>>& board) {
        // 分别按行、列、格子将区域划入vector<int>中,没有数就不加入
        vector<int> temp;
        // 1-按行
        for(int i=0;i<9;i++){
            // 将数存入temp
            temp.clear();
            for(int j=0;j<9;j++){
                if(board[i][j]!='.'){
                    //int num =atoi( board[i][j] );//失效,未解
                    temp.push_back(char2int(board[i][j]));
                }
            }
            // 判断该行有效数值是否成立?
            if( !isValidSet(temp)) return false; // 不成立则退出
        }
    
        // 2-按列
        for(int i=0;i<9;i++){
            temp.clear();
            for(int j=0;j<9;j++){
                if(board[j][i]!='.'){
                    temp.push_back( char2int(board[j][i]) );
                }
            }
            if( !isValidSet(temp)) return false; // 不成立则退出
        }
    
        // 3-按格子
        temp.clear();
        for(int i=0;i<7;i+=3){
            for(int j=0;j<7;j+=3){
                temp = getBlock(board,i,j);
                if( !isValidSet(temp)) return false; // 不成立则退出 
            }
        }
          
        return true;
    }
    
    // 获取格子
    vector<int> getBlock(vector<vector<char>>&board,int i,int j){
        vector<int> block;
        for(int m=0;m<3;m++){
            for(int n=0;n<3;n++){
                if(board[i+m][j+n]!='.'){
                    block.push_back( char2int(board[i+m][j+n]) );
                }
            }
        }
        return block;
    }
    
    // 字符转数字
    int char2int(char s){
        int result;
        stringstream ss;
        ss<<s;
        ss>>result;
        return result;
    }
    
    // 判断一个数组是否存在重复
    bool isValidSet(vector<int> nums){
        map<int,int> hash;
        for(auto ele:nums){
            auto findIter = hash.find(ele);
            if(findIter!=hash.end()){
                return false;//找到了存在重复
            }else hash[ele]++;//没找到,则插入   
        }
        return true;
    }

颓废的一天,不能这样下去了。睡觉。明天早起好好干。2020/3/5 23:46

1.8 三数之和

记录一下双指针的用法。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        if(nums.size() < 3) return res;
        sort(nums.begin(),nums.end()); //先排序
        for(vector<int>::iterator it = nums.begin(); it != nums.end()-2;){
            int tmp = *it;
            if(tmp > 0) break;
            int target = 0 - tmp;
            vector<int>::iterator left = it+1;
            vector<int>::iterator right = nums.end()-1;
            while(left < right){
                if(*right < 0) break;  //如果右边小于0,break
                if(*left + *right < target){
                    int v= *left;
                    while(left != right && *left == v) left++; //跳过相等的元素
                }else if(*left + *right > target){
                    int v= *right;
                    while(left != right && *right == v) right --;//跳过相等的元素
                }else{
                    vector<int> tmp_res{tmp,*left,*right}; //保存结果
                    res.push_back(tmp_res);
                    int v= *left;
                    while(left != right &&  *left == v) left++;//跳过相等的元素
                    v= *right;
                    while(left != right &&  *right == v) right --;//跳过相等的元素
                }  
            }
            while(it != nums.end()-2 && *it == tmp) it++;//跳过相等的元素
        }
        return res;
    }
};

//作者:hou-yong-sheng
//链接:https://leetcode-cn.com/problems/3sum/solution/c-shuang-zhi-zhen-by-hou-yong-sheng/
//来源:力扣(LeetCode)
//著作权归作者所有。侵删。

1.9 去重复数字

总结:这道题我开始以为得把所有重复数字全擦除掉,结果官方答案只要确定非重复数字的个数;区别显而易见。

注意:矢量迭代器擦除可能会导致迭代器失效,出现野指针。

//
// Created by wbzhang on 2020/2/27.
//

#ifndef ALGORITHMS_REMOVEDUPLICATES_H
#define ALGORITHMS_REMOVEDUPLICATES_H

#include "commonHeaders.h"

/** 原创;暴力法1--双指针
* 思路:鉴于数组已经排好顺序,重复数字必然连续,即对两位相邻数字擦除后者即可。
* 取指针p和q=p+1;若两数相等,则擦除q,此时下一轮循环时p将挪到下一位,因此需要将指针p向前挪一位(防止连续3个以上相同的串的情形失效)。
* 若两数不等,指针p向后挪一位。重复以上过程,直到指针p挪到倒数第二位。
*/

int removeDuplicates(vector<int>& nums) {
    if(nums.size()<2) return nums.size();
    for(vector<int>::iterator iter=nums.begin();iter!=nums.end()-1;++iter){
        auto iter2=iter+1;
        if (*iter==*(iter2)){
            //cout<<"before:"<<*iter2<<endl;
            nums.erase(iter2);
            //cout<<"after:"<<*iter2<<endl;
            iter--;// 删除后自动指向下一位,给复位
        }
    }

    return nums.size();
}

// 暴力法2
int removeDuplicates1(vector<int>& nums) {
    if(nums.size()<2) return nums.size();
    cout<<*(nums.begin()+nums.size()-1)<<endl;
    for(int i=0;i<nums.size()-1;++i){
        int j=i+1;
        if (nums[i]==nums[j]){
            nums.erase(nums.begin()+j);
            i--;
        }
    }
    return nums.size();
}

// 该解存在冗余。
// 未完成,迭代器失效
int removeDuplicates11(vector<int>& nums) {
    if(nums.size()<2) return nums.size();
    for(auto iter=nums.begin();iter!=nums.end()-1;++iter){
        //auto iter2=nums.end();
        auto iter2 = nums.begin()+nums.size()-1;
        while(iter2>iter){
            if (*iter==*(iter2)){
                nums.erase(iter2);//可能出现野指针?比如擦除了最后一项,iter2会变成野指针?
                //iter2--;
            }
            iter2--;
        }
    }
    return nums.size();
}


// 官方答案--双指针法
int removeDuplicates2(vector<int>& nums){
    int p=0,q=1;
    while(q!=nums.size()-1){
        if(nums[p]==nums[q]){
            q++;
        }else{
            nums[p+1]=nums[q];
            p++;
            q++;
        }
    }
    return p+1;
}

#endif //ALGORITHMS_REMOVEDUPLICATES_H

1.10 数组旋转

知识点:

1)数组旋转k次,当k>数组长度时,与旋转k%n次是同样的

2)位置i旋转k次后;nums[(i+k)%n]等于最初的nums[i]

3)矢量逆向reverse操作的是前闭后开区间。

// 1-原创,暴力法
/* 注意旋转k次与旋转k%nums.size()是一样的,因此可以简化运算量;
* 首先将k%=nums.size();然后直接将末尾的弹出,并插入到开头
*/
void rotate2(vector<int>& nums, int k) {
    if(k<1 || nums.size()<2) return;
    k%=nums.size();
    for(int i=1;i<=k;i++){
        int temp = nums.back();
        nums.pop_back();
        nums.insert(nums.begin(),1,temp);
    }
}

// 时间复杂度:O(k%nums.size()),空间复杂度O(1)


// 2-三次旋转
/* 将数组整体逆向排序;再将前k个数字逆向排序;再将后nums.size()-k个数字逆向排序
* 以上三步顺序可以适当调整。
*/

void rotate(vector<int>& nums, int k) {
       int len=nums.size();

       reverse(nums.begin(), nums.end() - k%len);
        //reverse翻转的是[begin,end)区间
       reverse(nums.end()- k%len,nums.end());
       reverse(nums.begin(),nums.end());
    }
};

void rotate(vector<int>& nums, int k) {
    if(k<1 || nums.size()<2) return;
    k%=nums.size();
    reverse(nums.begin(),nums.end());
    reverse(nums.begin(),nums.begin()+k);
    reverse(nums.begin()+k,nums.end());
}

// 时间复杂度:O(n),空间复杂度O(1)


// 3-额外数组,直接赋值
void rotate(vector<int>& nums, int k) {
    vector<int> nums2(nums);
    int len=nums.size();

    for(int i=0;i<len;i++){
        nums[(i+k)%len]=nums2[i];
    }       
}

// 时间复杂度:O(n),空间复杂度O(n)

写点小记

感觉自己的刷题策略似乎出了问题。没有系统学过数据结构和算法设计,去刷初级题的数组和字符串处理一般都能搞定;但是也仅限于此,接下来或许应该把c++的数据结构花点时间搞一搞,然后继续刷题。今天下了个leetcode app。
本帖到此为止,后续直接记录在leetcode上。
2020/3/6

2.0 验证回文串

在这里插入图片描述

思路分析:如题,验证回文串很容易使用首尾双指针解决。但是需要判断该字符是否为字母或者数字,此时需要用到c函数库中isalnum函数。isalnum参考链接,当isalnum(char c)当c为数字或字符时,返回非0值,否则返回0.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值