LeetCode 笔记

  • 1.无重复字符的最长子串

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

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

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

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = 0;
        int start = 0;
        unordered_map<char, int> dic;
        for(int end = 0; end < s.size(); end++) {
            if(dic.find(s[end]) != dic.end()) {
                start = max(dic[s[end]] + 1, start);      // 关键点,即使更新左指针
            }
            len = max(len, end - start + 1);
            dic[s[end]] = end;                            // 关键点,更新重复字符的下标
        }
        return len;
    }
};

--------------重要------------------

unordered_map存储键值对时,如遇到一键多值时,可先分析如下情况:

1.已存储的值是否在后续还会使用到,如若不再使用,则直接更改该健对应的值即可,无需担心一键多值问题。

  • 2.有效的括号

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

注意:括号字符串不一定是按顺序的"()[]{}",也可能是"([{}])"! 

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk1;
        unordered_map<char, char> dic = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        for(int i = 0; i < s.size(); i++) {
            if(!stk1.empty() && dic[s[i]] == stk1.top()) stk1.pop();
            else stk1.push(s[i]);
        }
        if(stk1.empty()) return true;
        else return false;
    }
};

掌握技能:

1.对于需要判断对应的情况,可存储在无序容器中,判断更方便;

2.栈stack为空时,调用top()函数会报错。

  •  3.最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成
// 该方法的思路是先找到回文子串的中间位置,然后向两侧扩展,进而找到最长的回文子串
class Solution {
public:
    pair<int, int> expandAC(const string& s, int left, int right) {
        while(left >= 0 && right < s.size() && s[left] == s[right]) {
            left--;
            right++;
        }
        return {left+1, right-1};
    }
    
    string longestPalindrome(string s) {
        int start = 0, end = 0;
        for(int i = 0; i < s.size(); i++) {
            // 回文子串的中间位置可能是单个字符,也可能是双字符
            auto [left1, right1] = expandAC(s, i, i);        
            auto [left2, right2] = expandAC(s, i, i+1);
            if((right1-left1) > (end-start)) {
                end = right1;
                start = left1;
            }
            if((right2-left2) > (end-start)) {
                end = right2;
                start = left2;
            }
        }
        return s.substr(start, end - start + 1);
    }
};

掌握技能:

1.pair的应用(1、将2个数据组合成一组数据;2、一个函数需要返回2个数据);

2.pair的头文件为#include <utility>;

3.通过挨个遍历可能中间位置扩展的子串,进而找到最长子串;

4.字符串的子串用substr(pos, len),第一个参数为起始的位置,第二个参数为需要拷贝长度

  •  4.合并两个有序链表

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

示例 1:

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

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列 
// 原始版,较啰嗦
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* head = new ListNode();
        ListNode* cur = head;
        ListNode* cur1 = list1;        // 无需设置新的指针
        ListNode* cur2 = list2;
        while(cur1 != nullptr && cur2 != nullptr) {
            if(cur1->val < cur2-> val) {
                cur->next = new ListNode(cur1->val);
                cur = cur -> next;
                cur1 = cur1 -> next;
            } else {
                cur->next = new ListNode(cur2->val);
                cur = cur -> next;
                cur2 = cur2 -> next;
            }
        }
        // 若有链表还未遍历完毕,只需直接全部添加到后面即可
        if(cur1 == nullptr && cur2 != nullptr) {    
            while(cur2 != nullptr) {
                cur->next = new ListNode(cur2->val);
                cur = cur -> next;
                cur2 = cur2 -> next;
            }
        } else if(cur1 != nullptr && cur2 == nullptr) {
            while(cur1 != nullptr) {
                cur->next = new ListNode(cur1->val);
                cur = cur -> next;
                cur1 = cur1 -> next;
            }
        }
        return head->next;
    }
};

// 答案版
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode(-1);    // 思路一致,设立一个虚拟头节点

        ListNode* prev = preHead;
        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                prev->next = l1;
                l1 = l1->next;
            } else {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev->next = l1 == nullptr ? l2 : l1;

        return preHead->next;
    }
};
  • 5.二叉树的中序遍历

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

示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点数目在范围 [0, 100] 内
  • -100 <= Node.val <= 100
// 递归法
// 递归法无需显示维护栈,递归时程序自动生成
class Solution {
public:
    void inorder(TreeNode* root, vector<int>& res) {
        if(!root) return;            // 结束条件
        // 单层逻辑
        inorder(root->left, res);    // 遍历左子树至叶子节点
        res.push_back(root->val);    // 保存左叶子节点及根节点的值
        inorder(root->right, res);   // 遍历右子树至叶子节点
    }
    
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        inorder(root, res);        // 传递参数(传入根节点,传出遍历路径)
        return res;
    }
};

// 迭代法
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;    // 迭代法需显示创建维护栈
        while(root || !stk.empty()) {
            while(root) {        // 依次遍历左子树至叶节点
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            res.push_back(root->val);    // 遍历到的叶节点存储
            root = root->right;          // 遍历右子树
        }
        return res;
    }
};

掌握内容:

1.中序遍历:左-根-右;前序遍历:根-左-右;后序遍历:左-右-根。

2.前、中、后序遍历都是深度优先遍历(DFS)。

3.递归方法的模板:

        1)结束条件(节点为nullptr);

        2)单层逻辑(遍历左-根-右);

        3)传递参数(传入根节点,返回遍历路径)。

  • 6. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

示例 1:

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

示例 2:

输入:root = [1,null,2]
输出:2

提示:

  • 树中节点的数量在 [0, 104] 区间内。
  • -100 <= Node.val <= 100
// 深度优先
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};

注意:

1.递归法需要加强,不太会用;

  • 7. 买卖股票的最佳时机

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

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

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

示例 1:

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

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104
// 双指针暴力法会超时,无法使用

// 通过记录历史最低值与最大利润,减少循环次数

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int inf = 1e9;
        int minprice = inf, maxprofit = 0;        // 给最低价赋最大值,最高价赋最小值
        for(int price : prices) {
            maxprofit = max(maxprofit, price - minprice);    // 记录当前价格与历史最低价的利润与历史最大利润对比,更新最大利润
            minprice = min(minprice, price);    // 遍历最低值
        }
        return maxprofit;
    }
};

注意:

1.暴力法在大多数场景下都会超时,优先选择单层循环的方法; 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值