leetcode 1-10

1. 两数之和

分析

返回下标
可以暴力枚举下, 两重循环, 如果和 = target, 就返回 O(n^2)
双指针的话, 需要先排序, 排序O(nlogn)
想下有没有O(n)的算法
每次枚举第2个数(s[i]), 去考虑 第2个数前面有没有数 + s[i] = target,
其实就是问前面是否存在一个数 = target - s[i]

map找一个数是log(n), 底层是平衡树, 无序的unordered_map 找数的话是O(1), 底层是hash表

从前往后扫描, 每扫描一个数, 就将该数放到hash表中, 当我们扫描到 s i s_i si的时候, 已经将 s i s_i si的所有数放到hash表中, 当访问到 s i s_i si的时候, 看一下hash表中是否有数 = target - s[i]
每个数最多会往hash表中插入1次, 每个数会查询hash表1次, hash表插入和查询操作都是O(1), 所以总共是O(n)

code

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;// 1.当前数的值 2.当前数的下标

        for (int i = 0; i < nums.size(); i ++ ){
            int another = target - nums[i]; // 找当前数的前面是否存在解
            // 由于当前数前面的数, 都放到hash表里了, 直接用hash.count()查询
            if (hash.count(another))  return {hash[another], i};  // 如果存在, 则直接返回答案
            hash[nums[i]] = i; // 否则, 将当前数插入到hash表 key = 当前数的值, value = 下标
        }
        return {};
    }

};

2. 两数相加

分析

模拟加法, 高精度加法模板类似

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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        auto dummy = new ListNode(-1), tail = dummy;
        int t = 0;
        while (l1 || l2 || t) {
            if (l1) t += l1->val, l1 = l1->next;
            if (l2) t += l2->val, l2 = l2->next;
            tail = tail->next = new ListNode(t % 10);
            t /= 10;
        }
        return dummy->next;
    }
};

3. 无重复字符的最长子串

分析

考虑该题的时候, 需要思考怎么能将所有情况都枚举到, 所有子串大概n^2 / 2种(两个端点, 端点有序, 即左端点可以[1,k]选k种, 由端点[k + 1, n]中选, n - k种)
需要在n^2/ 2种方案种, 找到无重复的子串
i(表示子串的右端点) 为分类依据, 将子串分成n类, 那么问题就变成固定了i, 找最靠左的j, 使得[j, i]不包含重复字符
在这里插入图片描述
如果暴力枚举j话, 总的时间复杂度外层要套i的循环, O(n^2)
考虑下单调性

证明在这里插入图片描述

所以当后指针i往后移动时候, 前指针j至多也是原地不动, 不可能往前移动.
这样的话, 每次i往后移动到i'的时候, 新前指针j'可以从上一个j的位置, 继续往后枚举就可以了, 这样相当于不会走回头路, 因此每个指针最多只会走n次, 加在一块的话,就是O(n)的算法
然后用hash表来维护[j, i]每个字符出现的次数
i向后移动一格的时候, 变成i + 1, 就将移动后的当前字母加入到hash表中
然后看下当前hash表中是否有重复元素, 如果有重复元素的话, 那么必然是hash[s[i + 1]](程序中应该是hash[s[i]]), 然后将j一直往后移动, 直到移动到直到i +1为止, 那么就只剩i + 1一个元素了, 就满足要求了
在这里插入图片描述

双指针, i为后指针, j为前指针
hash[i] > 1说明s[i] 元素在前面重复了, 前指针j指针需要后移, 找到不重复的位置

联动题

LeetCode 76. 最小覆盖子串
LeetCode 30. 串联所有单词的子串

code

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> hash;
        int res = 0;
        for (int i = 0, j = 0; i < s.size(); i ++ ){
            hash[s[i]] ++;// 将当前后指针i 对应的字符记录到hash表中
            while (hash[s[i]] > 1) hash[s[j ++ ]] --; // i为后指针, 当后指针遇到的字符串 > 1, 那么表示当前段末尾字符重复, 前指针后移
            res = max(res, i - j + 1);
        }
        return res;
    }
};

4. 寻找两个正序数组的中位数

分析

有简单的做法, 两个数组合并到一起sort, 然后取中位数O(m+nlog(m + n))
1.递归的方式O(log(n + m))
2.二分log(min(m,n)) ❌(非常不推荐, 边界十分复杂)
求一下两个数组的第k的元素, 让k = (m + n) / 2, 那么就得到答案了
为了解决该问题, 尽可能的分解成子问题来解决
先看下两个数组k/2位置元素
分两种情况来看

  1. A [ k 2 ] < B [ k 2 ] A[\frac{k}{2}] < B[\frac{k}{2}] A[2k]<B[2k]
    此时B[1, k/2]中严格< A [ k 2 ] A[\frac{k}{2}] A[2k]的元素个数至多为 k 2 − 1 \frac{k}{2} -1 2k1个, 因为最后一个数 A [ k 2 ] < B [ k 2 ] A[\frac{k}{2}] < B[\frac{k}{2}] A[2k]<B[2k], 所以两个数组就[1, k/2]这段合在一起严格< A [ k 2 ] A[\frac{k}{2}] A[2k]的元素个数<k个.
    并且因为 A [ k 2 ] < B [ k 2 ] A[\frac{k}{2}] < B[\frac{k}{2}] A[2k]<B[2k], 所以A[1, k/2]中所有元素不能存在第k个数(至少有个 B [ k 2 ] B[\frac{k}{2}] B[2k]卡着呢), 因此可以直接删除 A [ 1 , k / 2 ] A[1, k/2] A[1,k/2]中所有数(删掉k/2个元素), 来缩小搜索范围.
    在这里插入图片描述

  2. A [ k 2 ] > B [ k 2 ] A[\frac{k}{2}] > B[\frac{k}{2}] A[2k]>B[2k]
    同理, 第k个数一定不在 B [ 1 , k / 2 ] B[1, k/2] B[1,k/2]区间内(至少有个 A [ k / 2 ] A[k/2] A[k/2]卡着), 因此可以删除.

  3. A [ k 2 ] = B [ k 2 ] A[\frac{k}{2}] = B[\frac{k}{2}] A[2k]=B[2k]
    这种情况的话, 最好了, 因为直接就找到答案了, 答案就是 A [ k / 2 ] A[k/2] A[k/2]或者 B [ k / 2 ] B[k/2] B[k/2], 因此两段随便删除哪一段均可以

因此, 可以发现每次比较, 一定可以从中删除 k / 2 k/2 k/2个元素. 假如删除的是 B [ 1 , k / 2 ] B[1, k/2] B[1,k/2]区间中的所有元素, 那么问题就转化为在红色区间, 找第 k − k / 2 k - k/2 kk/2个元素
在这里插入图片描述
所以我们发现, 每次k/2, 当k==1的时候, 比较下两个数组的开头元素较小者即可.
k每次/2, 最多只会递归 log ⁡ k \log k logk次, k = ( m + n ) / 2 k = (m + n) / 2 k=(m+n)/2

code

代码k是从1开始, 因此计算中位数的时候, 两数组总和为偶数, 则找 t o t / 2 tot / 2 tot/2 t o t / 2 + 1 tot / 2 + 1 tot/2+1; 奇数就找 t o t / 2 + 1 tot/ 2 + 1 tot/2+1
并且为了方便, 假设第1个数组长度比较小

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int tot = nums1.size() + nums2.size();
        if (tot % 2 == 0){
            int left = find(nums1, 0, nums2, 0, tot / 2); // k从1开始, 所以tot按照[1, n]的方式计算的
            int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (left + right) / 2.0;
        }else {
            return find(nums1, 0, nums2, 0, tot / 2 + 1);
        }
    }

    int find(vector<int> &nums1, int i, vector<int> &nums2, int j, int k){
        if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
        if (k == 1) {
            if (nums1.size() == i) return nums2[j];
            else return min(nums1[i], nums2[j]);
        }
        if (nums1.size() == i) return nums2[j + k - 1];
        int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;// si 和 sj表示图中A[k / 2], B[k / 2]的后一个数,因为数组下标从0开始计算
        if (nums1[si - 1] > nums2[sj - 1]) // A[k / 2] > B[k / 2] 可以删除B[1, k / 2]
            return find(nums1, i, nums2, sj, k - (sj - j)); 
        else 
            return find(nums1, si, nums2, j, k - (si - i));
    }
};

5. 最长回文子串

分析

回文串分为两种

  1. 长度为奇数, 只需要左右两边对称即可, 中间一个无所谓
  2. 长度为偶数, 两两配对, 分别相等

先去枚举下回文串的中心点, 用两个指针, 同时从中间往两边走, 直到走到两个字符不一样/某一个走出边界为止, 这样就找到了以这个点为中心的最长的回文串了, 因为再继续走就更不一样/出界了
走的时候, 比较对应的字符, 如果一样就继续往两边走, 不一样就停止, 停下来的时候左指针位置L, 右指针位置R, 说明[L + 1, R - 1], 是以i为中心的最长的回文串, 长度就为R - 1 - (L + 1) + 1 = R - L - 1
长度为奇数的话, 将L = i - 1, R = i + 1, 即两指针在i的左右两边开始走, 无视中间的i
长度为偶数的话, L = i, R = i + 1
i的含义: 回文串的中心
枚举中心i O(n), 左右两个指针最多O(n), O(n^2)
在这里插入图片描述

联动

AcWing139. 回文子串(二分+ hash)

code

class Solution {
public:
    string longestPalindrome(string s) {
        string res;
        for (int i = 0; i < s.size(); i ++ ) {
            int l = i - 1, r = i + 1;
            while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++;
            if (res.size() < r - l - 1) res = s.substr(l + 1, r - l - 1);

            l = i, r = i + 1;
            while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++;
            if (res.size() < r - l - 1) res = s.substr(l + 1, r - l - 1);
        }
        return res;
    }
};

6. Z 字形变换

分析

可以发现第1行是等差数列, 公差是6, 怎么来的呢?
假如行数 = n,
除了第1个数之外, 第1列有 n - 1个数, 然后斜着的, 除了第1列的最后一个数, 有 n - 1个数,
在这里插入图片描述
然后看中间的数, 将中间的数分成两种类型在竖列上的数在斜列上的数 也是公差为2n - 2的等差数列
所以中间是两个等差数列混在一起, 先写第1个等差数列一项, 再写第2个等差数列的一项, 交替
在这里插入图片描述
每个等差数列的起点都是0, 1, 2, 3一直到 n - 1, 然后斜线上的等差数列的起点需要考虑, 比如5, 4, 可以发现斜线上的数 + 第1组等差数列的起点 = 2n - 2, 因此第2组等差数列的起点 = 2n - 2 - i
在这里插入图片描述

code

class Solution {
public:
    string convert(string s, int n) {
        if (n == 1) return s; // 边界情况, 只有1行, 直接返回
        string res;
        for (int i = 0; i < n; i ++ ){
            if (i == 0 || i == n - 1){
                for (int j = i; j < s.size(); j += 2 * n - 2)
                    res += s[j];
            }else {
                for (int j = i, k = 2 * n - 2 - i; j < s.size() || k < s.size(); j += 2 * n -2, k += 2 * n - 2){
                    if (j < s.size()) res += s[j];
                    if (k < s.size()) res += s[k];
                }
            }
        }
        return res;
    }
};

7. 整数反转

分析

int写法的话, 代码溢出只有两种情况, r是正数的话是一种情况, r是负数的话, 是另外一种情况
r > 0, 溢出的话, 意味着 x > 0 x > 0 x>0, 10 r + x % 10 10r + x \% 10 10r+x%10 正的方向溢出, 即
10 ∗ r + x % 10 > m a x 10 * r + x \% 10 > max 10r+x%10>max ⬅️➡️
10 ∗ r > m a x − x % 10 10 * r > max - x \% 10 10r>maxx%10 ⬅️➡️
r > ( m a x − ( x % 10 ) ) / 10 r > (max - (x \% 10)) / 10 r>(max(x%10))/10 (max - x % 10 ) 不会溢出
即判断下这个式子, 如果成立, 那么溢出, 返回0
r < 0, 溢出的话意味着, x < 10 x < 10 x<10, 10 r + x % 10 10r + x \% 10 10r+x%10负的方向溢出
10 r + x % 10 < m i n 10r + x \% 10 < min 10r+x%10<min
10 r < m i n − ( x % 10 ) 10r < min - (x \%10) 10r<min(x%10)
r < ( m i n − ( x % 10 ) ) / 10 r < (min - (x \%10) )/ 10 r<(min(x%10))/10
在这里插入图片描述

code(long long写法)

class Solution {
public:
    int reverse(int x) {
        long long r = 0;
        while (x){
            r = r * 10 + x % 10;
            x /= 10;
        }
        if (r > INT_MAX) return 0;
        if (r < INT_MIN) return 0;
        return r;
    }
};

code(int写法)

class Solution {
public:
    int reverse(int x) {
        int r = 0;
        while (x){
            if (x > 0 && r > (INT_MAX - x % 10) / 10) return 0;
            if (x < 0 && r < (INT_MIN - x % 10) / 10) return 0;
            r = r * 10 + x % 10;
            x /= 10;
        }
        if (r > INT_MAX) return 0;
        if (r < INT_MIN) return 0;
        return r;
    }
};

8. 字符串转换整数 (atoi)

分析

  1. 先删空格
  2. 第1个字符可能是'+'/'-'

int x = s[k] - '0'
int 溢出的话, 主要会在res = res * 10 + x;

  1. 如果is_minus = 1, 那么res > (MAX - x) / 10就会上溢出

  2. 如果is_minus = -1, 那么表示 res = res * 10 + x , * is_minus后会下溢出, 注意: 是*-1后溢出,
    转化为-res * 10 - x < MIN,
    在这里插入图片描述

  3. 因为在计算res = res * 10 + x;的时候, 都是先计算正数的结果, 然后再最后判断正负号, 那么会有一个问题
    如果res = res * 10 + s[k] - '0';是INT_MIN的话, 因为INT_MIN比INT_MAX多1, [-2147483648, 2147483647],
    因此正数存不下来, 所以要特判
    -res * 10 - x == INT_MIN表示溢出的第3种情况

code(long long)

class Solution {
public:
    int myAtoi(string s) {
        int k = 0;
        while (s[k] == ' ') k ++;
        if (k == s.size()) return 0;
        int is_minus = 1;
        
        if (s[k] == '-') is_minus = -1, k ++;
        else if (s[k] == '+') k ++;

        long long res = 0;
        while (k < s.size() && s[k] >= '0' && s[k] <= '9'){ // 只能用while, 用for遍历的话, "words and 987" 不好判断
                res = res * 10 + s[k] - '0';
            if (res > INT_MAX) break; // 此时不能直接返回INT_MAX, 因为还没计算符号问题, 等到外面结算
            k ++;
        }

        if (is_minus) res *= is_minus;
        if (res > INT_MAX) return INT_MAX;
        if (res < INT_MIN) return INT_MIN;
        return res;
    }
};

code(int)

class Solution {
public:
    int myAtoi(string s) {
        int k = 0;
        while (s[k] == ' ') k ++;
        if (k == s.size()) return 0;
        int is_minus = 1;
        
        if (s[k] == '-') is_minus = -1, k ++;
        else if (s[k] == '+') k ++;

        int res = 0;
        while (k < s.size() && s[k] >= '0' && s[k] <= '9'){// 只能用while, 用for遍历的话, "words and 987" 不好判断
            int x = s[k] - '0'; 
            if (is_minus > 0 && res > (INT_MAX - x)/ 10) return INT_MAX; // 第1种情况溢出
            if (is_minus < 0 && -res < (INT_MIN + x)/ 10) return INT_MIN; // 第2种情况溢出
            if (-res * 10 - x == INT_MIN) return INT_MIN; // 第3种情况溢出, 因为负数边界绝对值比正数边界多1
            res = res * 10 + x;
            k ++;
            if (res > INT_MAX) break;
        }

        if (is_minus) res *= is_minus;
        if (res > INT_MAX) return INT_MAX;
        if (res < INT_MIN) return INT_MIN;
        return res;
    }
};

9. 回文数

分析

直接除

code

class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0) return false;
        int p = 0;
        int a = x;
        while (a){
            p = 1ll * p * 10 + a % 10;
            a /= 10;
        }
        return p == x;
    }
};

10. 正则表达式匹配

分析

在这里插入图片描述

code

class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;
        vector<vector<int>> f(n + 1, vector<int>(m + 1));
        f[0][0] = true;
        for (int i = 0; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ ){ 
                // 第2个串需要考虑, 如果s串为空, 那么可能和p串匹配, 因为p可能有*, 但是j = 0开始的话, i = 0, 已经初始化了
                // i > 0, f[i][0] 一定不匹配, 非空的串 可能匹配空串, 所以j = 0开始没有意义
                
                if (j + 1 <= m && p[j + 1] == '*') continue; // 比如a*, 当前位置是a, 那么a不能单独用, 要联合*, 因此当前状态跳过, 到下一个状态*会计算a的情况
                if (i && p[j] != '*') f[i][j] = (s[i] == p[j] || p[j] == '.') && f[i - 1][j - 1];
                else if (p[j] == '*') f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');// 注意这里p[j] = ‘*’, 所以s[i] 得要与 p[j - 1]匹配
            }
        return f[n][m];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vc++全版本组件大全 VC++运行时(Visual C++ Runtime)是VC++开发环境中用于支持C和C++程序运行的基础库集合。这些库包含了执行C/C++程序所必需的基本函数和数据结构,例如内存管理、字符串操作、输入输出处理、异常处理等。VC++运行时库分为静态库和动态库两种形式,以适应不同类型的项目需求。 静态链接库 vs 动态链接库 静态链接库(Static Linking Libraries):在编译时,静态库的代码会被直接嵌入到最终生成的可执行文件中。这意味着每个使用静态库的程序都会包含库代码的一个副本,导致最终程序的体积较大,但不需要外部库文件支持即可独立运行。在VC++中,静态链接库的例子有LIBC.lib(用于单线程程序)和LIBCMT.lib(用于多线程程序)。 动态链接库(Dynamic Link Libraries):与静态链接相反,动态库的代码并不直接加入到应用程序中,而是在程序运行时被加载。这使得多个程序可以共享同一份库代码,节省了系统资源。VC++的动态运行时库主要通过msvcrt.dll(或其变体,如MSVCRTD.dll用于调试版本)实现,与之配套的导入库(Import Library)如CRTDLL.lib用于链接阶段。 运行时库的版本 VC++运行时库随着Visual Studio版本的更新而发展,每个版本都可能引入新的特性和优化,同时保持向后兼容性。例如,有VC++ 2005、2008、2010直至2019等多个版本的运行时库,每个版本都对应着特定的开发环境和Windows操作系统。 重要性 VC++运行时对于确保程序正确运行至关重要。当程序在没有安装相应运行时库的计算机上执行时,可能会遇到因缺失DLL文件(如MSVCP*.dll, VCRUNTIME*.dll等)而导致的错误。因此,开发完成后,通常需要分发相应的VC++ Redistributable Packages给最终用户安装,以确保程序能够在目标系统上顺利运行。 安装与部署 安装VC++运行时库通常是通过Microsoft提供的Redistributable Packages完成的,这是一个简单的过程,用户只需运行安装程序即可自动安装所需组件。对于开发者而言,了解和管理不同版本的运行时库对于确保应用程序的广泛兼容性和可靠性是必要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值