Datawhale & 阿里云天池LeetCode基础训练营 c++ Code

Datawhale & 阿里云天池LeetCode基础训练营

Task1: 数组

课后习题

1. 删除有序数组中的重复项(Easy)

题意: 给一个有序的数组,其中有若干数是重复出现的,如 [1,1,2,2,2,4,] 删除重复项后为 [1,2,4]。

现要求你将重复项删除,并返回新的数组长度。

示例 1:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

STL 做法

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        auto last = unique(nums.begin(),nums.end());
        nums.erase(last,nums.end());
        return nums.size();
    }
};

我们每次将重复的个数统计起来,然后前移cnt个位置,就能保证前面的值都是唯一的了

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int len = nums.size(), cnt = 0;
        for(int i = 1; i < nums.size(); i++){
            if(nums[i] == nums[i-1]){
                cnt++;
                len--;
            }else{
                nums[i-cnt] = nums[i];
            }
        }
        return len;
    }
};
2. 移除元素(Easy)

**题意:**给你一个数组和值val,从数组中移除所有等于val的元素

示例 1:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

和上题一样的思路,这里不再赘述

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int len = nums.size();
         int cnt = 0;
         for(int i = 0; i < nums.size(); i++){
             if(val == nums[i]){
                 len--;
                 cnt++;
                 continue;
             }
             nums[i-cnt] = nums[i];
         }
         return len;
    }
};
3. 三数之和(Medium)

**题意:**给定一数组,按任意顺序返回三数之和为0 的三元组,其中元素各不重复,且返回的三元组也不重复

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

这道题的难点即在于三元组判重,通过观察可发现,若要三元组不重复,其实就是使三元组的前两个元素不重复出现,

熟悉STL的朋友可以使用set<pair<int,int>> 来判重,然后第三个元素我们通过二分来查找即可

不过Set判重的复杂度也不低,可以优化一下做法:

考虑到我们将数组排序后,元素皆有序,那么问题就变成了有序数组中寻找所有不重复的二元组问题

显然,二元组的第一个元素不枚举重复的,(否则必然导致重复答案)第二个元素也是(同理)。


我实在是想不通为什么我的二分比他们枚举还慢这么多,如果有知道的大佬劳请指点一二

STL做法

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        if(n < 3) return {};
        sort(nums.begin(),nums.end());
        set<pair<int,int>> st;
        vector<vector<int>> ans;
        
        for(int i = 0; i < n; i++){
            if(nums[i] > 0) break;
            for(int j = i+1; j < n; j++){
                int l = j + 1, r = n - 1;
                while(l < r){
                    int mid = (l+r)/2;
                    if(nums[i] + nums[j] + nums[mid] == 0){
                        l = mid;
                        break;
                    }
                    else if(nums[i] + nums[j] + nums[mid] > 0) r = mid-1;
                    else l = mid+1;
                }
                if(l < n && nums[i] + nums[j] + nums[l] == 0){
                    if(st.find(make_pair(nums[i],nums[j])) == st.end()){
                        ans.push_back(vector{nums[i],nums[j],nums[l]});
                        st.insert(make_pair(nums[i],nums[j]));
                    }
                }
            }
        }
        return ans;
    }
};

优化版:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size(); 
        if(n < 3) return {};
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        for(int i = 0; i < n; i++){
            if(i > 0 && nums[i] == nums[i-1]) continue;//第一个位置判重
            for(int j = i+1; j < n; j++){
                if(j > i+1 && nums[j] == nums[j-1]) continue;//第二个位置判重
                int l = j+1, r = n-1;
                while(l < r){
                    int mid = (l+r)/2;
                    if(nums[i] + nums[j] + nums[mid] == 0){
                        l = mid;
                        break;
                    }
                    else if(nums[i] + nums[j] + nums[mid] > 0) r = mid-1;
                    else l = mid+1;
                }
                if(l < n && nums[i] + nums[j] + nums[l] == 0){
                    ans.push_back(vector{nums[i],nums[j],nums[l]});
                }
            }
        }
        return ans;
    }
};

Task2:链表

课后习题

1.合并两个有序链表(Easy)

**题意:**给你两个有序的链表,合成一个有序的新链表

示例 1:

img

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

思路:递归返回或者迭代合并再返回

递归版:

/**
 * 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* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(!list1) return list2;
        else if(!list2) return list1;
        else if(list1->val < list2->val){
            list1->next = mergeTwoLists(list1->next,list2);
            return list1;
        }else{
            list2->next = mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};
2.相交链表(Easy)

**题意:**给你两个链表,其中有一个节点往后他们都相同,求该节点

示例 2:

[外链图片转存中…(img-sz5pZ068-1645112683925)]

如该处的相交处为2这个节点

思路:直接开哈希表记录结点即可

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *t = headA;
        unordered_set< ListNode* > st;
        while(t){
            st.insert(t);
            t = t->next;
        }
        t = headB;
        while(t){
            if(st.count(t)) return t;
            t = t->next;
        }
        return nullptr;
    }
};
3.删除排序链表中的重复元素 II(Medium)

**题意:**将链表中所有出现1次以上的值的节点删除并返回新链表

示例 1:

img

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

思路:前后指针(或者叫左右指针), 首先用一个指针指向前指针,然后比较前后指针的值是否相等,是则一直移动后指针,直到不相等。然后更新左右指针,继续下次循环。

/**
 * 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* deleteDuplicates(ListNode* head) {
        if (!head) return nullptr;

        ListNode *prev = new ListNode(-123);
        ListNode *cur = prev;
        while (head) {
            int cnt = 0;
            while (head->next && head->val == head->next->val) {
                cnt++;
                head = head->next;
            } // 循环结束后,head 的值与下个节点的值不一样
            if (cnt == 0) {
                cur->next = new ListNode(head->val);
                cur = cur->next;
            }
            head = head->next;
        }
        return prev->next;
    }
};

Task3:栈

课后习题

1.最小栈(Easy)

**题意:**设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • getMin() —— 检索栈中的最小元素。

示例:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

利用另外一个栈来保存前缀最小值即可,这里便不模拟栈了,直接使用stl内的栈

class MinStack {
public:
    stack<int> stk,min_stk;
    MinStack() {
        min_stk.push(INT_MAX);
    }
    
    void push(int val) {
        stk.push(val);
        min_stk.push(min( min_stk.top(), val));
    }
    
    void pop() {
        stk.pop();
        min_stk.pop();
    }
    
    int top() {
        return stk.top();
    }
    
    int getMin() {
        return min_stk.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */
2.比较含退格的字符串(Easy)

题意:‘ # ’ 字符表示退格操作,给你两个字符串,问退格操作后两字符串是否相等

示例 1:

输入:s = "ab#c", t = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。

示例 2:

输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 “”。

遇到字母就入栈,遇到# 就退栈,最后一次比较每个出栈的字符即可

class Solution {
public:
    
    bool backspaceCompare(string s, string t) {
        stack<int> s_stk, t_stk;
        for(auto &c : s){
            if(c != '#'){
                s_stk.push(c);
            }else{
                if(!s_stk.empty()){
                    s_stk.pop();
                }
            }
        }
        for(auto &c : t){
            if(c != '#'){
                t_stk.push(c);
            }else{
                if(!t_stk.empty()){
                    t_stk.pop();
                }
            }
        }
        while(!s_stk.empty() && !t_stk.empty()){
            if(s_stk.top() != t_stk.top()) return false;
            s_stk.pop();
            t_stk.pop();
        }
        return s_stk.empty() && t_stk.empty();
    }   
};
3.基本计算器 II(Medium)

**题意:**给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

示例 1:

输入:s = "3+2*2"
输出:7

示例 2:

输入:s = " 3/2 "
输出:1

示例 3:

输入:s = " 3+5 / 2 "
输出:5

由于这里的运算比较简单,我们将其转化为一个加法栈,最后求和即可。

我们来看看每组数字前面的运算符

加号 就直接把数字入栈

减号 就把相反数入栈

乘除 就把该组数字与栈顶的数字相运算即可。

最后对加法栈进行求和就是答案。

class Solution {
public:
    int calculate(string s) {
        stack<int> stk;
        int val = 0;
        char pre_op = '+';
        int len = s.length();
        for (int i = 0; i < len; ++i) {
            if (isdigit(s[i])) {
                val = val * 10 + (s[i] - '0');
            }
            if (!isdigit(s[i]) && s[i] != ' ' || i == len - 1) {//防止除0和读完数字的情况
                switch (pre_op) {
                    case '+':{
                        stk.push(val);
                        break;
                    }
                    case '-':{
                        stk.push(-val);
                        break;
                    }
                    case '*':{
                        stk.top() *= val;
                        break;
                    }
                    case '/':{
                        stk.top() /= val;
                        break;
                    }
                }
                pre_op = s[i];
                val = 0;
            }
        }
        int ans = 0;
        while(!stk.empty()){
            ans += stk.top();
            stk.pop();
        }
        return ans;
    }
};

Task4:字符串

课后习题

1.验证回文字符串 Ⅱ

给定一个非空字符串 s最多删除一个字符。判断是否能成为回文字符串。

示例 1:

输入: s = "aba"
输出: true

示例 2:

输入: s = "abca"
输出: true
解释: 你可以删除c字符。

又由于回问串的特殊性质(回文串的子串也是回文串),所以我们可以从字符串两端开始判断,若此时两端字符不同,则分别判断去掉左右字符后是否回文即可。

class Solution {
public:
    bool check(int l, int r, string s){
        int i = l, j = r;
        while (i < j){
            if (s[i] != s[j])   return false;
            i ++, j --;
        }
        return true;
    }
    bool validPalindrome(string s) {
        int n = s.length();
        int l = 0, r = n - 1;
        while (l < r){
            if (s[l] == s[r])   l ++, r --;
            else{
                l ++;
                if (check(l, r, s))    return true;

                l--, r--;
                return check(l, r, s);
            }
        }
        return true;
    }
};
2.Excel表列名称

整数 columnNumber ,返回它在 Excel 表中相对应的列名称。

例如:

A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
...

容易看出是一个10进制转26进制的问题,直接处理即可(按权展开)

class Solution {
public:
    string convertToTitle(int columnNumber) {
        string ans;
        while (columnNumber > 0) {
            int a0 = (columnNumber - 1) % 26 + 1;
            ans += a0 - 1 + 'A';
            columnNumber = (columnNumber - a0) / 26;
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};
3.字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。

示例 1:

输入:s = "3[a]2[bc]"
输出:"aaabcbc"

记录括号前的数字值,然后循环将括号内的数字复制即可

class Solution {
public:
    string decodeString(string s) {
        stack<char> sta;
        for(auto ch : s){
            if(ch != ']')sta.push(ch);
            else{
                stack<char> tmp;
                while(!sta.empty() && sta.top() != '['){tmp.push(sta.top());sta.pop();}
                sta.pop();
                //找到它前面的倍数
                string number;
                while(!sta.empty() && sta.top() >= '0' && sta.top() <= '9'){
                    number += sta.top();sta.pop();
                }
                reverse(number.begin(),number.end());
                int nth = stoi(number);
                for(int i = 0; i < nth;++i){
                    auto current = tmp;
                    while(!current.empty()){sta.push(current.top());current.pop();}
                }
            }
        }
        string res;
        while(!sta.empty()){
            res += sta.top();sta.pop();
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

Task5:树

课后习题

1.二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:2

思路:

深搜,每次在左右子树之间选择一个深度最低的值。

若根为空,则最小深度为0,否则初始化根的深度为INT_MAX 然后再进行深搜

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int dfs(TreeNode *root){
        if(!root) return INT_MAX;
        if(root->left == nullptr && root->right == nullptr) return 1;
        return min(dfs(root->left), dfs(root->right)) + 1;
    }
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        return dfs(root);
    }
};
2.路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

思路:

深搜:若当前节点为叶子节点且值等于targetsum,返回true

否则,每次往下搜一层,目标值减去当前节点的值

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root) return false;
        if(!root->left && !root->right 
                && targetSum == root->val) return true;
        return hasPathSum(root->left, targetSum - root->val) 
            || hasPathSum(root->right, targetSum - root->val); 
    }
};
3.二叉搜索树迭代器

题意:类似于设计一个java的迭代器,只不过这里的迭代器是按中序遍历的顺序来迭代

思路,由于二叉树可以通过链式结构转为线性结构,所以我们按中序遍历将值存到数组里面

然后便可轻松判断hasNext()和next()

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class BSTIterator {
public:
    BSTIterator(TreeNode* root) {
        idx = 0;
        to_arr(root, v);
    }
    void to_arr(TreeNode *root, vector<int> &vv){
        if(!root) return ;
        to_arr(root->left, vv);
        vv.push_back(root->val);
        to_arr(root->right, vv);
    }
    int next() {
        return v[idx++];
    }
    
    bool hasNext() {
        return idx != v.size();
    }
    vector<int> v;
    int idx;
};

/**
 * Your BSTIterator object will be instantiated and called as such:
 * BSTIterator* obj = new BSTIterator(root);
 * int param_1 = obj->next();
 * bool param_2 = obj->hasNext();
 */

Task6:位运算

课后习题:

1.2 的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

思路:

由于计算机采用2进制,所以我们可以根据二进制的特点来进行计算

因为2的幂在2进制的表示下为 10000… ,所以判断一个数是否是2的幂只需判断其除了高位外是否都是0即可

因为 1 & 0 = 1 ,所以我们可以通过 n &(n-1) == 0 即可判断是否低位都是0

class Solution {
public:
    bool isPowerOfTwo(int n) {
        return n > 0 && (n & (n-1)) == 0;
    }
};
2.只出现一次的数字 II

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

思路:

由于每个数字只出现三次,所以我们可以通过枚举每一位二进制值,若该位的二进制值和为3的整数倍,则说明答案值该位为0,否则为1,数据范围在 int32位以下,所以我们枚举32位即可。

或运算的性质就类似于 按位不进位加法 , 因为 1 | 0 = 1 ,1 | 1 = 1,所以我们对答案求和时就可用或来按位求和

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for(int i = 0; i < 32; i++){
            int val = 0;
            for(auto &c : nums){
                val += (c >> i) & 1;
            }
            if(val % 3){
                ans |= 1 << i;
            }
        }
        return ans;
    }
};
3.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

思路:

这里我们使用状态枚举即可,由于二进制的性质,每一位上不是0就是1,所以我们可以通过枚举 1 - 2^n的值来选择每个值,0一般被用于表示不选,1则表示选。

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> res;
        for(int i = 0; i < (1 << n); i++){
            int t = i;
            vector<int> temp;
            for(int j = 0; j < n; j++){
                if((t >> j) & 1){
                    temp.push_back(nums[j]);
                }
            }
            res.push_back(temp);
        }
        return res;
    }
};

Task7: 双指针

课后习题

1.删除排序链表中的重复元素

题意:将排序链表中重复出现的多余的元素删除,使其只出现一次

若下一个节点不空且相同,则一直更新结点即可

/**
 * 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* deleteDuplicates(ListNode* head) {
        if (!head) return nullptr;

        ListNode *prev = new ListNode(-123);
        ListNode *cur = prev;
        while (head) {
            while (head->next && head->val == head->next->val) {
                head = head->next;
            }
            cur->next = new ListNode(head->val);
            cur = cur->next;
            head = head->next;
        }
        return prev->next;
    }
};
2.环形链表

题意:判断该链表是否有环

set判重即可

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        set< ListNode* > st;
        ListNode *temp = head;
        while(temp){
            if(st.count(temp)) return true;
            st.insert(temp);
            temp = temp->next;
        }
        return false;
    }
};
3.排序链表

题意:将给定链表排序并返回排序后的链表

将所有值保存并排序再回到链表全部替换顺序即可

/**
 * 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* sortList(ListNode* head) {
        priority_queue<int,vector<int>,greater<int>> que;
        auto temp = head;
        while(temp){
            que.push(temp->val);
            temp = temp->next;
        }
        temp = head;
        while(temp){
            temp->val = que.top();
            que.pop();
            temp = temp->next;
        }
        return head;
    }
};

Task8:搜索

课后习题

1.对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:

img

由于是镜像对称,所以我们递归时拿根节点的左子树和右子树比较即可

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool search(TreeNode *l, TreeNode *r){
        if(!l && !r) return true;
        if(!l || !r || l->val != r->val) return false;
        return search(l->left, r->right) && search(l->right, r->left);
    }
    bool isSymmetric(TreeNode* root) {
        return search(root, root);
    }
};
2.二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

中序遍历即:左根右

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */

class Solution {
public:
    void search(TreeNode *root){
        if( root == NULL ) return ;
        search(root->left);
        res.push_back(root->val);
        search(root->right);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        res.clear();
        search(root);
        return res;
    }
    vector<int> res;
};
3.二叉搜索树中第K小的元素

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

由于二叉树搜索树的特征就是左子节点值小于根节点,右子节点值大于根节点,那么我们按照中序遍历即可知道第k小的元素

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void inorder(TreeNode *root, vector<int>& ans){
        if(!root) return ;
        inorder(root->left, ans);
        ans.emplace_back(root->val);
        inorder(root->right, ans);
    }
    int kthSmallest(TreeNode* root, int k) {
        vector<int> ans;
        inorder(root, ans);
        return ans[k-1];
    }
};

Task9:排序

课后习题

1.有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

求完平方再排序

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> res;
        for(auto n : nums){
           res.emplace_back(n*n);
        }
        sort(begin(res),end(res));
        return res;
    }
};
2.数组的相对排序

给你两个数组,arr1arr2arr2 中的元素各不相同,arr2 中的每个元素都出现在 arr1 中。

arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

用pair记录相对位置,重新排序即可,未出现的数字权值可以设置成索引下标值

class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        map<int,int> mp;
        for(int i = 0; i < arr2.size(); i++){
            mp[arr2[i]] = i;
        }
        sort(begin(arr1), end(arr1),[&](int a, int b){
            if(mp.find(a) == mp.end() && mp.find(b) == mp.end()){
                return a < b;
            }
            if(mp.find(a) == mp.end()) return false;
            if(mp.find(b) == mp.end()) return true;
            return mp[a] < mp[b];
        });
       
        vector<int> ans = arr1;
        return ans;
    }
};
3. 对链表进行插入排序

给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头

插入排序 算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

偷个懒,排序完再存回链表

/**
 * 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:
    void insert_sort(vector<int> &v){
        int i, j, temp;
        for(i = 1; i < v.size(); i++){
            temp = v[i];
            for(j = i -1; j >= 0 && v[j] > temp; j--){
                v[j+1] = v[j];
            }
            v[j+1] = temp; 
        }
    }
    ListNode* insertionSortList(ListNode* head) {
        ListNode *t = head;
        vector<int> v;
        while(t){
            v.push_back(t->val);
            t = t->next;
        }
        insert_sort(v);
        reverse(begin(v), end(v));
        t = head;
        while(t){
            t->val = v.back();
            v.pop_back();
            t = t->next;
        }
        return head;
    }
};

课后习题

1.买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

记录第i天前的最低价格,然后在第i天卖出的价格,取最大值就是答案

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n <= 1) return 0;
        int min_price = INT_MAX, max_ans = 0;
        for(int i = 0; i < n; i++){
            if(prices[i] < min_price) min_price = prices[i];
            else max_ans = max(max_ans, prices[i] - min_price);
        }
        return max_ans;
    }
};
2.买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。

在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

每天有两个状态,持有一股或零股,每天又是一个状态,所以我们我们设

d p [ i ] [ 0 ] 为 第 i 天 持 有 0 股 股 票 所 能 获 得 的 最 大 利 润 dp[i][0] 为第i天持有0股 股票所能获得的最大利润 dp[i][0]i0

d p [ i ] [ 1 ] 为 第 i 天 持 有 1 股 股 票 所 能 获 得 的 最 大 利 润 dp[i][1] 为第i天持有1股 股票所能获得的最大利润 dp[i][1]i1

那么 d p [ i ] [ 0 ] 就 可 能 由 d p [ i − 1 ] [ 0 ] 和 d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] 转 移 而 来 dp[i][0] 就可能由dp[i-1][0] 和 dp[i-1][1] + prices[i] 转移而来 dp[i][0]dp[i1][0]dp[i1][1]+prices[i]

(这里表示我们前一天的股票还是0股,和 前一天有1股,但今天卖掉了,所以是 + prices[i])

d p [ i ] [ 1 ] 则 是 由 d p [ i − 1 ] [ 1 ] 和 d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] dp[i][1] 则是由 dp[i-1][1] 和 dp[i-1][0] - prices[i] dp[i][1]dp[i1][1]dp[i1][0]prices[i] 转移而来

初始状态: d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][0] = 0, dp[0][1] = -prices[0] dp[0][0]=0,dp[0][1]=prices[0]

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n,vector<int>(2));
        dp[0][0] = 0, dp[0][1] = -prices[0];
        for(int i = 1; i < n; i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
        }
        return dp[n-1][0];
    }
};
3.最长回文子序列给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

因为回文序列具有和dp一样的子结构特性,所以我们可以设 d p [ i ] [ j ] 为 第 i 个 位 置 到 第 j 个 位 置 的 最 长 回 文 子 序 列 长 度 dp[i][j] 为 第i个位置到第j个位置的最长回文子序列长度 dp[i][j]ij

d [ i ] [ i ] = 1 d[i][i] = 1 d[i][i]=1 (单个字符就是一个回文串)

那么当 s [ i ] = = s [ j ] 时 , d p [ i ] [ j ] 应 等 于 d p [ i + 1 ] [ j − 1 ] + 2 s[i] == s[j] 时, dp[i][j] 应等于 dp[i+1][j-1] + 2 s[i]==s[j]dp[i][j]dp[i+1][j1]+2

而当 s [ i ] ! = s [ j ] 时 , d p [ i ] [ j ] 应 等 于 m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) s[i] != s[j] 时,dp[i][j]应等于max(dp[i+1][j], dp[i][j-1]) s[i]!=s[j]dp[i][j]max(dp[i+1][j],dp[i][j1])

因为我们要求整个字符串的最长回文子序列,所以最后答案应该是 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n1]

那么我们两重循环,外层从后往前计算即可

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.length();
        vector<vector<int>> dp(n,vector<int>(n));
        for(int i = n-1; i >= 0; i--){
            dp[i][i] = 1;
            for(int j = i+1; j < n; j++){
                if(s[i] != s[j]){
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
                }else{
                    dp[i][j] = dp[i+1][j-1] + 2;
                }
            }
        }
        return dp[0][n-1];
    }
};

Task11:分治

课后习题

1. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

根据题意可知排序后这个元素一定是位于数组中间的,所以我们排序后取中间的哪个值即可

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size()/2];
    }
};
2.最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

dp:由于要求子数组连续,故我们可以在子数组变小时将新的子数组和 从当前值开始求和

然后求解最大值即可

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
       int ans = nums[0], n = nums.size(), sum = 0;
       for(int i = 0; i < n; i++){
           sum = max(sum + nums[i], nums[i]);
           ans = max(ans, sum);
       }
       return ans;
    }
};
3.从前序与中序遍历序列构造二叉树

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

img

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

因为前序的第一个节点就是根节点,故可以在中序节点中找到根节点,然后划分成左右子树部分,然后先对左子树继续进行寻根,一直把所以节点找完,然后就是我们的二叉树了

class Solution {
public:
    TreeNode* recurTree(int leftin,int rightin,int leftpre,int rightpre,vector<int>&preorder, vector<int>&inorder){
        if(leftpre>rightpre || leftin>rightin){return nullptr;}
        TreeNode* root=new TreeNode(preorder[leftpre]);
        int k;
        for(int i=leftin;i<inorder.size();i++){
            if(inorder[i]==root->val){k=i;break;}
        }
        root->left=recurTree(leftin,k-1,leftpre+1,leftpre+(k-leftin),preorder,inorder);
        root->right=recurTree(k+1,rightin,leftpre+(k-leftin)+1,rightpre,preorder,inorder);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        return recurTree(0,inorder.size()-1,0,preorder.size()-1,preorder,inorder);
    }
};

Task12:哈希表

课后习题

1.存在重复元素 II

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 ij ,满足 nums[i] == nums[j]abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

示例 1:

输入:nums = [1,2,3,1], k = 3
输出:true

哈希表记录每个值的位置,然后判断即可

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int,int> ump;
        for(int i = 0; i < nums.size(); i++){
            int n = nums[i];
            if(ump.count(n) && i - ump[n] <= k) return true;
            ump[n] = i;
        }
        return false;
    }
};
2.宝石与石头

给你一个字符串 jewels 代表石头中宝石的类型,另有一个字符串 stones 代表你拥有的石头。 stones 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

字母区分大小写,因此 "a""A" 是不同类型的石头。

示例 1:

输入:jewels = "aA", stones = "aAAbbbb"
输出:3

哈希表记录哪些石头是宝石,然后判断石头是否是宝石之一即可

class Solution {
public:
    int numJewelsInStones(string jewels, string stones) {
        unordered_map<char,int> ump;
        int ans = 0;
        for(auto &c : jewels){
            ump[c] = 1;
        }
        for(auto &c : stones){
            if(ump.count(c)){
                ans++;
            }
        }
        return ans;
    }
};
3.子域名访问计数

网站域名 "discuss.leetcode.com" 由多个子域名组成。顶级域名为 "com" ,二级域名为 "leetcode.com" ,最低一级为 "discuss.leetcode.com" 。当访问域名 "discuss.leetcode.com" 时,同时也会隐式访问其父域名 "leetcode.com"以及 "com"

计数配对域名 是遵循 "rep d1.d2.d3""rep d1.d2" 格式的一个域名表示,其中 rep 表示访问域名的次数,d1.d2.d3 为域名本身。

  • 例如,"9001 discuss.leetcode.com" 就是一个 计数配对域名 ,表示 discuss.leetcode.com 被访问了 9001 次。

给你一个 计数配对域名 组成的数组 cpdomains ,解析得到输入中每个子域名对应的 计数配对域名 ,并以数组形式返回。可以按 任意顺序 返回答案。

示例 1:

输入:cpdomains = ["9001 discuss.leetcode.com"]
输出:["9001 leetcode.com","9001 discuss.leetcode.com","9001 com"]
解释:例子中仅包含一个网站域名:"discuss.leetcode.com"。
按照前文描述,子域名 "leetcode.com" 和 "com" 都会被访问,所以它们都被访问了 9001 次。

其实就是统计每个串出现了多少次,同样哈希记录求和即可,最后按任意顺序输出

tips:注意域名可能是d2.d3这种情况即可

class Solution {
public:
    vector<string> subdomainVisits(vector<string>& cpdomains) {
        vector<string> res;
        unordered_map<string,int> ump;
        for(auto &s : cpdomains){
            string num, domain, d2, d3;
            for(int i = 0; i < s.length(); i++){
                if(s[i] == ' '){
                    num = s.substr(0,i);
                    domain = s.substr(i+1);
                    break;
                }
            }
            int cnt = 1;
            for(int i = 0; i < domain.length(); i++){
                if(domain[i] == '.'){
                    if(cnt == 1){
                        cnt++;
                        d2 = domain.substr(i+1);
                    }else{
                        cnt++;
                        d3 = domain.substr(i+1);
                        break;
                    }
                }
            }
            ump[string(" " + domain)] += stoi(num);
            ump[string(" " + d2)] += stoi(num);
            if(cnt == 3) ump[string(" " + d3)] += stoi(num);
        }
        for(auto &[k,v] : ump){
            res.push_back(string(to_string(v) + k));
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值