代码随想录算法训练营(60天刷题笔记)

day 1 数组专题(二分查找+双指针等)

leetcode 704

二分查找

        确定循环不变量:

        如果是取 区间左闭右开,则每次循环都要保证得到的区间是左闭右开的。

class Solution {
public:
    int search(vector<int>& nums, int target) {

        int l = 0, r = nums.size();
        //以左闭右开方式二分,则r取最后一个元素的下一位置即算法运行区间为:[l,r);

        int mid;
        while(l < r){  //取l < r而不取l <= r的原因:循环条件l<r本质上是指循环内部能取l但不能取r,从而与左闭右开的初始状态保持一致。
            
            mid = l + r >> 1; //只能下取整,当区间为[l,l+1)时,此时r=l+1,mid取值下取整刚好是区间中唯一能取到的元素,同样与左闭右开的初始态保持一致。

            if(nums[mid] < target) l = mid + 1; //l为左区间断点,能取到,一旦判断出mid位置不符,赋值就跳过该值,避免出现死循环:当区间只有两个元素时,取l = mid会使mid和l一直在同一位置无限循环这条语句。

            else if(nums[mid] > target) r = mid; //r为右区间端点,其指向的值不能取到, mid - 1位置的值还未与target比较,不能将r = mid - 1,不然每次循环都会漏掉这个位置

            else return mid;

        }
        return -1;
            
    }
};

左开右闭或者左闭右闭同理。关键是抓住“每次循环划分出的区间要保持:对元素的处理不漏不重”


双指针

27.移除元素

解法1

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0, fast = 0;

        for(;fast < nums.size(); fast++) //fast遍历寻找不是val的元素,slow接收fast找到的元素
        {
            if(nums[fast] != val)
            {
                 nums[slow] = nums[fast];
                 slow++;
            }
        }
        return slow;//最后slow一定小于等于fast,slow为有效元素的下一个位置,数值上等于有效元素的个数
    }
};

解法2

见leetcode题解

day 2 双指针、滑动窗口、二分、前缀和、排序


 977.有序数组的平方 

思想:

双指针算法,设一个头指针front和一个尾指针tail。由于数组元素非递减排列,所以要求的平方数数组高位的元素一定从原数组的两头产生,循环比较:头尾指针指向的值的平方的大小,并取大者放入新数组高位,退出循环时条件:front等于tail

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int front = 0, tail = nums.size() - 1;
        vector<int> result;
        while(front <= tail)//头尾没相遇则循环取平方大的数
        {
            if(nums[front] * nums[front] >= nums[tail] * nums[tail] )
            {
                
                result.push_back(nums[front] * nums[front]);
                front++;
            }
            else
            {
                result.push_back(nums[tail] *nums[tail]);
               // if(nums[tail] > nums[front]) swap(nums[tail],nums[front]);
                tail--;
            }
            
        }
//因为push_back()只能将元素放到vector尾部,故倒转一下再返回。
        int cnt = (nums.size() - 1);
        for(auto a : result)
            nums[cnt--] = a;
        return nums;
    }
};

 209.长度最小的子数组

有三种方法:

1.暴力即两重for循环(leetcode加强了数据已经过不了了)

第一重遍历作为子数组起始位置,第二重遍历当前其实位置开始有多少符合大小的子数组,取最小者。

两重循环完,能找出最小长度的子数组。

复杂度为O($n^2$)

可以用二分优化第二重循环的搜索,但要构造合适的二分使用条件。

2.二分+前缀和

在暴力的基础上,暴力解法中每轮循环都要重复计算“部分”前缀和,比如当target = 10,前三个数组元素为[1]、[2]、[6],那么每次都要计算[1] +[2],[1]+[2]+[3];

于是想到用前缀和优化,

前缀和可以通过O(1)的时间复杂度得到子数组的元素和;如

prefix_sum[i] - prefix_sum[j];

,就表示从数组i + 1到j的元素和。由于要找满足条件:

prefix_sum[i] - prefix_sum[j] >= target;

的j,又希望可以用二分优化,可以将上式化为prefix_sum[i] >= prefix_sum[j] + target;即在前缀和数组中查找满足条件:大于等于prefix_sum[j] + target的i;

代码如下:(力扣官方题解)

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }
        int ans = INT_MAX;
        vector<int> sums(n + 1, 0); 
        // 为了方便计算,令 size = n + 1 
        // sums[0] = 0 意味着前 0 个元素的前缀和为 0
        // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
        // 以此类推
        for (int i = 1; i <= n; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= n; i++) {
            int target = s + sums[i - 1];
            auto bound = lower_bound(sums.begin(), sums.end(), target);
            if (bound != sums.end()) {
                ans = min(ans, static_cast<int>((bound - sums.begin()) - (i - 1)));
            }
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

3 滑动窗口

代码简单,注意窗口前后端的移动!!

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum = 0;
        int len = 0;
        int result = INT32_MAX;
        for(int i = 0,j = 0; i < nums.size(); i ++)
        {
            sum += nums[i];

            while(sum >= target)
            {
                len = i - j + 1;
                result = result > len ? len : result;
                sum -= nums[j++];//窗口缩小,并从sum中减去移出窗口的值
            }
        }

        return result == INT32_MAX ? 0:result;
    }
};

59. 螺旋矩阵 II

边界条件很多,涉及四个循环,环环相扣,注意每个都要保持步调一致即不能破坏循环不变量;循环不变量可以仍选:左闭右开,左开右闭,等等。

代码随想录题解:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

day 3 链表专题(1)

 203.移除链表元素  

需要说明的是力扣上的头结点和高校教材上的头结点定义不同

力扣中的头结点是存储了数据的真实结点,可以认为力扣的定义和算法导论一致,但是算法导论用的是“链表头”而力扣用的是“头结点”;为统一链表各结点的操作而设置的空结点称为哨兵(哑对象,不带有效数据)。

国内的教材统一定义为:头结点不含有效数据,是为统一链表各结点的操作而设置的;由此链表可分为带头结点和不带头结点的两种。(计算机408统考中也是这种定义)。

搞清楚题设后就是本题就是简单的链表删除操作:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        
        ListNode *real_head = new ListNode(0,head);
        ListNode *p =real_head;

        if(real_head->next != nullptr)
        {
            while(head != nullptr)
            {
                if(head->val != val)
                {
                    head = head->next;
                    p = p->next;
                }
                else
                {   
                    auto q = head;
                    head = head->next;
                    p->next = head;
                    delete q;
                }
            }

            return real_head->next;
        }

        else return nullptr;


    }
};

 707.设计链表  

手写一个链表类,并实现类的成员函数:头插法、尾插法、指定位置插入、指定位置删除、返回指定位置的值。可以用单链表也可以用双链表实现。

需要注意的点:

每个结点都有相同的属性,但是不同位置的结点可能对属性的使用不一样;例如:如果链表类含有长度len成员变量,则只需要在头结点中维护即可,其余结点中len一直保持初始化时的值,也即是头结点描述了整个链表的性质,从而用到了更多的属性,其余结点只描述本结点的信息,只需要用少部分的属性,如data,next;

另外要搞清楚链表长度和结点编号的关系,选择带头结点的实现是最方便的,因为其中头结点可以存储整个链表的性质。

以下为单链表实现:

class MyLinkedList {

private:
    int val;
    MyLinkedList *next;
    int len;

public:
    MyLinkedList() {//构造函数
        this->val = 0;
        this->next = nullptr;
        //this.tail = nullptr;
        this->len = 0;
    }
    
    int get(int index) {//返回第index+1个结点的val(节点从0开始编号,故index+1)

        if(this->len > 0 && index >=0 && index < len)
        {   
            auto q = this;
            while(index >= 0)
            {
                q = q->next;
                index--;
            }
            return q->val;
        }
        else return -1;
    }
    
    void addAtHead(int val) {//头插法插入新结点
        MyLinkedList *p = new MyLinkedList();
        p->val = val;
        p->next = this->next;
        this->next = p;
        this->len++;
    }
    
    void addAtTail(int val) {//尾插法插入新结点
        MyLinkedList *p = new MyLinkedList();
        p->val = val;
        //p.next = nullptr;
        auto q = this;
        while(q->next != nullptr)
            q = q->next;
        q->next = p;
        this->len++;
    }
    
    void addAtIndex(int index, int val) {//根据给出的位置插入结点:插入到下标为index的结点之前,index为最后一个元素的下一个位置时(即index为链表长度)直接插入尾部;index大于链表长度,参数违法什么也不做。
    if(index == this->len)
    {
        addAtTail(val);
        return;
    }
    if(index == 0) 
    {
        addAtHead(val);
        return;
    }
    if(index > this->len) return;

    MyLinkedList *p = new MyLinkedList();
    p->val = val;
    auto q = this;
    while(index > 0)//找到插入位置
    {
        q = q->next;
        index--;
    }
    p->next = q->next;
    q->next = p;
    this->len++;
    }
    
    void deleteAtIndex(int index) {//在合法范围内,删除下标为index的结点,否则什么也不做
        if(index < 0) return;
        if(index >= this->len) return;
        auto q = this;
        while(index > 0)
        {
            q = q->next;
            index--;
        }
        auto p = q->next;
        q->next = p->next;
        this->len--;
        delete p;
       
    }
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

 206.反转链表 

直接了当的做法就是把整个链表作为输入,从头到尾遍历每个结点,并用头插法再生成一个链表,生成的就是原链表的反转了。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        auto old_head = head;
        ListNode *new_head = new ListNode();
        while(old_head != nullptr)
        {
            auto p = old_head->next;
            old_head->next = new_head->next;
            new_head->next = old_head;
            old_head = p;
        }
        return new_head->next;
    }

};

day 4 链表专题(2)

leetcode的链表题代码规范要求太严格了,自己写第一遍老是报错:heap-use-after-free ,也怪自己没养成良好习惯,debug都搞半天;

链表规范:

1. 任何结点只要逻辑上无后继结点必须置nullptr,不然后面用的时候leetcode就不让你再往后面加结点了,即使你只是改最后一个结点的next指针;

2. 使用指针前都要判空,以免后面代码出现访问指向类对象的空指针的成员变量的错误,即使你可能不会访问。

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

注意代码规范本体不难,关键在于画图理清步骤和结点间断链之后要确保不丢失结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *dummy_head = new ListNode();
        dummy_head->next = head;
        auto cur = dummy_head;
        if(cur->next == nullptr || cur->next->next == nullptr) return head;

        while(cur->next != nullptr && cur->next->next != nullptr)
        {
            auto tmp1 = cur->next->next;
            auto tmp2 = cur->next->next->next;

            cur->next->next = tmp2;
            tmp1->next = cur->next;
            cur->next = tmp1;
            cur = tmp1->next;
        }

        return dummy_head->next;
    }
};

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

双指针:要找倒数第n个,可转化为找离终点距离为n的结点,设置两个指针,只要保持两个指针距离为n,则可使后一个指针到达尾端时,前一个指针与尾端的距离为n。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        

        ListNode *dummy_head = new ListNode();//设置一个哨兵头结点
        dummy_head->next = head;//哨兵指向头结点
        ListNode *fast = dummy_head, *slow = dummy_head;
        int cnt = 0;

        while(slow->next != nullptr)
        {
            slow = slow->next;
            cnt++;
            if(cnt >= n + 1)
            {
                fast = fast->next;
            }
        }
        //if(fast == dummy_head) return nullptr;
        fast->next = fast->next->next;
        return dummy_head->next;
    }

   
};

 面试题 02.07. 链表相交  

仍然是双指针算法,循环遍历如下内容:相交的链表有一个性质是从头开始遍历完自己后遍历别人,当把相交链表中的所有元素都遍历完后刚好相遇。把是否相遇作为循环的条件:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // int len_a = 0;
        // int len_b = 0;
        if(!headA || !headB) return NULL;
        ListNode *headA_0 = new ListNode(0);
        headA_0->next = headA;
        ListNode *headB_0 = new ListNode(0);
        headB_0->next = headB;
        ListNode *A = headA_0;
        ListNode *B = headB_0;
        while(A->next != B->next)
        {
            if(A->next)
                A = A->next;
            else A = headB_0;

            if(B->next)
                B = B->next;
            else 
                B = headA_0;
        }

        return A->next;
    }
};

 142.环形链表II  

这题第一种解法感觉像在写数学题,下次再刷可能还是一点思路没有(所以了解即可);还是哈希表解法容易掌握也更具有普适性。

(力扣官解)哈希:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

day5 哈希专题

可以先看看C++premier 中 关联容器 这一章,理解什么是哈希,尤其是set和map的各种api使用。

242.有效的字母异位词 

class Solution {
public:
    bool isAnagram(string s, string t) {

        if(s.size() != t.size()) return false;
        int record[26] = {};

        for(auto &a : s)
        {
            record[a - 'a']++;
        }
        for(auto &a : t)
        {
            record[a - 'a']--;
        }

        for(int i = 0; i < 26; i++)
        {
            if(record[i] != 0) return false;
        }

        return true;
    }
};

349. 两个数组的交集 

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> result1,tmp;
        vector<int> result2;
        for(auto &a : nums1)
            result1.insert(a);
        for(auto &a : nums2)
            tmp.insert(a);
        for(auto &a: tmp)
            if(result1.find(a) != result1.end()) result2.push_back(a);
        return result2;
    }
};

202. 快乐数(要搞清楚死循环是因为sum开始重复出现了)

class Solution {
public:
    // 取数值各个位上的单数之和
    int getSum(int n) {
        int sum = 0;
        while (n) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int> set;
        while(1) {
            int sum = getSum(n);
            if (sum == 1) {
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
                return false;
            } else {
                set.insert(sum);
            }
            n = sum;
        }
    }
};

1. 两数之和  

暴力:

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

哈希(代码随想录):

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
                return {iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {};
    }
};

day6 哈希(2)

454.四数相加II 

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int count = 0;
        unordered_map<int,int> map1_2;
        for(auto &a : nums1)
            for(auto &b : nums2)
                map1_2[a+b]++;//nums1和nums2中所有可能的两数和记录在map1_2中并记录各和出现的次数
        for(auto &c : nums3)
            for(auto &d: nums4)
                if(map1_2.find(0-(c+d))!= map1_2.end())//a+b+c+d=0,则c+d=0-(a+b)
                    count+=map1_2[0-(c+d)];//注意别直接count++,因为map1_2中和重复的次数也要加进去

    return count;
    }
};

383. 赎金信 

题目明确说了都是小写字母,那么可以开一个含26个整数的数组来哈希映射每个字母出现情况。

构造时:通过map[x-'a']++记录字母出现次数。

检查时:通过map[x-'a']--构造消耗字母的数组,然后遍历该数组如果出现小于零的元素则表明有不存在于magezine中的字母被使用。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int map[26] = {0};
        for(auto &a : magazine)
            map[a-'a']++;
        for(auto &b : ransomNote)
            map[b-'a']--;
        for(int i = 0; i < 26; i++)
            if(map[i] < 0) return false;
        return true;
    }
};

15. 三数之和 

用哈希做人做麻了,绷不住直接用的卡哥的代码,哈希做法去重写不出来,最后把双指针写法看懂就行了,留着二刷再看。

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[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};

18. 四数之和 

同样用的卡哥代码,看懂就过,时间不多,优先跟上进度。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
            // 剪枝处理
            if (nums[k] > target && nums[k] >= 0) {
            	break; // 这里使用break,统一通过最后的return返回
            }
            // 对nums[k]去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 2级剪枝处理
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                    break;
                }

                // 对nums[i]去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
                        right--;
                    // nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
                    } else if ((long) nums[k] + nums[i] + nums[left] + nums[right]  < target) {
                        left++;
                    } else {
                        result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                        // 对nums[left]和nums[right]去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }

            }
        }
        return result;
    }
};

day7 字符串(1)

344.反转字符串

双指针;元素变化:头变尾,尾变头,可通过一个swap()函数交换两个指针位置的值来实现,两指针存在两种情况:1.字符串为奇数个字符时会相遇 2 .偶数个时会互相越过一个位置。所以条件判断设为头<尾就行。

class Solution {
public:
    void reverseString(vector<char>& s) {
        int i = 0,j = s.size() -1 ;

        while(i < j)
        {
            swap(s[i++],s[j--]);
        }
    }
};

541. 反转字符串II

class Solution {
public:
    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.size(); i += (2 * k)) {
            // 1. 每隔 2k 个字符的前 k 个字符进行反转
            // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if (i + k <= s.size()) {
                reverse(s, i, i + k - 1);
                continue;
            }
            // 3. 剩余字符少于 k 个,则将剩余字符全部反转。
            reverse(s, i, s.size() - 1);
        }
        return s;
    }
};

卡码网:54.替换数字

开一个string变量放字符,遇到数字单独处理,因为数字编码比字母小,凡是字符减去a小于0的都是数字,遇到数字就往string变量后边push_back 一个number串。

#include<iostream>
#include<vector> ;
#include<string>
using namespace std;

const string tmp = "number";

int main()
{   
    string s;
    cin >> s;
    string c;
    for(auto &a : s)
    {
        if(a - 'a' >= 0) c.push_back(a);
        
        else 
        {
            for(auto &tmp1 : tmp)
                c.push_back(tmp1);
        }
    }
    cout << c;
    return 0;
}

151.翻转字符串里的单词

卡码网:55.右旋转字符串

day 10 栈和队列(1)

232.用栈实现队列

在输出栈和输入栈转移元素时,使用语句:stOut.push(this->stIn.pop()); 会导致编译错误,原因是空时,pop()可能返回一个不匹配的类型。 也不能使用return pop()。

class MyQueue {
private:
    std::stack<int> stOut,stIn;
public:
    MyQueue() {

    }
    
    void push(int x) {
        this->stIn.push(x);
    }
    
    int pop() {
        if(this->stOut.empty())
        {
            while(!this->stIn.empty())
            {
                this->stOut.push(this->stIn.top());
                this->stIn.pop();
            }
        }
        int result = this->stOut.top();
        stOut.pop();
        return result;
        // 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
        // if (stOut.empty()) {
        //     // 从stIn导入数据直到stIn为空
        //     while(!stIn.empty()) {
        //         stOut.push(stIn.top());
        //         stIn.pop();
        //     }
        // }
        // int result = stOut.top();
        // stOut.pop();
        // return result;
    }
    
    int peek() {
        int result = this->pop();
        this->stOut.push(result);
        return result;
    }
    
    bool empty() {
        return (stIn.empty() && stOut.empty());
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

225. 用队列实现栈

一个队列实现栈;出栈操作等于出队列尾元素,前面的所有元素依次再入队回到初始状态。

class MyStack {
private:
    std::queue<int> que;
public:
    MyStack() {

    }
    
    void push(int x) {
        que.push(x);
    }
    
    int pop() {
        int size = que.size();
        size--;//减少一次搬运,使尾部元素停留在头部;
        while(size--)
        {
            que.push(que.front());
            que.pop();
        }
        int result = 0;
        result = que.front();
        que.pop();

        return result;
    }
    
    int top() {
        int result = pop();
        que.push(result);
        return result;
    }
    
    bool empty() {
        return que.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

day 11 栈和队列(2)

20. 有效的括号

这题能想到用栈但是具体细节怎么处理不好想。比如一边取出string中的括号字符一边要往栈中存匹配的字符而不是原字符,而且只存左括号不存右括号,右括号先出现又与前一个不匹配就是无效括号。匹配则出栈,全匹配最后栈会空。

class Solution {
private:
    std::stack<char> st;
public:
    bool isValid(string s) {
        if(s.size() % 2 != 0) return false;

        for(int i = 0; i < s.size(); i++)
        {
            if(s[i] == '(') st.push(')');
            else if(s[i] == '{') st.push('}');
            else if(s[i] == '[') st.push(']');
            else if(!st.empty() && s[i] == st.top()) st.pop();

            else return false;
        }
        if(st.empty()) return true;
        else return false;
    }
};

1047. 删除字符串中的所有相邻重复项

150. 逆波兰表达式求值

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值