C++ : 力扣_Top(1-21)

C++ : 力扣_Top(1-21)

文章目录

  • C++ : 力扣_Top(1-21)
      • 1、两数之和(简单)
      • 2、两数相加(中等)
      • 3、无重复字符的最长子串(中等)
      • 4、寻找两个有序数组的中位数(困难)
      • 5、最长回文子串(中等)
      • 7、整数反转(简单)
      • 8、字符串转换整数 (atoi)(中等)
      • 10、正则表达式匹配(困难)
      • 11、盛最多水的容器(中等)
      • 13、罗马数字转整数(简单)
      • 14、最长公共前缀(简单)
      • 15、三数之和(中等)
      • 17、电话号码的字母组合(中等)
      • 19、删除链表的倒数第N个节点(中等)
      • 20、有效的括号(简单)
      • 21、合并两个有序链表(简单)


1、两数之和(简单)

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target){
        unordered_map<int,int> m;
        int size = nums.size();
        for(int i=0;i<size;++i){    
        // 此处不能用nums[i]作为判断条件,因为若是0就自动停止了
            int another = target-nums[i];
            if(m.find(another)!=m.cend()){  // 如果在map中
                return {m[another],i};
            }
            m.insert({nums[i],i});  
        }
        return {};
    }
};

思路:使用map,减少二次搜索的成本 O(n^2) -> O(nlogn)

执行用时 :4 ms, 在所有 cpp 提交中击败了99.76%的用户
内存消耗 :10 MB, 在所有 cpp 提交中击败了41.19%的用户


2、两数相加(中等)

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

/**
 * 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 len1 = 1, len2 = 1;     // 两个链表长度信息
        ListNode * p = l1, *q = l2;   // 定义新节点p,q
        
        while(p->next!=NULL){           
            ++len1; p=p->next;
        }
        
        while(q->next!=NULL){
            ++len2; q=q->next;
        } 

        // 若长度不同,则在后补零
        if(len1>len2){      
            for(int i=1;i<=len1-len2;++i){
                q->next = new ListNode(0);
                q=q->next;
            }
        }else{
            for(int i=1;i<=len2-len1;++i){
                p->next = new ListNode(0);
                p=p->next;
            }
        }

        p = l1; q = l2;  // p,q归位
        int c = 0;     // 进位符
        ListNode * l3 = new ListNode(-1);  // 保存结果的链表(返回值)
        ListNode * w = l3;  // 移动节点w
        int i = 0;   // 每位加和

        while(p!=NULL){
            i = c + p->val + q->val;  // 每位加和,包括进位
            w->next = new ListNode(i%10);  // 每位的结果
            c = (i>=10) ? 1 : 0;  // 是否进位
            
            w = w->next;
            p = p->next;
            q = q->next;
        }
        
        // 若最后一位还需进位
        if(c){
            w->next = new ListNode(1);
            w = w->next;
        }
        return l3->next;
    }
};

思路:需要考虑不等长情况补零,以及满十进位的问题;巧用指针!!

执行用时 :28 ms, 在所有 cpp 提交中击败了76.16%的用户
内存消耗 :10.2 MB, 在所有 cpp 提交中击败了91.83%的用户


3、无重复字符的最长子串(中等)

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

(子串是连续的一段,子序列可以是不连续的)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int j = 0, r = 0;
        
        //vector m;
        string m;    // 建议用string而非vector,利用的空间更小,链表的空间占用也较大
        
        if(s.empty()) return 0;
        
        else{
            for(char i : s){
                auto it = find(m.begin(),m.end(),i);
                if(it == m.end()){
                    ++j;
                    m.push_back(i);   // 这种插入是倒序的,要注意!
                }
                // 遇到重复字符
                else{
                    r = (r>=j) ? r : j;
                    m.erase(m.begin(),it+1);
                    m.push_back(i);
                    j = m.size();
                }
            }
            r = (r>=j) ? r : j;
        }
        return r;
    }
};

思路:(滑动窗口思想)涉及到容器中字符的查找,一开始想使用map或set,但因为涉及到删除最先插入的N个元素的顺序问题,map或set无法解决,故只能使用顺序容器,建议使用空间和速度更快的string;

使用vector:
执行用时 :16 ms, 在所有 cpp 提交中击败了67.35%的用户
内存消耗 :9.3 MB, 在所有 cpp 提交中击败了86.91%的用户

使用string:
执行用时 :12 ms, 在所有 cpp 提交中击败了81.38%的用户
内存消耗 :9 MB, 在所有 cpp 提交中击败了95.11%的用户

==============================================

另一种更好的解法(可以参考思路):
利用128个元素的向量(当做ASCII码的索引)

class Solution{
public:
    int findstr(string s)
    {
        //s[start,end) 前面包含 后面不包含
        int start(0), end(0), length(0), result(0);
        int sSize = int(s.size());
        vector<int> vec(128, -1);
        while (end < sSize)
        {
            char tmpChar = s[end];
            //仅当s[start,end) 中存在s[end]时更新start
            if (vec[int(tmpChar)] >= start)
            {
                start = vec[int(tmpChar)] + 1;
                length = end - start;
            }
            vec[int(tmpChar)] = end;

            end++;
            length++;
            result = max(result, length);
        }
        return result;
    }
};

执行用时 :4 ms, 在所有 cpp 提交中击败了99.17%的用户
内存消耗 :9.7 MB, 在所有 cpp 提交中击败了85.30%的用户


4、寻找两个有序数组的中位数(困难)

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        vector<int> num;
        for(int i : nums1){num.push_back(i);}
        for(int i : nums2){num.push_back(i);}
        sort(num.begin(),num.end());
        int y = num.size()%2;
        int s = num.size();
        if(y == 0){
            return (num[s/2-1]+num[s/2])*0.5;
        }else{
            return (num[(s+1)/2-1]);
        }
    }
};

思路:最简单的集中排序求解

执行用时 :24 ms, 在所有 C++ 提交中击败了57.08%的用户
内存消耗 :10.5 MB, 在所有 C++ 提交中击败了74.23%的用户

==============================================

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length1 = nums1.size();
        int length2 = nums2.size();
        int i,j,k;
        vector<int> temp(length1+length2, 0);
        for(i=0,j=0,k=0;i<length1&&j<length2;++k)
        {
            if(nums1[i]<nums2[j])
            {
                temp[k] = nums1[i];
                i++;
            }
            else
            {
                temp[k] = nums2[j];
                j++;
            }
        }
        while(i<length1)        
            temp[k++]=nums1[i++];
        while(j<length2)
            temp[k++]=nums2[j++]; 
        if((length1+length2)%2 ==1)      
            return temp[(length1+length2)/2];    
        else
            return (temp[(length1+length2)/2]+temp[(length1+length2)/2-1])/2.0;
    }
};

思路:使用了归并排序进行排序,然后再取中位数;

执行用时 :28 ms, 在所有 C++ 提交中击败了35.04%的用户
内存消耗 :9.8 MB, 在所有 C++ 提交中击败了85.00%的用户


5、最长回文子串(中等)

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.empty()) return s;
        string result = s;
        auto iter = s.begin();
        auto left = iter, right = iter;
        int len = 0, maxlen = 0;
        while(iter!=s.end()){
            if(iter>=s.begin()&&iter+1!=s.end()){ // 单中心,优先往左右扩展
                left = iter - 1;
                right = iter + 1;
                len = 1;
                for(;left>=s.begin()&&right<s.end();--left,++right){
                    if(*left==*right){
                        len = len + 2;
                    }
                    else{
                        break;
                    }
                }
                if(len > maxlen){
                    result.assign(++left,right);
                    maxlen = len;
                }
            }
            if(iter+1!=s.end()&&*iter==*(iter+1)){ // 中间两数相等
                len = 2;
                left = iter - 1;
                right = iter + 2;
                for(;left>=s.begin()&&right<s.end();--left,++right){
                    if(*left==*right){
                        len = len + 2;
                    }
                    else{
                        break;
                    }
                }
                if(len > maxlen){
                    result.assign(++left,right);
                    maxlen = len;
                }
            }
            ++iter;
        }
        return result;
    }
};

思路:中心扩展法,遍历一次,最差复杂度为O(n^2),空间复杂度O(1);注意区分中间有一个字符和中间有两个字符的情况;


7、整数反转(简单)

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31−1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

class Solution {
public:
    int reverse(int x) {
        string s = to_string(x); // 转换为string
        int num = 0, sign = 1; // 结果num,正负号sign
        if(*s.begin()=='+'||*s.begin()=='-'){
            if(*s.begin()=='-') sign = -1; // 如果为负
            s.assign(s.begin()+1,s.end()); 
        }
        std::reverse(s.begin(),s.end()); // 翻转字符串
        try{
            num = stoi(s); // 尝试执行string转int
        }catch(exception){
            return 0;  // 如果失败,返回0
        }
        return num * sign; // 不然返回结果
    }
};
// 一种更简单的循环计算方法
class Solution {
public:
    int reverse(int x) {
        int num = 0;
        while(x!=0){
            if(num>214748364||num<-214748364) return 0;
            num = num * 10 + x % 10;
            x = x / 10;
        }
        return num;
    }
};

思路:简单的题,灵活利用string的各种功能,使用了try/catch异常,注意泛型函数的使用;注:int的范围是-2147483648~2147483647.


8、字符串转换整数 (atoi)(中等)

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

class Solution {
public:
    int myAtoi(string str) {
        if(str.empty()) return 0;
        long int num = 0;
        int sign = 1;
        auto iter = str.begin();
        while(*iter==' '){ // 开头有空格
            ++iter;
        }
        if(iter!=str.end()&&(*iter=='+'||*iter=='-')){ // 有+-号
            if(*iter=='-') sign = -1;
            ++iter;
        }
        if(iter!=str.end()&&(*iter<='9'&&*iter>='0')){ // 如果这时第一个字符是数字
            while(iter!=str.end()){ // 遍历连续数字
                if(*iter<='9'&&*iter>='0'){ // 如果是数字
                    num = num * 10 + (*iter - '0');
                    if(sign*num>2147483647){ // 如果超出int范围
                        return INT_MAX;
                    }
                    else if(sign*num<-2147483648){
                        return INT_MIN;
                    }
                }
                else{  // 如果遇到别的字符
                    break;
                }
                ++iter;
            }
        }
        else{
            return 0;
        }
        return static_cast<int>(num * sign);
    }
};

思路:没啥难度,注意考虑周全;


10、正则表达式匹配(困难)

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

class Solution {
public:
    bool isMatch(string s, string p) {
        if(s.empty()&&p.empty()) return true; // 如果两数组为空,则匹配
        return isMatchCore(s,p,0,0); // 不然则进入递归
    }
    bool isMatchCore(string& s,string& p,int i,int j){
        if(i>=s.size()&&j>=p.size()) return true;  // 如果两个指针都到头了,则匹配
        if(i<s.size()&&j>=p.size()) return false; // 如果字符串没到头但模板到头了,则不匹配
        if(j+1<p.size()&&p[j+1]=='*'){ // 遇到x*形式
            if(s[i]==p[j]||(p[j]=='.'&&i<s.size())){ // 当前字符相等或者是.*形式时
                return isMatchCore(s,p,i,j+2) // 没有匹配,直接跳过
                    || isMatchCore(s,p,i+1,j+2) // 匹配最后一个字符
                    || isMatchCore(s,p,i+1,j); // 匹配当前一个字符
            }
            else{ // 当前字符就不匹配
                return isMatchCore(s,p,i,j+2); // 跳过c*段
            }
        }
        else{ // 当前不是x*模式(字符串可能已走完)
            if(s[i]==p[j]||(p[j]=='.'&&i<s.size())){ // 当前字符相等,或遇到.模式
                return isMatchCore(s,p,i+1,j+1);
            }
            return false;
        }
    }
};

思路:正则表达式匹配问题,最好的方法是动态规划,匹配当前部分,然后匹配后面的部分。利用递归解决比较方便,但复杂度较高;有空可以了解一下dp数组循环方法;


11、盛最多水的容器(中等)

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。
示例:输入: [1,8,6,2,5,4,8,3,7] 输出: 49

class Solution {
public:
    int maxArea(vector<int>& height) {
        if(height.empty()||height.size()==1) return 0; // 如果为空或只有一个值
        int left = 0, right = height.size()-1; // 定义左右两端指针
        int temp, max = 0; // 存放最大容量
        while(right>left){ // 遍历,直到到头
            // 计算当前最大容量,然后将两个指针中较短的那个向中间移动;注意最后的底长要加1,因为前面有自增量操作;
            temp = (height[left]<=height[right]?height[left++]:height[right--])*(right-left+1);
            max = temp>max?temp:max;
        }
        return max;
    }
};

思路:想法并不难,但不是很好想;左右两端指针,向中间移动;盛水容量只和当前两个指针中较短的那个有关,如果将较长的指针向中间移动,只会使得容量变得更小,所以每次移动较短的指针,这样可能会继续增大容量;


13、罗马数字转整数(简单)

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

class Solution {
public:
    // I 1  V 5  X 10  L 50
    // C 100  D 500  M 1000
    int romanToInt(string s) {
        if(s.empty()) return 0;
        int iter = 0;
        int sum = 0;
        while(iter!=s.size()){
            if(isLessThanNext(s,iter)){ // 如果遇到特殊情况IV、IX、XL、XC、CD、CM
                sum = sum + getnum(s[iter+1]) - getnum(s[iter]);
                iter = iter + 2;
            }
            else{
                sum = sum + getnum(s[iter]);
                iter = iter + 1;
            }
        }
        return sum;
    }
    bool isLessThanNext(string& s,int i){
        if(i+1!=s.size()){ // 当前下标i不是最后一字符
            if(getnum(s[i])<getnum(s[i+1])){
                return true;
            }
            else return false;
        }
        return false;
    }
    int getnum(char c){
        switch(c){
            case 'I' : return 1;
            case 'V' : return 5;
            case 'X' : return 10;
            case 'L' : return 50;
            case 'C' : return 100;
            case 'D' : return 500;
            case 'M' : return 1000;
        }
        return 0;
    }
};

思路:比较简单的题,只需要区分两种情况:之间加上当前字符表示的值、或判断当前字符小于下一个字符时,加上下一个字符减去当前字符的数字;


14、最长公共前缀(简单)

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if(strs.empty()) return ""; // 不到两个字符串
        if(strs.size()==1) return strs[0]; // 如果只有一个字符串,则返回自身
        int j = 0; // 公共前缀位数j
        string result; // 结果字符串
        bool isCommon; // 如果当前字符是公共字符
        while(j<strs[0].size()){ // 公共前缀位数j
            isCommon = true;
            for(int i=0;i<strs.size()-1;++i){ // 每个字符串i
                if(j>=strs[i].size()||j>=strs[i+1].size()||strs[i][j]!=strs[i+1][j]){
                    isCommon = false;
                    break;
                } // 若当前字符串的j位和下一个字符串的j位不相等或j位越界;
            }
            if(isCommon){ // 循环完毕,当前字符是公共字符
                result.push_back(strs[0][j]); // 输出该公共字符
                ++j; // 继续循环,不要忘了递增j
            }
            else{
                break;
            }
        }
        return result;
    }
};

思路:从每个字符串的第一个字符开始判断,如果他们都相等,则说明这个字符是公共前缀的字符;然后依次判断,直到出现第一个不是公共字符的前缀,则停止;


15、三数之和(中等)

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int> > result; // 结果数组
        if(nums.size()<3) return result; // 如果数组元素小于3个,直接返回空结果
        sort(nums.begin(),nums.end()); // 数组排序
        if(nums.front()>0||nums.back()<0) return result; // 如果全为负或者全为正,肯定无结果
        int temp, i = 0; // temp保存暂时的和,i为当前第一个数的下标
        int left, right; // 定义第二个数和第三个数的下标
        while(i<nums.size()&&nums[i]<=0){ // 当三数和的最小值都大于0时,和不可能等于0,循环结束
            left = i + 1;
            right = nums.size() - 1;
            while(left<right){ // 遍历,直到left和right相遇
                temp = nums[i] + nums[left] + nums[right];
                if(temp==0){ // 如果当前符合结果
                    result.push_back({nums[i],nums[left],nums[right]}); // 输出
                    ++left; // left右移
                    while(left<right&&nums[left]==nums[left-1]) ++left; // 排除当前的重复数字
                    --right; // right左移
                    while(left<right&&nums[right]==nums[right+1]) --right; // 排除当前的重复数字
                }
                else if(temp>0) --right;
                else ++left;
            }
            ++i;
            while(i<nums.size()&&nums[i]==nums[i-1]) ++i; // 一定要注意越界问题!
        }
        return result;
    }
};

思路:避免使用暴力搜索和扩展额外空间,该题可以先进行排序,然后根据排序后的数组列出三个指针,第一个指针指向数组第一个数字,其余两个指向后面的数组的头尾,并逐渐向中间遍历;注意代码简洁!注意数组下表越界问题!注意重复的组合处理问题!!


17、电话号码的字母组合(中等)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
case ‘2’ : return {“a”,“b”,“c”};
case ‘3’ : return {“d”,“e”,“f”};
case ‘4’ : return {“g”,“h”,“i”};
case ‘5’ : return {“j”,“k”,“l”};
case ‘6’ : return {“m”,“n”,“o”};
case ‘7’ : return {“p”,“q”,“r”,“s”};
case ‘8’ : return {“t”,“u”,“v”};
case ‘9’ : return {“w”,“x”,“y”,“z”};

输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

// 不用递归的笨方法,但不影响效率
class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> result;
        vector<string> letter;
        if(digits.empty()) return result;
        for(int i=0;i<digits.size();++i){
            letter = NumToLetter(digits[i]); // 获得当前数字代表的字符vector
            if(!letter.empty()){
                CombineLetter(letter,result); // 将当前数字代表的字符添加到结果数组中 
            }
        }
        return result;
    }
    vector<string> NumToLetter(char c){
        switch(c){
            case '2' : return {"a","b","c"};
            case '3' : return {"d","e","f"};
            case '4' : return {"g","h","i"};
            case '5' : return {"j","k","l"};
            case '6' : return {"m","n","o"};
            case '7' : return {"p","q","r","s"};
            case '8' : return {"t","u","v"};
            case '9' : return {"w","x","y","z"};
            default : return {};
        }
    }
    void CombineLetter(vector<string>& letter,vector<string>& result){
        if(result.empty()){  // 若result还为空
            for(auto s : letter){
                result.push_back(s);
            }
        }
        else{ // 若result已有值
            // 先用letter中除第一个字符外的其它字符创建新的组合添加到result队尾
            int size = result.size(); // 提前保存这时result的大小(很重要)
            for(int i=1;i<letter.size();++i){ // 从letter中除去第一个字符后循环
                for(int j=0;j<size;++j){ // 循环result
                    string temp = result[j] + letter[i];
                    result.push_back(temp);
                }
            }
            // 再用letter中的第一个字符填充result中的原有组合
            for(int i=0;i<size;++i){
                result[i].append(letter[0]);
            }
        }
    }
};

思路:一个数字一个数字添加,然后直接在result里组合加上这一个字母的组合,部分实现细节不太好想,但也不难; 缺点:可以用一个数组或map解决的数字字母对应问题,却写成了麻烦的函数;总体利用DFS回溯思想更简单;

// DFS回溯法
class Solution {
    vector<string> NumToLetter = { // 自建哈希表数组
        "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
    vector<string> letterCombinations(string digits) {
        vector<string> result; // 结果矩阵
        if(digits.empty()) return result; // 如果数组为空
        string temp; // 暂存目前的深度优先搜索结果
        DFS(digits,0,temp,result); // 利用递归进行深度优先遍历
        return result;
    } // 
    void DFS(string& digits, int pos, string& temp, vector<string>& result){
        if(pos>=digits.size()){ // 如果当前组合数目(如"ae")和digits(如”12“)长度相等了
            result.push_back(temp); // 保存当前的结果
        }
        else{
            string s = NumToLetter[digits[pos]-'0']; // 得到与当前遍历到的数字相对应的那个字符串
            for(int i=0;i<s.size();++i){ // 遍历该字符串
                temp.push_back(s[i]); // 组合当前这个字符串中的其中一个字符
                DFS(digits,pos+1,temp,result); // 遍历下一个数字
                temp.pop_back(); // 取消组合当前这个字符,转换到组合下一个字符
            }
        }
    }
};

思路:比较难想的思路,类似于求全排列的递归形式,但也不相同;类似于求从每个组中都抽出一个字符然后求排列;熟练利用DFS和递归思想是关键;


19、删除链表的倒数第N个节点(中等)

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:给定的 n 保证是有效的。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==nullptr) return nullptr;
        ListNode* left = head;
        ListNode* right = head;
        for(int i=0;i<n;++i){  // 多移动一次,让left停在要删除节点的前面节点
            right = right->next;
        }
        if(right!=nullptr){ // 不是删除头节点时
            while(right->next!=nullptr){
                left = left->next;
                right = right->next;
            }
            ListNode* temp = left->next;
            left->next = left->next->next;
            delete temp; temp = nullptr;   
        }
        else{ // 要删除头节点
            ListNode* temp = left;
            head = head->next;
            delete temp; temp = nullptr;
        }
        return head;
    }
};

思路:注意链表删除操作时的余量,另外注意删除的如果是头节点的情况;


20、有效的括号(简单)

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

class Solution {
public:
    bool isValid(string s) {
        if(s.empty()) return true;
        stack<char> stk;
        for(int i=0;i<s.size();++i){
            if(s[i]=='('||s[i]=='{'||s[i]=='['){ // 如果是左括号,则入栈
                stk.push(s[i]);
            }
            else{ // 如果是右括号,栈不为空且栈首对应则出栈
                if(!stk.empty()&&
                ((s[i]==')'&&stk.top()=='(')
                ||(s[i]=='}'&&stk.top()=='{')
                ||(s[i]==']'&&stk.top()=='['))){
                    stk.pop();
                }
                else{
                    return false;
                }
            }
        }
        if(stk.empty()) return true;
        else return false;
    }
};

思路:利用栈进行挨个匹配,注意使用stack.top()的时候要注意前提是栈不为空;另外注意匹配的准则是栈最后要弹空才行;


21、合并两个有序链表(简单)

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1==nullptr) return l2;
        if(l2==nullptr) return l1;
        ListNode* node;
        if(l1->val<l2->val){
            node = l1;
            node->next = mergeTwoLists(l1->next,l2);
        }
        else{
            node = l2;
            node->next = mergeTwoLists(l1,l2->next);
        }
        return node;
    }
};

思路:可以利用递归或非递归的方法解决,递归方法很巧妙比较难想,循环的方法直观简单但比较繁琐;


你可能感兴趣的:(C++ : 力扣_Top(1-21))