lettcode解题记录(一)

前言

今年要准备夏令营机考,因此决定在leetcode上刷题。但根据自己以前的经验,往往刷了题之后就不再去回看,导致某些题目仍然会遗忘,因此决定边刷题边用博客记录下来,加深印象!我的题目是leetcode中文网站上的,关于题目具体描述和输入输出可以参考(https://leetcode-cn.com/problemset/all/)。代码都是自己写的,也许不是最巧妙的,但是是自己觉得最好理解的解题方式。话不多说,开始刷题!


1、两数之和

题目描述:
给一个整数数组,找出其中和为某个特定值的两个数。

题目虽然不难,但还是有几种方法可以解决!

解题思路

思路一

暴力搜寻,用两次循环来实现,时间代价是 O ( N 2 )

代码如下

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int s = nums.size();
        vector<int> res;
        for(int i=0;i<s-1;i++){
            for(int j = i+1;j<s;j++){
                if(nums[i] + nums[j] == target){
                    res.push_back(i);
                    res.push_back(j);
                }
            }
        }
        return res;

    }
};

思路二

第二种方法是先对数组进行排序,然后用两个指针head和tail,一个从头开始,一个从尾巴开始,每次 n u m [ h e a d ] + n u m [ t a i l ] 的值与target的关系,如果相等直接就得到结果,如果和大于target,tail就往前走,如果小于target,head就往后走,这样只需遍历一遍,总的时间代价是排序的 O ( N l o g N ) 。由于输出要求是两个数的在数组中的位置,可以定义一个数据结构来存储每个值和它对应的位置

代码如下

class Solution {
private:
    struct node{
        int val;
        int ind;
        node(int v,int i){
            val = v;
            ind = i;
        }
        bool operator <(const node & other){
            return this->val < other.val;
        }
    };

public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        vector<node> sort_nums;
        for(int i=0;i<len;i++){
            sort_nums.push_back(node(nums[i],i));
        }
        sort(sort_nums.begin(),sort_nums.end());
        int head = 0, tail = len-1;
        vector<int> res;
        while(head<tail){
            int tmp = sort_nums[head].val+sort_nums[tail].val;
            if(tmp == target){
                res.push_back(sort_nums[head].ind);
                res.push_back(sort_nums[tail].ind);
                return res;
            }
            if(tmp>target){
                tail -= 1;
            }
            if(tmp<target){
                head += 1;
            }
        }

    }
};

第三种思路

利用hash表来存储每个值所需要的值,这种方法很巧妙,利用了hashmap查询时间为 O ( 1 ) 的特性。c++中的hash表是STL中的unordered_map

代码如下

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        unordered_map<int,int> hashMap;
        int tmp;
        for(auto i=0;i<len;i++){
            tmp = target - nums[i];
            unordered_map<int,int>::iterator iter = hashMap.find(tmp);
            //找到了对应的值
            if(iter != hashMap.end()){
                return vector<int>({iter->second,i});
            }
            hashMap[nums[i]] = i;
        }


    }
};

2、两数相加

题目描述:

给定两个非空单列表,求其和,返回也是一个但列表,对数字的存储是逆序的。题目不难,主要 是列表的操作需要细心,我自己实现的代码如下:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry = 0;
        int sum = l1 -> val + l2 -> val + carry;
        ListNode * head = new ListNode(sum % 10);
        ListNode * tmp = head;
        carry = sum / 10;
        l1 = l1 -> next;
        l2 = l2 -> next;
        int val1 = 0;
        int val2 = 0;
        //只要有一个不为null就继续进行下去
        while(l1 || l2 || carry){
            //此处l1和l2如果为NULL就保持仍为NULL
            val1 = l1 ? l1 -> val : 0;
            val2 = l2 ? l2 -> val : 0;
            sum = val1 + val2 + carry;
            tmp -> next = new ListNode(sum % 10);
            carry = sum / 10;
            l1 = l1 ? l1 -> next : NULL;
            l2 = l2 ? l2 -> next : NULL;
            tmp = tmp -> next;
        }
        return head;
    }
};

3、无重复字符最长子串

题目描述

给一个字符串,找出不含有重复字符的子串长度,例如abcabbca的最长无重复子串就是abc和
bca、cab,其长度均是3,所以最长子串长度是3。

解题思路

思路一

最简单的方法当然是直接暴力(这也是找不到好的解决办法的时候的一个不错的选择,可以得一
部分分),这题的暴力是枚举所有的子串,然后判断是否有重复字符,这种方法时间代价是
$O(N^3)$,往往是不能通过全部测试样例的

代码如下:

class Solution {
private:
    bool isLegal(const string &subStr){
        unordered_map<char,int> temp;
        unordered_map<char,int>::iterator iter;
        for(int i = 0;i<subStr.length();i++){
            iter = temp.find(subStr[i]);
            //有重复字符
            if(iter != temp.end()){
                return false;
            }
            temp[subStr[i]] = 0;
        }
        return true;
    }

public:
    int lengthOfLongestSubstring(string s) {
        string subStr;
        int len = s.length();
        if(s == "") return 0;
        if(len == 1) return 1;
        int max = 1;
        for(int i = 0; i < len ; i++){
            int leLen = len - i ;
            for(int j = 1;j <= leLen; j++){
                subStr = s.substr(i,j);
                if(isLegal(subStr)){
                    max = max > j ? max : j;
                }
            }
        }
        return max;
    }
};

运行结果是有两个样例通不过,看了一下发现没通过的测试样例也不是很长,因此需要时间复杂度更小的方法

思路二

在从 i j 1 的子串中,如果这个子串已经是证明没有重复字符的,那么我们就可以把 s [ j ] 这个字符加入到子串中,这个新的子串也是没有重复字符的,但如果s[j]已经是在子串中,假设 s [ k ] == s [ j ] ( i <= k < j ) ,那么就可以直接把i变成 k + 1 ,因为如果 i 不跳过 k 的话,从i到后面的 j 仍会有重复的字符。这个从 i j 构成了一个窗口,窗口内的就是没有重复字符的子串

代码如下

class Solution {
private:
    //找到字符的位置
    int findChar(string subStr, char c){
        for(auto i=0;i<subStr.length();i++){
            if(subStr[i] == c){
                return i;
            }
        }
        return -1;
    }

public:
    int lengthOfLongestSubstring(string s) {
        if(s == "") return 0;
        string subStr;
        int len = s.length();
        if(len == 1) return 1;
        int i =0, j = 1;
        int max = 1;
        while(i < len && j < len){
            subStr = s.substr(i,j-i);
            int index = findChar(subStr,s[j]);
            if(index == -1){
                if(j+1 == len){
                    return max > j - i + 1 ? max : j - i + 1;
                }else{
                    j = j + 1;
                }
            }else{
                max = max > j - i ? max : j -i;
                i = i + index + 1;
            }
        }
    }
};

这种方法可以通过样例,但是还可以改进,主要是查询效率不高,如果能够维护一个窗口的hash表就更好了,将字符的位置存为value,这样查询和找位置也方便很多。先判断是否有s[j]是否在窗口里面,在的话,直接取出value值即可。

代码如下

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (s == "") return 0;
        int len = s.length();
        if (len == 1) return 1;
        int i = 0, j = 1;
        int max = 1;
        unordered_map<char, int> set;
        unordered_map<char, int>::iterator iter;
        set[s[i]] = i;

        while (i < len && j < len) {
            iter = set.find(s[j]);
            //s[j]不在子串中
            if (iter == set.end()) {
                if (j + 1 == len) {
                    return max > j - i + 1 ? max : j - i + 1;
                }
                else {
                    set[s[j]] = j;
                    j = j + 1;
                }
            }
            else {
                int index = iter->second;
                max = max > j - i ? max : j - i;
                for (int k = i; k < index + 1; k++) {
                    set.erase(set.find(s[k]));
                }
                i = index + 1;
            }

        }
        return max;
    }
};

4、两个排序数组的中位数

题目描述

给两个规模为m和n的有序数组,求这两个数组的中位数,要求时间复杂度为O(log(m+n)),这个 题稍微有点复杂,先放这儿,下次再说。。。

5、最长回文子串

题目描述

找出某个字符串中的最长回文子串。

第一种方法当然是暴力解决,没啥好说的。

class Solution {
private:
    bool isLegal(string s) {
        int len = s.length();
        for (int i = 0; i<len / 2; i++) {
            if (s[i] != s[len - 1 - i]) return false;
        }
        return true;
    }
public:
    string longestPalindrome(string s) {
        int max = 1;
        int len = s.length();
        if(len == 1) return s;
        string subStr;
        char first = s[0];
        char _res [] = {first,};
        string res = string(_res);
        for (int i = 0; i<len; i++)
            //直接排除长度比max小的子串
            for (int j = i + max; j<len; j++) {
                subStr = s.substr(i, j - i + 1);
                if (isLegal(subStr)) {
                    max = max >= j - i + 1 ? max : j - i + 1;
                    res = subStr;
                }
            }
        return res;
    }
};

虽然过了测试样例,但毕竟耗时太长,复杂度有点高,为 O ( N 3 ) 。下面继续探讨

回文串的特点是左右对称的,因此我们可以考虑从某个字符串进行扩展生成回文子串,如对aba 左右各加一个c,就构成了新的子串cabac。但是子串的长度可以为偶数,也可以为奇数,因此 在基础串长度可以为1也可以为2,故而需要分开讨论。这种方法的时间复杂度为 O ( N 2 )

代码如下

class Solution {
public:
    string longestPalindrome(string s) {
        if (s == "") return "";
        int len = s.length();
        if (len == 1)return s;
        int max = 1;
        int i, j;
        string res = string({ s[0], });
        //基础串长度为1
        for (int k = 1; k<len - 1; k++) {
            i = k - 1;
            j = k + 1;
            while (i >= 0 && j < len) {
                if (s[i] == s[j]) {
                    if ((j - i + 1) > max) {
                        res = s.substr(i, j - i + 1);
                        max = j - i + 1;
                    }
                    i -= 1;
                    j += 1;
                }
                else {
                    break;
                }
            }
        }
        //基础串长度为2
        for (int k = 0; k<len - 1; k++) {
            if (s[k] == s[k + 1]) {
                res = max > 2 ? res : string({s[k],s[k+1]});
                i = k - 1;
                j = k + 2;
                while (i >= 0 && j < len) {
                    if (s[i] == s[j]) {
                        if ((j - i + 1) > max) {
                            res = s.substr(i, j - i + 1);
                            max = j - i + 1;
                        }
                        i -= 1;
                        j += 1;
                    }
                    else break;
                }
            }
        }
        return res;
    }
};

这种方法就快了很多,但仍然不是最快的解决办法,我看到网上还有一种 O ( N ) 的解决办法,没细看,点击这里


6、z字形变换

题目描述

将一个字符串按照给定的 行数排列成Z字形,例如将”PAYPALISHIRING”进行三行排列,结果如下

P   A   H   N
A P L S I I G
Y   I   R

然后从左往右读取字符,其结果就是”PAHNAPLSIIGYIR”,进行四行排列,结果就是

P     I    N
A   L S  I G
Y A   H R
P     I

结果是”PINALSIGYAHRPI”,题目给定字符串和行数,求这个输出的字符串

解题思路

我自己的想法是逐个计算每个字符在Z字形排列后的位置,因此先分析Z字形生成的规律。遍历原字符串,每numRows-1个字符之后,Z字形排列的方向发生改变。以行数为4为例,前三个数在第一条竖直线上,此时他们的纵坐标是一样的,都为零,而第4,5,6个字符则是行坐标递减,纵坐标递增,因此我们只需要用一个 dir 变量来表示当前变化的方向,用 r 和 c 来表示当前字符的坐标,并根据dir的值动态改变 r 和 c 的值。

代码如下:

class Solution {
public:
    string convert(string s, const int numRows) {
        if(numRows == 1) return s;
        const int len = s.length();
        char ** arr = new char *[numRows];
        for (int i = 0; i < numRows; i++) {
            arr[i] = new char[len];
            for (int j = 0; j < len; j++)
                arr[i][j] = '0';
        }
        //dir==0表示竖直方向,dir==1表示斜线方向
        //r — 横坐标, c — 纵坐标
        int r = 0, dir = 0, c = 0;
        for (int i = 0; i<len; i++) {
            if (!dir) {
                arr[r][c] = s[i];
                if (r == numRows - 1) {
                    dir = 1;
                    r -= 1;
                    c += 1;
                }
                else r++;

            }
            else {
                arr[r][c] = s[i];
                if (r == 0) {
                    dir = 0;
                    r += 1;
                }
                else {
                    c++;
                    r--;
                }
            }
        }
        string res = "";
        for (int i = 0; i<numRows; i++)
            for (int j = 0; j<len; j++) {
                if (arr[i][j] != '0') res += arr[i][j];
            }
        return res;


    }
}; 

行数为1时是特殊情况,直接返回原字符串即可。另外对拐角处的字符坐标容易出错。这种方法容易理解,但复杂度为 O ( N ) ,空间复杂度为 O ( n u m R o w s N ) ,其中 N 是字符串的长度

7、反转整数

题目描述

给一个 i n t 型整数,求出该整数的反转之后的数字,如123反转之后为321,-123反转之后为-321,如果反转后数字不在整数范围内 i n t 范围内,则返回零

解题思路

题目比较简单,求出每一位,逆序计算即可,主要是要考虑翻转后数字的范围问题,因此可用 l o n g l o n g 类型来存储数字,便于比较,计算指数用的是 c m a t h 库里面的 p o w 函数

代码如下

class Solution {
public:
    int reverse(int x) {
        if (x == 0) return 0;
        long long max = pow(2, 31) - 1;
        long long min = -pow(2, 31);
        int left, mode;
        vector<int> temp;
        long long res = 0;
        long long carry = 1;
        if (x > 0) {
            left = x / 10;
            mode = x % 10;
            while (left > 0 || mode > 0) {
                temp.push_back(mode);
                mode = left % 10;
                left /= 10;
            }
            for (int i = temp.size() - 1; i >= 0; --i) {
                res += temp[i] * carry;
                carry *= 10;
            }
            if (res > max) return 0;
            return res;
        }
        else {
            left = -x / 10;
            mode = -x % 10;
            while (left >0 || mode > 0) {
                temp.push_back(mode);
                mode = left % 10;
                left /= 10;
            }
            for (int i = temp.size() - 1; i >= 0; i--) {
                res += temp[i] * carry;
                carry *= 10;
            }
            if (-res < min) return 0;
            return -res;
        }


    }
};

8、字符串转整数

题目描述

将字符串转为整数,或从字符串里面提取出整数,要求第一个非空字符必须为+,-,或者数字,否则无法提取。

解题思路

这题不难,主要在于对字符串的多种形式需要考虑完备。比如”+-131wera”就当作无法提取,还有考虑全零的数如”000000”以及”+000321”等类型的字符串类型,感觉就是比较麻烦,对算法没什么要求,总体来看题目没啥意义。

代码如下

class Solution {
private:
    bool isDigit(char c) {
        return c >= '0' && c <= '9';
    }
public:
    int myAtoi(string str) {
        int len = str.length();
        vector<char> res;
        //findNum判断是否可以匹配
        bool findNum = false;
        const long long MAX = pow(2, 31) - 1;
        const long long MIN = -pow(2, 31);
        for (int i = 0; i<len; i++) {
            if (!findNum && str[i] != ' ' && !isDigit(str[i]) && str[i] != '-' && str[i] != '+') return 0;
            if (findNum && !isDigit(str[i])) break;
            if (isDigit(str[i])) {
                findNum = true;
                res.push_back(str[i]);
            }
            if ((str[i] == '-' || str[i] == '+') && !findNum) {
                findNum = true;
                res.push_back(str[i]);
            }
        }
        if (res.empty()) return 0;
        long long temp = 0;
        long long carry = 1;
        if (isDigit(res[0])) {
            int begin = -1;
            //对于可能第一位为0的数字,需要去除掉
            for (int i = 0; i < res.size(); i++) {
                if (res[i] != '0') {
                    begin = i;
                    break;
                }
            }
            if (begin == -1) return 0;
            if (res.size() - begin > 10) return MAX;
            for (int i = res.size() - 1; i >= begin; --i) {
                temp += (res[i] - '0') * carry;
                carry *= 10;
            }
            if (temp > MAX) return MAX;
            return temp;
        }
        else {
            int begin = -1;
            //对于可能第一位为0的数字,需要去除掉
            for (int i = 1; i < res.size(); i++) {
                if (res[i] != '0') {
                    begin = i;
                    break;
                }
            }
            if (begin == -1) return 0;
            if (res.size() - begin > 11) return res[0] == '+' ? MAX : MIN;
            carry = 1;
            for (int i = res.size() - 1; i >= begin; --i) {
                temp += (res[i] - '0') * carry;
                carry *= 10;
            }
            if (res[0] == '+') return temp > MAX ? MAX : temp;
            return -temp < MIN ? MIN : -temp;
        }


    }
};

10、回文数

题目说明

判断一个数是否是回文数

解题思路

首先最简单的方法是用最基本的判断方法:即回文是对称的。因此可逐位求出个位数,然后判断是否对称。这种方法所用时间复杂度为 O ( l o g 10 x ) ,空间复杂度也为 O ( l o g 10 x ) ,应该算是一种不错的方法。此外可以排除一些基本的情况,即负数直接返回false,小于十的数直接返回true。
class Solution {
public:
    bool isPalindrome(int x) {
        if(x < 0) return false;
        if(x < 0) return true;
        int carry = 1;
        vector<int> temp;
        int left = x / 10;
        int mode = x % 10;
        if(left == 0) return true;
        temp.push_back(mode);
        while(left > 0){
            mode = left % 10;
            left /= 10;
            temp.push_back(mode);
        }
        int i = 0, j = temp.size() - 1;
        while(i < j){
            if(temp[i] != temp[j]) return false;
            i++;
            j--;
        }
        return true;
    }
};

但是我看到网上有种空间复杂度更低的方法,即是通过反转一半的数字进行判断。比如123321我们从最右边开始,一次求出其对应位的数字,1 , 2, 3,然后计算成新的数字321与原数字移位之后剩下的数字(321)进行比较即可。关键是如何找到原数字的一半位数,可通过比较移位后剩下的数字与反转数字的大小,例如构成的12就比移两位剩下的值1233小,就可以证明还没有达到原位数的一半。这种方法的缺陷是整10的数不能适应这个规则,得单独讨论。

class Solution {
public:
    bool isPalindrome(int x) {
        if(x < 0) return false;
        if(x < 10) return true;
        if(x % 10 == 0) return false;
        int left = x;
        int right = 0;
        while(right < left){
            right = 10 * right + left % 10;
            left /= 10;
        }
        //位数为奇数和偶数的情况
        return left == right || left == right / 10;
    }
};

猜你喜欢

转载自blog.csdn.net/qq_36304617/article/details/81318887