leetcode 11-20

11. 盛最多水的容器

分析

用两个指针, 一个指针指向最开头, 一个指针指向最结尾,
如果第2个指针的高度比较低的话, 那就把第2个指针, 往前移动1位
如果第1个指针的高度比较低的话, 那就把低1个指针, 往后移动1位
每移动完一次, 求下当前两指针所构成的面积, 更新下最大值
模拟样例:
第1次:高度: [1, 8, 6, 2, 5, 4, 8, 3, 7]
下标: [0, 1, 2, 3, 4, 5, 6, 7, 8]

距离是8, 高度是1, 面积8

第2次, 高度: [1, 8, 6, 2, 5, 4, 8, 3, 7] 因为1比较矮, 前指针往后移动1格

距离是7, 高度是7, 7 x 7 = 49

做法的正确性

证明: 两指针一开始在最优解的两侧, 每次其中一个指针往中间靠拢, 必然会有其中一个指针到达最优解的一边, 要么左边先到, 要么右边先到, 一定会有一个先到, 不妨设左边先到
在这里插入图片描述
假设左边先到的话
在这里插入图片描述
然后我们证明后指针所指向的高度, 严格小于 前指针所指向的高度
反证法: 如果后指针所指向的高度 >= 前指针所指向的高度, 那么会有新的面积(图中红色框的面积)
原最优解的面积最大也是 蓝色虚线的面积, 显然小于红色面积, 与假设蓝色指针所指向的位置是最优解, 矛盾
在这里插入图片描述
因此, 如果左边先到达边界, 后指针的高度必然是 < 前指针的高度, 因此后指针一直可以往前走, 那么一定可以遍历到最优解, 取到的最大值就是解

code

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0;
        for (int i = 0, j = height.size() - 1; i < j;){
            res = max(res, min(height[i], height[j]) * (j - i));
            if (height[i] < height[j]) i ++;
            else j --;
        }
        return res;
    }
};

12. 整数转罗马数字

分析

罗马数字一般左边大, 右边小, II, 表示2, XII, 10 + 2; 如果左边小, 右边大, 表示减法, IV, 表示5 - 1 = 4
先看

个位123456789
IIIIIIIVVVIVIIVIIIIX
十位102030405060708090
XXXXXXXLLLXLXXLXXXXC
百位100200300400500600700800900
CCCCCCCDDDCDCCDCCCCM
千位100020003000400050006000700080009000
MMMMMM

有了这个表示后, 可以直接查表做
比方说1234, 千位是1-M, 百位是2-CC, 十位是3-XXX, 个位是4-IV
MCCXXXIV

再来个3999
千位是3-MMM, 百位是9-CM, 十位是9-XC, 个位是9-IX
MMMCMXCIX

因此直接模拟即可, 实现的时候一些技巧

技巧

千位的话, 有几个1千, 值就-几个1千, 答案就+几个M
百位的话, 有规律, 100, 200, 300之间的话, 有几个1百, 就+几个C(这里指数字->罗马), 600, 700, 800, 有几个C就加几个百(这里指罗马->数字)
100, 400 500 900没有规律, 单独需要记住
从大到考虑这几项, 如果>= 900, 就对应成CM, -900; >= 500, 就对应成D, -500; >= 400, 对应成CD, - 400; >= 100, 对应成C, - 100

模拟下, 如果数是942, 那么会对应一个CM, 然后-900
如果是8xx, 那么会对应D, -500, 变成3xx, 3xx >= 100, 会给一个C, 然后变成2xx, 会再给一个C, 变成1xx, 再给个C, 变成xx, 所以合起来是DCCC, 正好对应800的DCCC

对应十位, 个位也是类似, 单独扣出, 90, 50, 40, 10, 9, 5, 4, 1

模拟下2649

>= 1000, 给个M, 变成1649, 仍然>= 1000, 再给M, 变成649, 当前:MM
649 >= 500, 给个D, 变成149, >= 100, 给个C,变成49 当前:MMDC
49 >= 40, 给个XL, 变成9 当前:MMDCXL
9>=9, 给个IX, 变成0, 结束, 当前MMDCXLIX

code

class Solution {
public:
    string intToRoman(int num) {
        int value[] = {
            1000,
            900, 500, 400, 100,
            90, 50, 40, 10,
            9, 5, 4, 1
        };
        string reps[] = {
            "M", 
            "CM", "D", "CD", "C",
            "XC", "L", "XL", "X",
            "IX", "V", "IV", "I",
        };
        string res;
        for (int i = 0; i < 13; i ++ ){
            while (num >= value[i]){
                res += reps[i];
                num -= value[i];
            }
        }
        return res;
    }
};

13. 罗马数字转整数

分析

找规律, 除了4, 40, 400, 9, 90, 900, 其他罗马数字相加就是结果
比方说, 80 = 50 + 10 + 10 + 10, L+X+X+X; 700 = 500 + 100 + 100, D+C+C
但是4, 9这些怎么处理, 可以发现前面字母 < 后面字母, 就用减法, 其他情况就加
2649 MMDCXLIX
M: 1000, M: 1000, 2000
DC: D= 500, C = 100, D>C, 那么 500 + 100 = 600
XL: X= 10, L = 50, X<L, 那么 -10 + 50
IX: I= 1, X = 10, I < X, 那么 -1 + 10

code

因为是从千位往个位算, 因此遇到MD这个, 必然会先加上千位, 然后进行DC判断

class Solution {
public:
    int romanToInt(string s) {
        unordered_map<char, int> hash;
        hash['I'] = 1, hash['V'] = 5;
        hash['X'] = 10, hash['L'] = 50;
        hash['C'] = 100, hash['D'] = 500;
        hash['M'] = 1000;

        int res = 0;
        for (int i = 0; i < s.size(); i ++ )
            if (i + 1 < s.size() && hash[s[i]] < hash[s[i + 1]])
                res -= hash[s[i]];
            else res += hash[s[i]];
        return res;
    }
};

14. 最长公共前缀

分析

首先考虑下时间复杂度, 最起码是所有字符串的长度之和
因为只要能做到时间复杂度 < 所有字符串的长度之和, 就可以了
首先看下每个字符串的第1个字母, 是否一样? 如果不一样, 公共前缀是空的
一样的话, 最长公共前缀起码是1, 再来看下第2个字母是否都一样, 还一样的话, 看第3个
直到找到不一样的, 比如第4个字母, 不全一样, 那么最长的公共前缀就是3

时间复杂度 <= 所有字符串长度之和
两重循环, 第1重循环枚举当前枚举的是第几个字符, 第二重循环枚举下所有字符串, 看下所有字符串的当前位置是否都一样

code

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res;
        if (strs.empty()) return res;

        for (int i = 0;; i ++ ){ // 循环没有结束条件, 因为循环里必然会退出
            if (i >= strs[0].size()) return res; // 如果当前循环位置 >= 第1个字符串长度, 直接返回
            else {
                char c = strs[0][i]; // 取出第1个字符串当前位置的字母
                for (auto& str : strs) // 遍历所有字符串
                    if (i >= str.size() || str[i] != c) return res; // 如果遍历到的字符串长度不够 或者 字母不相同
                res += c;
            }
        }
        return res;
    }
};

code(代码优化)

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res;
        if (strs.empty()) return res;
        for (int i = 0; i < strs[0].size(); i ++ ){
            char c = strs[0][i];
            for (auto& str : strs){
                if (str[i] != c) return res;
            }
            res += c;
        }
        return res;
    }
};

15. 三数之和

分析

注意是三个元素a, b, c. a, b, c不能是同一个元素, 但是他们的数值可以是一样的.
不能重复, 1, 2, 3和2, 3, 1算同一个
双指针算法:
双指针算法一定要保证序列有序, 有序才能用双指针
所以先将整个数组排序, 然后先枚举第一个数, 因为双指针, 但是有3个数, 不可能有3指针算法. 即: 先枚举第1个数的位置
为了避免重复, 要求 i < j < k i < j < k i<j<k, 这样就会减少重复情况
i i i固定以后, j , k j, k j,k可以用双指针了, 假设 j j j固定了, 找到一个最小的k使得nums[j] + nums[k] + nums[i] >= 0, 那么对于每一个j, 都有一个固定的k(可以求出来这个k)
当然可以先枚举j, 再枚举k 两维循环, 找最小的k
暴力枚举的话O(n^2), 可以找到每一个j对应的k
双指针的话, 可以到O(n)

为什么可以用双指针
因为整个序列是有序的, nums[i]是固定的, 当j往后移动的时候, nums[j]⬆️, 那么nums[k]⬇️(因为需要保证nums[j] + nums[k] + nums[i] >= 0)
j->j’的话, k一定往左走, 走到k’在这里插入图片描述
在这里插入图片描述

yxc:因此j只会递增, k只会递减, j最多只会走n, k最多也只会走n, 加到一块O(2n)(但是, 我认为j + k最多走n步, 应该上课讲错了, 虽然答案也是O(n))

重复答案处理

比方说 1 1 1 1 1 -2
1 1 1 1 1 -2
1 1 1 1 1 -2
其实是同一种, 都是 1 1 -2
在枚举完第1个i的值=1的时候, 走到下一个位置, 发现nums[i]仍然是1, (其实这种情况在上一个位置都枚举完了), 因此下一个nums[i] = 1的情况都要略过, 因此i必须往后, 走到非1的位置. 即: 下一个nums[i]与上一个nums[i]相同的话, 就跳过

联动题

  1. 最接近的三数之和
    四数之和

code

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i ++ ){
            if (i && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1, k = nums.size() - 1; j < k; j ++ ){
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k --; 
                // k是最小的数 使得>= 0, 成立, 才保证单调
                // 如果下一个位置仍然可以保证>= 0, 那么k往前移动一格
                if (nums[i] + nums[j] + nums[k] == 0) res.push_back({nums[i], nums[j], nums[k]});
            }
        }
        return res;
    }
};

16. 最接近的三数之和

分析

最接近有两重含义, 左边最接近, 和右边最接近, 两种情况都要考虑到
保证答案只有1个, 可以不用考虑判重问题
最坏情况下, 三重循环, O(n^3)
考虑优化:
先排序, 双指针, 因为有3个变量, 不好用双指针, 所以先枚举一个变量, 枚举i, nums[i]固定了,
对于j, k, 再枚举j, 对于每一个j找到一个最小的k使得nums[i] + nums[j] + nums[k] >= target
这样就求出了>= target的最小值
另外的情况, 那么nums[i] + nums[j] + nums[k - 1] < target是必然的, 也是最接近的
用上题的算法, 求出来k, 然后k再 -1, 就是左边的最接近的情况, 取min

code

因为此题计算过程要记录差值的最小值, 和总和, 所以用pair来存, 1.存差值, 2.存总和

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        pair<int, int> res(INT_MAX, INT_MAX);// 1.保存差值, 用于计算的时候取最小 2.保存总和, 来输出答案
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i ++ ){
            for (int j = i + 1, k = nums.size() - 1; j < k; j ++ ){
                while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= target) k --;
                int s = nums[i] + nums[j] + nums[k];
                res = min(res, make_pair(abs(s - target), s)); // s不一定>= target, 因为可能给的数据就是<= target的
                if (j < k - 1){ // 一定要写, 否则j循环会和k - 1重合
                    int s = nums[i] + nums[j] + nums[k - 1];
                    res = min(res, make_pair(target - s, s));
                }
            }
        }
        return res.second;
    }
};

17. 电话号码的字母组合

分析

爆搜顺序, 考虑当前位置填哪些字母, 需要path, 当前位置u, 两个参数
递归树
在这里插入图片描述
时间复杂度: 每个字母最多4种选择, n个字母, 4^n * push_back需要O(n)复杂度, O(4^n * n)

code

class Solution {
public:
    string strs[10] = {
        "",
        "", "abc", "def",
        "ghi", "jkl", "mno",
        "pqrs", "tuv", "wxyz",
    };
    vector<string> res;
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return res;
        dfs(digits, 0, "");
        return res;
    }
    void dfs(string& digits, int u, string path){
        if (u == digits.size()){
            res.push_back(path);
            return;
        }else {
            for (auto& c : strs[digits[u] - '0'])
                dfs(digits, u + 1, path + c);
        }
    }
};

18. 四数之和

分析

思路同15, 16题, 先枚举两个变量, 后两个变量用双指针优化掉, 变成O(n)
去重的方法和前面一样, 如果当前数和前面一样, 就跳过
推广:
如果求5数之和的话, 先枚举其中3个数
如果求n个数之和的话, 那么dp(背包问题)

code

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i ++ ){
            if (i && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.size(); j ++ ){
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                for (int k = j + 1, u = nums.size() - 1; k < u; k ++ ){
                    if (k > j + 1 && nums[k] == nums[k - 1]) continue;
                    while (u - 1 > k && nums[i] + nums[j] + nums[k] + nums[u - 1] >= target) u --;
                    if (nums[i] + nums[j] + nums[k] + nums[u] == target) res.push_back({nums[i], nums[j], nums[k], nums[u]});
                }

            }
        }
        return res;
    }
};

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

分析

删的话, 可能会删掉头节点, 因此需要加1个虚拟头节点, 虚拟头节点一定不会被删
所以这题要删除倒数第n个点, 核心是找到倒数第n个点的前一个点, 然后指针改一下
在这里插入图片描述
怎么找倒数第k个点, 先求下链表的总长度n, 要找的倒数第k个点的前一个点, 也就是倒数第k+1个点
然后从头往后遍历, 遍历到倒数第 k + 1个点
跳1步 能跳到第2个点
倒数第1个点, 就是当前的第n - 1 + 1个点
倒数第k + 1个点, 就是当前的 n - (k + 1) + 1 = n - k个点,
因此需要跳n - k - 1步

code(两次扫描)

/**
 * 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 k) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        int n = 0;
        for (auto p = dummy; p; p = p->next) n ++;
        auto p = dummy;
        for (int i = 0; i < n - k - 1; i ++ ) p = p->next;
        p->next = p->next->next;

        return dummy->next;
    }
};

code(一次扫描)

first指针(图中红色的)移动到k + 1的位置, 即:先循环k + 1次. 然后first指针只有(n - k - 1)步可以走了, 此时再让firstsecond指针一起走
那么结束后, 绿色指针就会走到n - k - 1的位置, 也就是倒数第k个点的前一个点
然后如上做法, 调整指针位置即可
在这里插入图片描述

/**
 * 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 k) {
        ListNode* dummy = new ListNode(-1);
        dummy-> next = head;

        ListNode* first = dummy;
        ListNode* second = dummy;

        for (int i = 0; i <= k; i ++ ) first = first->next;
        while (first){
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        return dummy->next;
    }
};

20. 有效的括号

分析

把左括号看成妹子, 如果是左括号, push到栈里, 然后往后找, 然后发现右括号(男生), 男生会找最近的左括号且能匹配的, 如果能匹配的话, 就弹出栈顶的左括号(妹子), 并且继续往后走;不匹配的话, 返回false
最后如果栈里有元素, 表示不合法, 栈为空则合法

code

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (int i = 0; i < s.size(); i ++ ){
            if (s[i] == '(' || s[i] == '[' || s[i] == '{'){
                stk.push(s[i]);
            }else if (s[i] == ')'){
                if (stk.empty() || stk.top() != '(') return false; // 如果当前栈为空, 没有妹子可以领取了, 但当前来了一个男生, 或者 栈顶女生和当前男生匹配不上
                stk.pop();
            }else if (s[i] == ']'){
                if (stk.empty() || stk.top() != '[') return false;
                stk.pop();
            }else {
                if (stk.empty() || stk.top() != '{') return false;
                stk.pop();
            }
        }
        return stk.empty();
    }
};

code(简便写法)

可以观察ASCII码发现左括号和右括号差值<= 2, 所以在判断匹配的时候, 直接看差值就行了

class Solution {
public:
    bool isValid(string s) {
        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.size() && abs(stk.top() - s[i]) <= 2) stk.pop();
                else return false;
            }
        }
        return stk.empty();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园失物招领系统管理系统按照操作主体分为管理员和用户。管理员的功能包括字典管理、论坛管理、公告信息管理、失物招领管理、失物认领管理、寻物启示管理、寻物认领管理、用户管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 校园失物招领系统管理系统可以提高校园失物招领系统信息管理问题的解决效率,优化校园失物招领系统信息处理流程,保证校园失物招领系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 ,管理员权限操作的功能包括管理公告,管理校园失物招领系统信息,包括失物招领管理,培训管理,寻物启事管理,薪资管理等,可以管理公告。 失物招领管理界面,管理员在失物招领管理界面中可以对界面中显示,可以对失物招领信息的失物招领状态进行查看,可以添加新的失物招领信息等。寻物启事管理界面,管理员在寻物启事管理界面中查看寻物启事种类信息,寻物启事描述信息,新增寻物启事信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值