写一些自己LeetCode的刷题过程及总结01(数组+链表+哈希表+字符串)


##leetcode上有2000+的题,不可能都刷完,我的刷题顺序是先分类型,然后再分难度,不断提升,当然过程中也参考了其他大神们的一些建议,光刷题收获不大,最重要的还是不断归纳总结,没事刷两道,坚持写总结其实也挺有意思的。##
##还在不断更新总结!##
##本文仅用来记录自己平时的学习收获##
##有朝一日我也能写出漂亮的代码!##

一、数组

1.1 leetcode部分数组题目及代码

704.二分查找
27.移除元素
209.长度最小的子数组
54.螺旋矩阵
59.螺旋矩阵2

1、704.二分查找

//之前写了一个关于二分找的博客,对一些二分查找的题目进行了总结分类,这里不再多说了
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
};

2、27.移除元素

//通过双指针求解
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0;
        for (int j = 0; j < nums.size(); ++j) {
            if (nums[j] != val) {
                nums[left++] = nums[j];
            }
        }
        return left;
    }
};

3、209.长度最小的子数组

//使用滑动窗口求解
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int slow = 0;
        //滑动窗口中的数值之和
        int sum = 0;
        //返回的最小长度
        int minLen = INT_MAX;
        for (int fast = 0; fast < nums.size(); ++fast) {
            sum += nums[fast];
            //每次更新left(起始位置),并判断滑动窗口中的数值是否符合条件
            while (sum >= target) {
                if (minLen > fast - slow + 1) minLen = fast - slow + 1;
                //滑动窗口的精髓,不断更新left(起始位置)
                sum -= nums[slow];
                ++slow;
            }
        }
        //没有符合条件的返回0
        if (minLen == INT_MAX) return 0;
        return minLen;
    }
};

4、54.螺旋矩阵

//这两道螺旋矩阵的题没有涉及什么算法,只是纯粹的模拟过程,但是比较考察对代码的掌控能力
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> vec;
        if (matrix.size() == 0) return vec;
        int up = 0;
        int down = matrix.size() - 1;
        int left = 0;
        int right = matrix[0].size() - 1;
        while (true) {
            for (int i = left; i <= right; ++i) vec.push_back(matrix[up][i]);
            if (++up > down) break;
            for (int i = up; i <= down; ++i) vec.push_back(matrix[i][right]);
            if (--right < left) break;
            for (int i = right; i >= left; --i) vec.push_back(matrix[down][i]);
            if (--down < up) break;
            for (int i = down; i >= up; --i) vec.push_back(matrix[i][left]);
            if (++left > right) break;
        }
        return  vec;
    }
};

5、59.螺旋矩阵2

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int up = 0;
        int down = n - 1;
        int left = 0;
        int right = n - 1;
        int num = 1;
        int goal = n * n;
        vector<vector<int>> vec(n, vector<int> (n, 0));
        while (num <= goal) {
            for (int i = left; i <= right; ++i) {
                vec[up][i] = num;
                ++num;
            }
            ++up;
            for (int i = up; i <= down; ++i) {
                vec[i][right] = num;
                ++num;
            }
            --right;
            for (int i = right; i >= left; --i) {
                vec[down][i] = num;
                ++num;
            }
            --down;
            for (int i = down; i >= up; --i) {
                vec[i][left] = num;
                ++num;
            }
            ++left;
        }
        return vec;
    }
};

1.2 数组总结

总的来说数组的题目种类比较多,不好做细致的分类,第一部分这里只列出了一些基础类的题目,比如二分查找、双指针、滑动窗口(其实也可以看做是双指针)等,至于回溯、动规等放在后面。

这里记录一下双指针的一些细节问题
1、一般来说数组或链表的遍历问题,能用暴力法解决的应该都能用双指针。可以分为同向双指针和反向双指针。
2、同向双指针:一快一慢,或者一个先动当满足或不满足某一条件时另一个再动,两个都初始化为0或nums.size()-1。(如27题)
3、反向双指针:从首位出发,初始化为0和nums.size()-1。(如977题)
4、同向双指针在遍历结束后可保证数组中元素的相对位置不变;而反向双指针在遍历结束后不能保证元素的相对位置不变

二、链表

2.1 链表的定义

//单链表
struct ListNode {
	int val;//数据域
	ListNode* next;//指针域
	ListNode() : val(0), next(nullptr) {} //构造函数
	ListNode(int x) : val(x), next(nullptr) {} //构造函数
	ListNode(int x, ListNode* node) : val(x), next(node) {} //构造函数
};

2.2 leetcode部分链表题目及代码

203.移除链表元素
707.设计链表
206.翻转链表
24.两两交换链表中的节点
19.删除链表的倒数第N个节点
142.环形链表2

1、203.移除链表元素

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //设置一个虚拟头节点方便后面进行删除操作
        ListNode* preHead = new ListNode(0, head);
        ListNode* pre = preHead;
        ListNode* cur = head;
        while (cur != nullptr) {
            if (cur->val == val) {
                pre->next = cur->next;
                cur = cur->next;
            } else {
                pre = pre->next;
                cur = cur->next;
            }
        }
        return preHead->next;
    }
};

2、707.设计链表

//这道题基本上覆盖了链表的基本操作
class MyLinkedList {
public:
    struct ListNode {
        int val;
        ListNode* next;
        ListNode(int x) : val(x), next(nullptr) {}
    };

    //初始化链表
    MyLinkedList() {
        //这里定义的是一个虚拟头节点,并不是真正的链表头节点,所以m_size = 0
        m_dummyHead = new ListNode(0);
        m_size = 0;
    }
    
    //获取链表中第 index 个节点的值。如果索引无效,则返回-1。
    int get(int index) {
        if (index > m_size - 1 || index < 0) return -1;
        ListNode* cur = m_dummyHead->next;
        while (index--) {
            cur = cur->next;
        }
        return cur->val;
    }
    
    //在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
    void addAtHead(int val) {
        ListNode* cur = new ListNode(val);
        cur->next = m_dummyHead->next;
        m_dummyHead->next = cur;
        ++m_size;
    }
    
    //将值为 val 的节点追加到链表的最后一个元素。
    void addAtTail(int val) {
        ListNode* node = new ListNode(val);
        ListNode* cur = m_dummyHead;
        while (cur->next != nullptr) {
            cur = cur->next;
        }
        cur->next = node;
        ++m_size;
    }
    
    //在链表中的第 index 个节点之前添加值为 val  的节点。
    //如果 index 等于链表的长度,则该节点将附加到链表的末尾。
    //如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
    void addAtIndex(int index, int val) {
        if (index > m_size) return;
        //else if (index <= 0) addAtHead(val);
        else {
            ListNode* node = new ListNode(val);
            ListNode* cur = m_dummyHead;
            while (index--) {
                cur = cur->next;
            }
            node->next = cur->next;
            cur->next = node;
            ++m_size;
        }
    }
    
    //如果索引 index 有效,则删除链表中的第 index 个节点.
    void deleteAtIndex(int index) {
        if (index >= 0 && index < m_size) {
            ListNode* cur = m_dummyHead;
            while (index--) {
                cur = cur->next;
            }
            ListNode* temp = cur->next;
            cur->next = cur->next->next;
            delete temp;
            --m_size;
        }
    }

private:
    int m_size;
    ListNode* m_dummyHead;
};

3、206.翻转链表

//解法1:递归
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode* node = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return node;
    }
};

//解法2:迭代
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

4、24.两两交换链表中的节点

//遇到这种操作多个指针的,建议画个图辅助理解
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* cur = dummyHead;
        while (cur->next != nullptr && cur->next->next != nullptr) {
            //记录两个临时节点
            ListNode* temp1 = cur->next;
            ListNode* temp2 = cur->next->next->next;

            cur->next = cur->next->next;
            cur->next->next = temp1;
            cur->next->next->next = temp2;

            //cur向前移动两位,准备下一次交换
            cur = cur->next->next;
        }
        return dummyHead->next;
    }
};

5、19.删除链表的倒数第N个节点

//让fast指针多走n步,就可以找到倒数第n个节点
//第876题链表的中间节点 与之类似
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while (n-- && fast != nullptr) {
            fast = fast->next;
        }
        //fast再向前走一步,这样当fast=nullptr的时候slow就刚好指向要删除节点的上一个节点
        fast = fast->next;
        while (fast != nullptr) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

6、142.环形链表2

这道题开始有些不好理解,可以分为两步:
	a、判断是否有环
	b、找环的入口

a、首先来看怎样判断链表有环,定义两个指针slow和fast,让slow每次前进一个节点,让fast每次前进两个节点,如果slow和fast相遇(slow == fast),那么说明有环。
b、然后再来找环的入口,相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数: x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:(x + y) * 2 = x + y + n (y + z)两边消掉一个(x+y): x + y = n (y + z)因为要找环形的入口,那么要求的是x,因为x表示头结点到环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了slow指针了。当 n为1的时候,公式就化解为 x = z,这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
在这里插入图片描述

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            if (slow == fast) {
                slow = head;
                while (slow != fast) {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

2.3 链表总结

遇到删除链表元素、交换链表元素、向链表中某一位置插入元素的题,可以通过创建虚拟头节点来简化操作。
总之,个人感觉链表的题几乎都能用双指针去解决,关于双指针这里不在解释了。

三、哈希表

3.1 哈希表

哈希表是根据关键码的值而直接进行访问的数据结构,其实简单的说数组就是一张哈希表,哈希表中的关键码就是数组的索引,通过索引直接访问数组中的元素。
一般来说,哈希表用来快速判断一个元素是否出现在集合中,或是用来去除重复元素
举个例子:如果我们要判断一个叫”张三“的学生是否在这个班级中,如果我们用数组arr[]来存储所有的学生信息,那么我们在查找是需要遍历整个数组,对arr[i]的姓名进行判断,看他是否是“张三”,这样的话时间复杂度是O(n)。但是如果使用哈希表的话,在初始化时把班级里学生的姓名存在哈希表里,在查询时就可以通过索引直接知道这位同学是否在这个班级中,时间复杂度为O(1)
但是,这里有一个问题,如何才能将学生姓名映射到哈希表上,这就涉及到了哈希函数

3.2 哈希函数

通过哈希函数,可以把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引来快速知道某位同学是否在这个班级中。
哈希函数通过hashcode把姓名转化为数值,一般hashcode是通过特定的编码方式将其他数据类型转化为不同的数值,这样就可以实现把学生名字映射为哈希表上的索引了。
一般来说,每一位同学的姓名会对应到唯一的索引处,这也就是哈希表可以去重的原因。但是,难免会有几位同学的名字映射到了哈希表的同一个索引处,这就是哈希碰撞,这里不在解释了。

3.3 C++中常见的三种哈希结构

数组
set(集合)
map(映射)

在这里插入图片描述
在这里插入图片描述其中,红黑树是一种二叉搜索树(为了解决二叉搜索树的瘸腿问题),所以set、multiset、map和mulitmap是有序的。
当需要使用集合来解决哈希问题时,优先使用unordered_set,它的查询和删除效率是最优的;如果要求集合是有序的,使用set;如果要求不仅有序还要有重复元素,那就用multiset。

3.4 leetcode部分哈希表题目及代码

242.有效的字母异位词
349.两个数组的交集
350.两个数组的交集II
202.快乐树
1.两数之和
383.赎金信
454.四数相加2
15.三数之和
149.直线上最多的点数
169.多数元素
205.同构字符串
1418.点菜展示表

1、242.有效的字母异位词

class Solution {
public:
    bool isAnagram(string s, string t) {
        //这里用一个数组来充当哈希表
        int arr[26] = {0};
        for (char ch : s) {
            ++arr[ch - 'a'];
        }
        for (char ch : t) {
            --arr[ch - 'a'];
        }
        for (int i = 0; i < 26; ++i) {
            if (arr[i] != 0) return false;
        }
        return true;
    }
};

2、349.两个数组的交集

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> resultSet;
        unordered_set<int> tempSet(nums1.begin(), nums1.end());
        for (int val : nums2) {
            if (tempSet.find(val) != tempSet.end()) {
                resultSet.insert(val);
            }
        }
        return vector<int> (resultSet.begin(), resultSet.end());
    }
};

3、350.两个数组的交集II

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> map;
        vector<int> ret;
        for (int num : nums1) {
            ++map[num];
        }
        for (int num : nums2) {
            if (map.find(num) != map.end() && map[num] != 0) {
                --map[num];
                ret.push_back(num);
            }
        }
        return ret;
    }
};

4、202.快乐树

//这道题一开始脑子没有转过这个弯儿,
//题目中说了有可能无限循环,那么为什么会出现无限循环呢?
//如果当一次求和后的值出现了重复(跟之前某次求和后的值重复了),那么就会出现无线循环。
//所以这道题就变成了:判断求和后的值sum是否出现重复,那么判断重复就要想到哈希。
class Solution {
public:
    //用来计算每个位的平方和
    int getSum(int num) {
        int sum = 0;
        while (num != 0) {
            int temp = num % 10;
            sum += temp * temp;
            num /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> tempSet;
        while (true) {
            int sum = getSum(n);
            if (sum == 1) return true;
            //如果这个sum曾经出现过,就证明陷入了无限循环,return false
            if (tempSet.find(sum) != tempSet.end()) return false;
            else tempSet.insert(sum);
            n = sum;
        }
    }
};

5、1.两数之和

//终于到这道题了,梦开始的地方,总结到这里突然有些感触,想到自己当时连STL都没学就跑来刷题的样子了,也是头铁,基础还是要打牢。
//题目这里让返回的是下标,如果要直接返回对应的元素的,直接用set就行
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> tempMap;
        for (int i = 0; i < nums.size(); ++i) {
            unordered_map<int, int>::iterator it = tempMap.find(target - nums[i]);
            if (it != tempMap.end()) {
                return {it->second, i};
            }
            tempMap.insert(pair<int, int> (nums[i], i));
        }
        return {};
    }
};

6、383.赎金信

//这道题。。。怎么说呢,跟我分享的第一个也就是第242道题基本一样
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int arr[26] = {0};
        for (char ch : magazine) {
            ++arr[ch - 'a'];
        }
        for (char ch : ransomNote) {
            --arr[ch - 'a'];
            if (arr[ch - 'a'] < 0) return false;
        }
        return true;
    }
};

7、454.四数相加2

//这道题是一个很经典的哈希法题目,A[i]+B[j]+C[k]+D[l]=0 => (A[i]+B[j])+(C[k]+D[l]) = 0.
//也即是A[i] + B[j] = 0 - C[k] + D[l]
//再具体的思路直接放到代码中了
class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        //定义一个map,key存放a+b的数值,val存放a+b的数值出现的次数
        unordered_map<int, int> tempMap;
        //遍历A、B两个数组,统计两元素之和以及和出现的次数
        for (int a : A) {
            for (int b : B) {
                ++tempMap[a + b];
            }
        }
        //count统计a+b+c+d=0的次数
        int count = 0;
        //遍历C、D两个数组,如果找到0-(c+d)在map中出现过,就用count加上出现的次数
        for (int c : C) {
            for (int d : D) {
                if (tempMap.find(0 - (c + d)) != tempMap.end()) {
                    count += tempMap[0 - (c + d)];
                }
            }
        }
        return count;
    }
};

8、15.三数之和

//本来不想把这道题放在这里,但是做了前一道题后再看这道题时第一眼想到的就是哈希,所以想用哈希法解一下。
//两层for循环就可以确定a和b的数值,然后c = 0 - (a + b)可以用哈希法来确定是否出现过(类似于上一道题:454.四数相加2),
//但是有一个很麻烦的问题,就是题目中说的不可以包含重复的三元组。
//虽然这道题可以用哈希法求解,但是因为不好做剪枝操作,所以最好别用哈希法,
//可以参考leetcode中其他大佬们说的双指针法求解,关于双指针解法的过程我会在后续的双指针文章中给出。

//哈希法也是参考了其他大佬的过程,感觉还是比较麻烦、不容易理解
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        //找出a+b+c=0
        //a = nums[i], b = nums[j], c = 0 - (a + b)
        for (int i = 0; i < nums.size(); ++i) {
            //排序后如果第一个元素a已经大于0,那么就不可能构成三元组
            if (nums[i] > 0) break;
            //对三元组a去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            unordered_set<int> tempSet;
            for (int j = i + 1; j < nums.size(); ++j) {
                //对三元组b去重
                if (j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2]) continue;
                int c = 0 - (nums[i] + nums[j]);
                if (tempSet.find(c) != tempSet.end()) {
                    result.push_back({nums[i], nums[j], c});
                    //对三元组c去重
                    tempSet.erase(c);
                } else {
                    tempSet.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

9、18.四数之和

//跟上一道题一样,更麻烦些,还是别用哈希了,我会放在后面的双指针文章中。

10、149.直线上最多的点数

class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
        int len = points.size();
        if (len < 3) return len;
        int maxCount = 2;
        for (int i = 0; i < len - 1; ++i) {
            unordered_map<double, int> tempMap;
            for (int j = i + 1; j < len; ++j) {
                double dx = points[i][0] - points[j][0];
                double dy = points[i][1] - points[j][1];
                double k = (dx == 0) ? INT_MAX : dy / dx;
                if (tempMap.find(k) == tempMap.end()) {
                    tempMap[k] = 2;
                } else {
                    ++tempMap[k];
                }
                maxCount = max(tempMap[k], maxCount);
            }
        }
        return maxCount;
    }
};

11、169.多数元素

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int len = nums.size();
        unordered_map<int, int> m;
        for (int num : nums) {
            ++m[num];
        }
        int ret;
        for (auto it = m.begin(); it != m.end(); ++it) {
            if (it->second > len / 2) {
                ret = it->first;
                break;
            }
        }
        return ret;
    }
};

12、205.同构字符串

class Solution {
public:
    bool isIsomorphic(string s, string t) {
        unordered_map<char, char> map1;
        unordered_map<char, char> map2;
        for (int i = 0, j = 0; i < s.size(); i++, j++) {
            if (map1.find(s[i]) == map1.end()) { // map1保存s[i] 到 t[j]的映射
                map1[s[i]] = t[j];
            }
            if (map2.find(t[j]) == map2.end()) { // map2保存t[j] 到 s[i]的映射
                map2[t[j]] = s[i];
            }
            // 发现映射 对应不上,立刻返回false
            if (map1[s[i]] != t[j] || map2[t[j]] != s[i]) {
                return false;
            }
        }
        return true;
    }
};

13、1418.点菜展示表

//这道题其实不难理解,只是比较繁琐,考察代码掌握能力
class Solution {
public:
    vector<vector<string>> displayTable(vector<vector<string>>& orders) {
        // 从订单中获取餐品名称和桌号,统计每桌点餐数量
        unordered_set<string> nameSet;
        unordered_map<int, unordered_map<string, int>> foodsMap;
        for (auto& order : orders) {
            nameSet.insert(order[2]);
            int id = stoi(order[1]);
            ++foodsMap[id][order[2]];
        }
        // 提取餐品名称,并按字母顺序排列
        vector<string> names;
        for (auto& name : nameSet) {
            names.push_back(name);
        }
        sort(names.begin(), names.end());
        // 提取桌号,并按餐桌桌号升序排列
        vector<int> ids;
        for (auto& [id, _] : foodsMap) {
            ids.push_back(id);
        }
        sort(ids.begin(), ids.end());
        // 填写点菜展示表
        int m = foodsMap.size();
        int n = nameSet.size();
        vector<vector<string>> table(m + 1, vector<string> (n + 1));
        table[0][0] = "Table";
        copy(names.begin(), names.end(), table[0].begin() + 1);
        for (int i = 0; i < m; ++i) {
            int id = ids[i];
            auto& temp = foodsMap[id];
            table[i + 1][0] = to_string(id);
            for (int j = 0; j < n; ++j) {
                table[i + 1][j + 1] = to_string(temp[names[j]]);
            }
        }
        return table;
    }
};

3.5 哈希表总结

当需要快速判断一个元素是否出现在集合中,或是要对元素进行去重的时候,就要考虑哈希法。
但是哈希法牺牲了空间换取时间,因为需要用额外的哈希表来存储数据,才能实现快速查找。

四、字符串

4.1 leetcode部分字符串题目及代码

344.反转字符串
541.反转字符串2
剑指offer05.替换空格
58.最后一个单词
151.翻转字符串里的单词
剑指offer58-II左旋转字符串
28.实现strStr()

1、344.反转字符串

//这道题也可以放到双指针里,不过太简单了
//其实也可以用reverse(s.begin(), s.end())一行解决,。。。没必要
class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0;
        int right = s.size() - 1;
        while (left < right) {
            char ch = s[left];
            s[left] = s[right];
            s[right] = ch;
            ++left;
            --right;
        }
    }
};

2、541.反转字符串2

//在遍历过程中只要让i += 2*k每次移动2k个就行
class Solution {
public:
    string reverseStr(string s, int k) {
        //每隔2k个字符的前k个字符进行反转
        for (int i = 0; i < s.size(); i += 2*k) {
            //剩余字符少于大于等于k个,反转前k个
            if (i + k <= s.size()) {
                reverse(s, i, i + k - 1);
                continue;
            }
            //剩余字符不足k个,反转剩余字符
            reverse(s, i, s.size() - 1);
        }
        return s;
    }
    void reverse(string& str, int start, int end) {
        int left = start;
        int right = end;
        while (left < right) {
            char ch = str[left];
            str[left] = str[right];
            str[right] = ch;
            ++left;
            --right;
        }
    }
};

3、剑指offer05.替换空格

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

//放一个别人写的进阶解法,不需要额外的辅助空间,直接对原字符串进行扩充
//首先扩充数组到每个空格被替换成%20后的大小
//然后从后向前替换空格,也就是双指针法,j指向旧长度的末尾,i指向新长度的末尾
//注:为什么要从后向前,为什么不从前向后
//从前向后填充就是O(n^2)了,因为每次添加元素后,都要将添加元素之后的所有元素向后移动。
//很多数组填充问题,都可以先扩充数组大小然后从后向前填充
class Solution {
public:
    string replaceSpace(string s) {
        //统计空格个数
        int count = 0;
        for (char ch : s) {
            if (ch == ' ') ++count;
        }
        int oldSize = s.size();
        //扩充s的大小
        s.resize(oldSize + count * 2);
        int newSize = s.size();
        for (int j = oldSize - 1, i = newSize - 1; j < i; --j, --i) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }

4、58.最后一个单词

class Solution {
public:
    int lengthOfLastWord(string s) {
        int i = s.size() - 1;
        int count = 0;
        while (i >= 0 && s[i] == ' ') --i;
        while (i >= 0 && s[i] != ' ') {
            --i;
            ++count;
        }
        return count;
    }
};

5、151.翻转字符串里的单词

//跟上一个题有点像
class Solution {
public:
    string reverseWords(string str) {
        if (str.size() <= 1) return str;
        int i = str.size() - 1;
        string ret = "";
        while (i >= 0) {
            int count = 0;
            while (i >= 0 && str[i] == ' ') --i;
            while (i >= 0 && str[i] != ' ') {
                --i;
                ++count;
            }
            if (count > 0) {
                ret += str.substr(i + 1, count) + ' ';
            }
        }
        return ret.substr(0, ret.size() - 1);
    }
};

6、剑指offer58-II左旋转字符串

//当然也可以用reverse(),不推荐
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        if (s.size() < n) return s;
        string ret = "";
        for (int i = n; i < s.size(); ++i) {
            ret += s[i];
        }
        for (int i = 0; i < n; ++i) {
            ret += s[i];
        }
        return ret;
    }
};

7、28.实现strStr()

//其实这道题是KMP算法的经典例子,由于我现在还没搞懂KMP,所以先给出其它解答方法
//后面会详细介绍KMP
class Solution {
public:
    int strStr(string haystack, string needle) {
        if (haystack.size() < needle.size()) return -1;
        for (int i = 0; i <= haystack.size() - needle.size() + 1; ++i) {
            if (haystack.substr(i, needle.size()) == needle) return i;
        }
        return -1;
    }
};

4.2 字符串总结

字符串也可以理解成一个字符数组,所以可以按照操作数组的方式去操作字符串,C++中封装了很多操作字符串的库函数,但在解题过程中尽量不要使用,尤其是涉及到题目的关键部分。
双指针法在数组、链表和字符串中都会经常使用,后面会专门对双指针法的题目进行总结。
字符串的反转系列比较考察对代码的掌控能力,比如第541题,虽然难度不大但是需要在for循环上做文章。
最后是KMP,常用来解决字符串的匹配问题,这个也会在后续文章中进行总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值