三月刷题笔记(C++)

3-1 字形变换 —[Nm]


6. Z 字形变换 - 力扣(LeetCode) (leetcode-cn.com)

方法一:模拟

由于给定的字符串是按照z形保存,故按照特定顺序进行遍历。

其中 i 代表对应的行,通过 flag 进行变换方向。

当numsRow 为1或0时,其无法按z型保存,故直接返回。

详情见:Z 字形变换(清晰图解) - Z 字形变换 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
public:
    string convert(string s, int numRows) {
        if(numRows < 2) return s;
        vector row(numRows);
        int i=0, flag=-1;
        for(char &ch:s)
        {
            row[i]+=ch;
            if(i==0 || i==numRows-1)
                flag = -flag;
            i += flag;
        }
        string res;
        for(auto &str:row)
        {
            res += str;
        }
        return res;
    }
};
  • 时间复杂度 O(N) :遍历一遍字符串 s
  • 空间复杂度 O(N):各行字符串共占用 O(N) 额外空间

PS:值得注意的是,将第一个强制for循环中的+=替换称为append后(即row[i].append(ch))发生错误 如下:

Line 9: Char 20: error: no matching member function for call to ‘append’
row[i].append(ch);
~~~^~
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1263:7: note: candidate function not viable: no known conversion from ‘char’ to 'const char ’ for 1st argument; take the address of the argument with &
append(const _CharT
__s)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1221:7: note: candidate function not viable: no known conversion from ‘char’ to ‘const std::__cxx11::basic_string’ for 1st argument
append(const basic_string& __str)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1290:7: note: candidate function not viable: no known conversion from ‘char’ to ‘initializer_list’ for 1st argument
append(initializer_list<_CharT> __l)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1320:9: note: candidate template ignored: requirement '_and>, std::_not *>>, std::_not>>>::value’ was not satisfied [with _Tp = char]
append(const _Tp& __svt)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1250:7: note: candidate function not viable: requires 2 arguments, but 1 was provided
append(const _CharT __s, size_type __n)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1280:7: note: candidate function not viable: requires 2 arguments, but 1 was provided
append(size_type __n, _CharT __c)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1309:9: note: candidate function template not viable: requires 2 arguments, but 1 was provided
append(_InputIterator __first, _InputIterator __last)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1238:7: note: candidate function not viable: requires at least 2 arguments, but 1 was provided
append(const basic_string& __str, size_type __pos, size_type __n = npos)
^
/usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/basic_string.h:1335:2: note: candidate function template not viable: requires at least 2 arguments, but 1 was provided
append(const _Tp& __svt, size_type __pos, size_type __n = npos)
^
1 error generated.
~~~~~~~

产生错误原因为:append似乎只能够添加字符串类型到对应str末尾

当string str; str+=ch; 再进行append,输出答案错误。

该问题的解决方案为:使用push_back(ch);

3-2 寻找最近的回文数 ----[Nm]


564. 寻找最近的回文数 - 力扣(LeetCode) (leetcode-cn.com)

方法一:模拟

构造回文整数有一个直观的方法:用原数的较高位的数字替换其对应的较低位。例如,对于数 12345,我们可以用 1 替换 5,用 2 替换 4,这样原数即变为回文整数 12321。

在这种方案中,我们修改的都是较低位的数字,因此构造出的新的整数与原数较为接近。大部分情况下,这种方案是最优解,但还有部分情况我们没有考虑到。

构造的回文整数大于原数时,我们可以减小该回文整数的中间部分来缩小回文整数和原数的差距。例如,对于数 9932199321,我们将构造出回文整数 99399,实际上 99299 更接近原数。

当我们减小构造的回文整数时,可能导致回文整数的位数变化。例如,对于数 100,我们将构造出回文整数 101,减小其中间部分将导致位数减少。得到的回文整数形如 999…999(10^len-1)

构造的回文整数小于原数时,我们可以增大该回文整数的中间部分来缩小回文整数和原数的差距。例如,对于数 1239912399,我们将构造出回文整数 12321,实际上 12421 更接近原数。

当我们增大构造的回文整数时,可能导致回文整数的位数变化。例如,对于数 998,我们将构造出回文整数 999,增大其中间部分将导致位数增加。得到的回文整数形如 100…001(10^len+1)。

构造的回文整数等于原数时,由于题目约定,我们需要排除该回文整数,在其他的可能情况中寻找最近的回文整数。

考虑到以上所有的可能,我们可以给出最终的方案:分别计算出以下每一种可能的方案对应的回文整数,在其中找到与原数最近且不等于原数的数即为答案。

用原数的前半部分替换后半部分得到的回文整数。

用原数的前半部分加一后的结果替换后半部分得到的回文整数。

用原数的前半部分减一后的结果替换后半部分得到的回文整数。

为防止位数变化导致构造的回文整数错误,因此直接构造 999…999 和 100…001 作为备选答案。

class Solution {
public:
    vector getCandidates(const string& n) {
        int len = n.length();
        //提前放入两个特殊元素
        vector candidates = {
            (long)pow(10, len - 1) - 1,
            (long)pow(10, len) + 1,
        };
        //奇数长度特殊处理
        long selfPrefix = stol(n.substr(0, (len + 1) / 2));
        for (int i : {selfPrefix - 1, selfPrefix, selfPrefix + 1}) {
            string prefix = to_string(i);
            //若为奇数则跳过倒数第一个字符
            string candidate = prefix + string(prefix.rbegin() + (len & 1), prefix.rend());
            candidates.push_back(stol(candidate));
        }
        return candidates;
    }

    string nearestPalindromic(string n) {
        long selfNumber = stol(n), ans = -1;
        //ans初始化为-1作为进入条件
        const vector& candidates = getCandidates(n);
        for (auto& candidate : candidates) {
            if (candidate != selfNumber) {
                if (ans == -1 ||
                    abs(candidate - selfNumber) < abs(ans - selfNumber) ||
                    abs(candidate - selfNumber) == abs(ans - selfNumber) && candidate < ans) {
                    ans = candidate;
                }
            }
        }
        return to_string(ans);
    }
};

3-3 各位相加 --[Nm]


258. 各位相加 - 力扣(LeetCode) (leetcode-cn.com)

方法一:按位模拟

class Solution {
public:
    int addDigits(int num) {
        while(num > 9 )
        {
            int tmp = 0;
            while(num != 0)
            {
                tmp += num%10;
                num /= 10;
            }
            num = tmp;
        }
        return num;
    }
};

时间复杂度:O(lognum),其中 num 是给定的整数。对于 num 计算一次各位相加需要 OO(lognum) 的时间,由于 num≤2 ^31 −1,因此对于 num 计算一次各位相加的最大可能结果是 82,对于任意两位数最多只需要计算两次各位相加的结果即可得到一位数。

空间复杂度:O(1)。

请寻找以下代码中存在的错误:

class Solution {
public:
    int addDigits(int num) {
        int res = 0;
        while(num > 9 )
        {
            res += num%10;
            num /=10;
            if(num == 0)
            {
                num = res;
                res = 0;
            }
        }
        return num;
    }
};

当运行测试用例38时,返回3.

正确答案为2.

问题出现在num/=10上,当num被除到只剩个位时,循环结束,num只进行一次/10,故剩8;

方法二:数学

运用同余式相加性质,详情见:各位相加 - 各位相加 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
public:
    int addDigits(int num) {
        return (num - 1) % 9 + 1;
    }
};

复杂度双O(1)

3-4 子数组范围和 --[Nm]


2104. 子数组范围和 - 力扣(LeetCode) (leetcode-cn.com)

方法一:遍历

从第一个元素开始遍历到结尾:i

从当前元素i开始遍历的结尾:j

记录差值

class Solution {
public:
    long long subArrayRanges(vector& nums) {
        int n=nums.size();
        long long ret =0;
        for(int i=0; i

时间O(N^2),空间O(1)

方法二:单调栈

使用该方法为困难难度,要求O(N)时间

class Solution {
public:
    long long subArrayRanges(vector& nums) {
        int n = nums.size();
        vector minLeft(n), minRight(n), maxLeft(n), maxRight(n);
        stack minStack, maxStack;
        for (int i = 0; i < n; i++) {
            while (!minStack.empty() && nums[minStack.top()] > nums[i]) {
                minStack.pop();
            }
            minLeft[i] = minStack.empty() ? -1 : minStack.top();
            minStack.push(i);
            
            // 如果 nums[maxStack.top()] == nums[i], 那么根据定义,
            // nums[maxStack.top()] 逻辑上小于 nums[i],因为 maxStack.top() < i
            while (!maxStack.empty() && nums[maxStack.top()] <= nums[i]) { 
                maxStack.pop();
            }
            maxLeft[i] = maxStack.empty() ? -1 : maxStack.top();
            maxStack.push(i);
        }
        minStack = stack();
        maxStack = stack();
        for (int i = n - 1; i >= 0; i--) {
            // 如果 nums[minStack.top()] == nums[i], 那么根据定义,
            // nums[minStack.top()] 逻辑上大于 nums[i],因为 minStack.top() > i
            while (!minStack.empty() && nums[minStack.top()] >= nums[i]) { 
                minStack.pop();
            }
            minRight[i] = minStack.empty() ? n : minStack.top();
            minStack.push(i);

            while (!maxStack.empty() && nums[maxStack.top()] < nums[i]) {
                maxStack.pop();
            }
            maxRight[i] = maxStack.empty() ? n : maxStack.top();
            maxStack.push(i);
        }

        long long sumMax = 0, sumMin = 0;
        for (int i = 0; i < n; i++) {
            sumMax += static_cast(maxRight[i] - i) * (i - maxLeft[i]) * nums[i];
            sumMin += static_cast(minRight[i] - i) * (i - minLeft[i]) * nums[i];
        }
        return sumMax - sumMin;
    }
};

时间复杂度:O(n),其中 nn 为数组的大小。使用单调栈预处理出四个数组需要 O(n),计算最大值之和与最小值之和需要 O(n)。

空间复杂度:O(n)。保存四个数组需要 O(n);单调栈最多保存 n 个元素,需要 O(n)。

3-5 最长特殊序列 Ⅰ --[Nm]


521. 最长特殊序列 Ⅰ - 力扣(LeetCode) (leetcode-cn.com)

解决该题时不要想复杂(子序列、后缀数组),根据题目可知只要两字符串不相等便存在最大长度特殊序列,即两字符串中较长的一个。

因为字符串最长子串即是字符串本身,故直接返回最大字符串长度即可。

class Solution {
public:
    int findLUSlength(string a, string b) {
        return a!=b? max(a.length(), b.length()): -1;
    }
};

时间复杂度:O(n),其中 nn 是字符串 a 的长度。当两字符串长度不同时,时间复杂度为 O(1);当字符串长度相同时,时间复杂度为 O(n)。因此时间复杂度为 O(n)。

空间复杂度:O(1)。

3-6 适合打劫银行的日子 --[Nm-Ex]


[2100. 2100. 适合打劫银行的日子 - 力扣(LeetCode) (leetcode-cn.com) - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/find-good-days-to-rob-the-bank/)

题目要求前time非递增,后time非递减

故:有效元素下标为:time ~ size()-time-1

本题很容易联想到动态规划,从dp[time]开始到dp[size()-time-1]

首先初始化dp[time],之后的状态根据前状态得到

能想到一个分支:dp[i-1]=true,那么dp[i]要怎样得到true或false

这里便无从下手,让我们看看官方题解是如何解答的

方法一:动态规划

计算第i天前警卫连续非递增天数为left,第i天后警卫连续非递减为right

只要满足条件:left >= time && right >= time,便代表第i天适合打劫

那么如何计算left和right呢?

使用动态规划方法求得

如:left[i]代表第i天前递减天数

则分以下两种情况讨论:

security[i] <= security[i-1]:left[i] = left[i-1]+1

**else:**left[i] = 0;

分析计算right[i]同理可得

最后依次检测全部日期,得到答案

class Solution {
public:
    vector goodDaysToRobBank(vector& security, int time) {
        int n =security.size();
        vector left(n),right(n);
        for(int i=1; i ans;
        //根据条件统计结果
        for(int i=time; i= time && right[i] >= time)
                ans.emplace_back(i);
        }
        return ans;
    }
};

时间复杂度:O(n),其中 n 为数组 security 的长度。需要遍历数组求出第 i 天前连续非递增的天数与第 i 天后连续非递减的天数,然后再遍历数组检测第 i 天是否适合打劫。

空间复杂度:O(n),其中 n 为数组 security 的长度。需要 O(n) 的空间来存储第 i 天前连续非递增的天数与第 i 天后连续非递减的天数。

方法二:前缀和数组

设置一个预处理数组,g[i] 代表当前时间 security[i] 与前一时间 security[i−1] 的大小关系

security[i] > security[i - 1] 时有 g[i] = 1

security[i] < security[i - 1] 时有 g[i] = -1

否则 g[i] = 0,另外我们特别的有 g[0] = 0 的边界情况

然后我们对g应用「前缀和」思想:使用a数组记录前缀1的数量,使用b数组记录前缀-1的数量
最终,如果i为「适合打劫银行的日子」,那么满足time <=i<=n - time,且满足**(i一 time,i]范围前缀1数量为0**,(i,i+time]范围前缀―1数量为0

class Solution {
public:
    vector goodDaysToRobBank(vector& security, int time) {
        int n = security.size();
        vector g(n);
        for(int i=1; isecurity[i-1]? 1:-1;
        }
        //统计条件
        vector a(n+1),b(n+1);	//由于需要计算边界后个数,故设为N+1
        for(int i=1; i<=n; i++)
        {
            a[i] = a[i-1] + (g[i-1] == 1? 1:0);
            b[i] = b[i-1] + (g[i-1] == -1? 1:0);
        }
        //处理条件
        vector ans;
        for(int i=time; i

复杂度双O(N)

3-7 七进制数


504. 七进制数 - 力扣(LeetCode) (leetcode-cn.com)

简单题

class Solution {
public:
    string convertToBase7(int num) {
        if (num==0) return "0";
        string res;
        bool digits = num<0? 1:0;
        num = abs(num);
        while(num)
        {
            int tmp = num % 7;
            num = num / 7;
            res+=to_string(tmp);
        }
        if(digits) 
            res.push_back('-');
        reverse(res.begin(), res.end());
        return res;
    }
};

3-8 蜡烛之间的盘子 -[Of]


2055. 蜡烛之间的盘子 - 力扣(LeetCode) (leetcode-cn.com)

方法一:预处理+前缀和

1、创建presum数组用于记录当前位置包括前面盘子的总数量

2、使用双指针来遍历找到蜡烛,由presum[j] - presum[i];计算得到符合条件范围内的盘子

**为什么不使用 j-i-1?**这是因为存在i~j范围内存在蜡烛的情况

3、进一步优化,若能够由给定起始坐标begin结束索引end直接得到对应的i、j能够进一步降低时间复杂度。

那么如何优化?

创建数组left与right:

left:用于记录最近左蜡烛索引

right:用于记录最近右蜡烛索引

right[begin]~left[end]即为当前子数组以两蜡烛作为边界的答案范围

使用presum[right[begin]] - presum[left[end] 计算有效盘子个数即可。

如下为该方法代码:

class Solution {
public:
    vector platesBetweenCandles(string s, vector>& queries) {
        int n = s.length();
        vector preSum(n);
        for (int i = 0, sum = 0; i < n; i++) {
            if (s[i] == '*') {
                sum++;
            }
            preSum[i] = sum;
        }
        vector left(n);
        for (int i = 0, l = -1; i < n; i++) {
            if (s[i] == '|') {
                l = i;
            }
            left[i] = l;
        }
        vector right(n);
        for (int i = n - 1, r = -1; i >= 0; i--) {
            if (s[i] == '|') {
                r = i;
            }
            right[i] = r;
        }
        vector ans;
        for (auto& query : queries) {
            int x = right[query[0]], y = left[query[1]];
            ans.push_back(x == -1 || y == -1 || x >= y ? 0 : preSum[y] - preSum[x]);
        }
        return ans;
    }
};

时间复杂度:O(n+q),其中 n 为数组长度,q 为询问数量。我们需要 O(n) 的时间预处理。对于每一个询问,我们需要 O(1) 的时间计算答案。

空间复杂度:O(n),其中 n 为数组长度。我们需要 O(n) 的空间保存预处理的结果。注意返回值不计入空间复杂度。

来源:蜡烛之间的盘子 - 蜡烛之间的盘子 - 力扣(LeetCode) (leetcode-cn.com)

3-9 得分最高的最小轮调(hard)


798. 得分最高的最小轮调 - 力扣(LeetCode) (leetcode-cn.com)

求最优K即数组轮调下标,对应位置的子数组移前,计算元素值小于等于索引元素个数。

若k使得该结果最大,则其作为答案返回。

方法一:差分数组

由于只有当元素索引>=元素值时,分数+1;

故分析得到:

对于数组元素nums[i] :

若该元素导致得分+1:其下标所在范围为**[nums[i] , n-1]** 共n-x个下标位置

否则其下标所在范围为:[0, nums[i] -1] 共x个下标位置

计算轮调后下标:index = ( i + n-k) % n //其中n-k可看作前移元素个数(若i-k不为负数,则移动后下标为 i-k)

以下推导假设 i-k 为合法下标:

首先确定下标的范围:[ 0, n-1],故有:0 <= i-k <= n-1

得到k对应的上下界: k<=i 与 k >= i - (n-1)

接着对 nums[i] <= i-k 进行变形得到:k <= i-nums[i]

对两个k的下界进行选择,由于 nums[i] >=0,故i-nums[i] <= i

由于需要满足条件,故考虑区间更小的 i-nums[i] 作为区间下界

综上所述,最后推到出k的范围:[ i - ( n - 1), i - nums[i]]

接下来考虑如何进行扩展,即+n、%n来将值转为正数:

当i-(n-1) <= i-nums[i] 时,合法无需操作。

当i-(n-1) >= i-numsp[i]时,负数下标等价为(i-k+n)%n,此时k的范围变为:[0, i-nums[i]] 与 [i - (n - 1), n - 1] 两段

故而分析出元素组每个nums[i] 能够分的k的取值范围,假定取值范围为 [l,r],我们可以对[l,r]进行+i标记,代表范围为k嫩巩固得1分,当处理完全部元素后,找到标记次数最多的k即为答案。

标记操作使用差实现,标记最多次数最多的位置可对差分数组求前缀和再进行遍历即可。

差分方法参考:【区间求和问题】差分入门模板题 (qq.com)

方法参考:【宫水三叶】上下界分析 + 差分应用 - 得分最高的最小轮调 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
public:
    int bestRotation(vector& nums) {
        int n = nums.size();
        vector diffs(n);
        for (int i = 0; i < n; i++) {
            int low = (i + 1) % n;
            int high = (i - nums[i] + n + 1) % n;
            diffs[low]++;
            diffs[high]--;
            if (low >= high) {
                diffs[0]++;
            }
        }
        int bestIndex = 0;
        int maxScore = 0;
        int score = 0;
        for (int i = 0; i < n; i++) {
            score += diffs[i];
            if (score > maxScore) {
                bestIndex = i;
                maxScore = score;
            }
        }
        return bestIndex;
    }
};

时间复杂度:O(n),其中 n 是数组 nums 的长度。需要遍历数组 nums 两次。

空间复杂度:O(n),其中 n 是数组 nums 的长度。需要创建长度为 n 的数组 diffs。

3-10 N 叉树的前序遍历 --[Ex]


589. N 叉树的前序遍历 题解 - 力扣(LeetCode) (leetcode-cn.com)

方法一:递归

因为是树的前序遍历,故很容易就能使用递归解决问题。

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    //递归
    vector res;
    vector preorder(Node* root) {
        recur(root);
        return res;
    }
    void recur(Node* root)
    {
        if(root == nullptr)
            return;
        res.push_back(root->val);
        for(auto & N:root->children)
        {
            recur(N);
        }
        return;
    }
};

时间复杂度:O(m),其中 m 为 N 叉树的节点。每个节点恰好被遍历一次。

空间复杂度:O(m),递归过程中需要调用栈的开销,平均情况下为 O(logm),最坏情况下树的深度为 m−1,此时需要的空间复杂度为 O(m)。

方法二:迭代

递归使用栈空间,故迭代使用栈模拟即可。

当处理二叉树的先序遍历时,只需要放入当前弹出节点的右节点再放入左节点即可。

但该题目为多叉树,只需将孩子节点自右向左遍历入栈即可。

class Solution {
public:
    vector preorder(Node* root) {
        vector res;
        if(root == nullptr)
            return res;
        
        stack st;
        st.emplace(root);
        while(!st.empty())
        {
            Node* node = st.top();
            st.pop();
            res.emplace_back(node->val);
            for(auto it = node->children.rbegin(); it != node->children.rend(); it++)
            {
                st.emplace(*it);
            }
        }
        return res;
    }
};

这里注意子节点的遍历方式,其中it不能为引用,否则出现以下错误。

Line 35: Char 23: error: non-const lvalue reference to type ‘reverse_iterator<…>’ cannot bind to a temporary of type 'reverse_iterator<…>'
for(auto &it = node->children.rbegin(); it != node->children.rend(); it++)
**^ ~~~~~~~~~~~~~~~~~~~~~~~**
1 error generated.

第 35 行:Char 23:错误:对类型"reverse_iterator<…>"的非常量左值引用无法绑定到类型为"reverse_iterator<…>"的临时类型。

**时间复杂度:O(m):**其中 m 为 N 叉树的节点。每个节点恰好被访问一次。

**空间复杂度:O(m):**其中 m 为 N 叉树的节点。如果 N 叉树的深度为 1 则此时栈的空间为 O(m−1),如果 N 叉树的深度为 m−1 则此时栈的空间为 O(1),平均情况下栈的空间为 O(logm),因此空间复杂度为 O(m)。

3-11 统计最高分的节点数目


2049. 统计最高分的节点数目 - 力扣(LeetCode) (leetcode-cn.com)

方法一:创建无向图+DFS

通过删除某一个节点,最多分为三个子树:左孩子为根节点、右孩子为根节点、被删除节点父节点所在子树

创建图为 父亲节点:孩子节点

对该图进行dfs搜索,最后得到每个节点对应的score,记录最大score为maxscore,记录分数为maxscore的数量为cnt即为返回结果。

class Solution {
public:
    long maxScore = 0;  //记录最大分数,用于cnt++
    int cnt = 0;    //返回值,即最高得分节点数目
    int n;  //节点总数,用于得到size用于计算三种情况下节点的个数
    vector> children;   //构图,用于dfs

    int dfs(int node) {
        long score = 1;
        int size = n - 1;   //当前节点被删除
        for (int c : children[node]) {
            int t = dfs(c); //得到以当前节点孩子节点为根节点的树的节点个数
            score *= t; //乘左右孩子数量
            size -= t;  //保存剩余节点个数
        }
        //判断是否为最初根节点,否则导致size=0
        if (node != 0) {
            score *= size;  //乘被删节点父节点所在树节点的节点个数
        }
        
        //代表出现了与最高分相同的节点,返回答案计数+1
        if (score == maxScore) {
            cnt++;
        } else if (score > maxScore) {
            maxScore = score;
            cnt = 1;
        }
        return n - size;    //返回以当前节点node为根节点组成的树的节点总数
    }

    int countHighestScoreNodes(vector& parents) {
        this->n = parents.size();
        //[父亲]:{左右孩子}
        this->children = vector>(n);
        for (int i = 0; i < n; i++) {
            int p = parents[i];
            if (p != -1) {
                children[p].emplace_back(i);
            }
        }
        //从根节点开始dfs搜索
        dfs(0);
        //返回有最高得分节点的数目
        return cnt;
    }
};

时间复杂度:O(n),其中 n 是树的节点数。预处理,深度优先搜索均消耗 O(n) 时间。

空间复杂度:O(n)。统计每个节点的子节点消耗 O(n) 空间,深度优先搜索的深度最多为 O(n) 空间。

3-12 N 叉树的后序遍历 --[De]


590. N 叉树的后序遍历 - 力扣(LeetCode) (leetcode-cn.com)

方法一:递归

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector _children) {
        val = _val;
        children = _children;
    }
};
*/

//BackTracking
class Solution {
public:
    vector postorder(Node* root) {
        vector res;
        helper(root, res);
        return res;
    }
    void helper(const Node* root, vector & res)
    {
        if(root == nullptr)
        {
            return;
        }
        for(auto &ch:root->children)
        {
            helper(ch, res);
        }
        res.emplace_back(root->val);
    }
};

时间复杂度:O(m),其中 m 为 N 叉树的节点。每个节点恰好被遍历一次。

空间复杂度:O(m),递归过程中需要调用栈的开销,平均情况下为 O(logm),最坏情况下树的深度为 m−1,需要的空间为 O(m−1),因此空间复杂度为 O(m)。

简洁版:

class Solution {
public:
    vector ans;
    vector postorder(Node* root) {
        if(root == nullptr)
            return ans;
        for(const auto &cur:root->children)
            postorder(cur);
        ans.emplace_back(root->val);
        return ans;
    }
};

方法二:迭代

使用逆序从右到左的方式将孩子节点压入到栈中,这样变实现了从左到右的输出。

class Solution {
public:
    vector postorder(Node* root) {
        vector ans;
        if(!root)   //判空返回空数组
            return ans;
        
        stack stk;
        stk.emplace(root);
        
        //用于保存上一个遍历输出的节点
        Node* prev = nullptr;

        while(!stk.empty())
        {
            Node* node = stk.top();
            //当没有孩子或孩子已经遍历完,将当前节点放入数组
            if(node->children.empty() || node->children.back() == prev)
            {
                ans.push_back(node->val);
                stk.pop();
                prev = node;
            }
            else
            {
                //将节点的孩子逆序压入栈中
                for(auto it = node->children.rbegin(); it != node->children.rend(); ++it)
                {
                    stk.emplace(*it);
                }
            }
        }
        return ans;
    }
};

迭代错误版本:

class Solution {
public:
    vector postorder(Node* root) {
       vector res;
       stack stk;
       stk.push(root);
       while(!stk.empty())
       {
           Node* node = stk.top();  //remove node from stk
           if(!(node->children.empty())) //judge children isempty
           {
               for(int i = node->children.size()-1; i>=0; --i)
               {
                   stk.emplace(node->children[i]);
               }
           }
           else
           {
               stk.pop();   //pop node from stack
               res.emplace_back(node->val);
           }
       } 
       return res;
    }
};

首先该代码中没有进行判空,其次出现的问题为时间超时。

原因分析:

当一个节点的孩子全部被输出弹出栈后,回到当前节点,此时正确操作为输出当前节点。

但是该判断条件为判断其是否还有孩子?

故接下来会继续将其孩子逆序压入栈中,导致陷入死循环。

想要解决问题应添加一个对应判断孩子是否输出的附加条件。(如:保存上一个节点信息或创建一个哈希表) 如下:

且更好的遍历方式为:for(auto it = node->rbegin(); it != node->children.rend(); ++it);

class Solution {
public:
    vector postorder(Node* root) {
        vector res;
        if (root == nullptr) {
            return res;
        }

        stack st;
        unordered_set visited;
        st.emplace(root);
        while (!st.empty()) {
            Node * node = st.top();
            /* 如果当前节点为叶子节点或者当前节点的子节点已经遍历过 */
            if (node->children.size() == 0 || visited.count(node)) {
                res.emplace_back(node->val);
                st.pop();
                continue;
            }
            for (auto it = node->children.rbegin(); it != node->children.rend(); it++) {
                st.emplace(*it);
            }
            visited.emplace(node);
        }       
        return res;
    }
};

若不使用逆序压入栈,则需要使用哈希表对输出进行记录:

class Solution {
public:
    vector postorder(Node* root) {
        vector res;
        if (root == nullptr) {
            return res;
        }
        
        unordered_map cnt;
        stack st;
        Node * node = root;
        while (!st.empty() || node != nullptr) {
            while (node != nullptr) {
                st.emplace(node);
                if (node->children.size() > 0) {
                    cnt[node] = 0;
                    node = node->children[0];
                } else {
                    node = nullptr;
                }         
            }
            node = st.top();
            int index = cnt.count(node) ? (cnt[node] + 1) : 0;
            if (index < node->children.size()) {
                cnt[node] = index;
                node = node->children[index];
            } else {
                res.emplace_back(node->val);
                st.pop();
                cnt.erase(node);
                node = nullptr;
            }
        }
        return res;
    }
};

时间复杂度:O(m),其中 m 为 N 叉树的节点。每个节点恰好被访问一次。

空间复杂度:O(m),其中 m 为 N 叉树的节点。如果 NN 叉树的深度为 1 则此时栈和哈希表的空间为 O(1),如果 N 叉树的深度为 m-1 则此时栈和哈希表的空间为 O(m−1),平均情况下栈和哈希表的空间为 O(logm),因此空间复杂度为O(m)。

3-13 UTF-8 编码验证 --[Nm]


393. UTF-8 编码验证 - 力扣(LeetCode) (leetcode-cn.com)

想要解决该题首先需要明白题目表达的含义:

给定一个数组:如 [ 235, 140, 4]

判断数组中的元素组合是否为UTF-8编码格式。

首先,该题给予一个限制:

输入是整数数组。只有每个整数的 最低 8 个有效位 用来存储数据。这意味着每个整数只表示 1 字节的数据

即数组中每个元素的范围:0~255

接下来根据上面的数组进行分析:

1、先转换成二进制:

11101011 10001100 00000100

2、其次需要注意的是如何使用UTF-8的判断条件

(1)1个字节,第一位为0

(2)n个字节(n>1),前n位为1,第n+1位是0。后面字节前两位皆为10,即一定有n-1个序列前两位为10才符合规则

3、分析数据

首先通过第一个8位二进制来获取条件信息:

111:代表有3字节,且高位第3个字符为0 符合条件(1)

第二个字节以10开头 符合条件(2)

第三个字节以00开头 不符合条件 (2)

返回false

结束

故而,我们能够知道如何对给定的数据进行循环分析,以下为解决该题的方法:

方法一:模拟

  • 从data[i] 第7位开始遍历,计算连续1的个数,即字节数。记作cnt

    若cnt 为1 或 大于 4,则违反编码规则,返回false

  • 此时,我们需要明白,此时的data[i]与其后的cnt-1个元素组合成了一个UTF-8编码,即它们变为一个整体。

    我们需要查找data中元素范围 [i+1, i+cnt-1]范围内的全部元素是否以10开头,即第7位与第6位是否分别位1和0;

    同时,若不存在第i-cnt-1个元素,也返回false。

    若出现不符合条件的情况,返回false

  • 当检查完且符合条件后,接着去判断下一个UTF-8编码进行检查。

    即i+=cnt,将检查点转移到下一个8位二进制。

  • 当全部检查都符合条件后,返回true;

class Solution {
public:
    bool validUtf8(vector& data) {
        int n = data.size();
       
       
        for(int i = 0; i < n; )
        {
            int t = data[i], j = 7;

            while(j >= 0 && (((t >> j) & 1) == 1))
                j--;

            //得到UTF-8编码位数
            int cnt = 7 - j;   

            //判断条件(1)
            if(cnt == 1 || cnt > 4) 
                return false;
            //不存在对应元素
            if(i + cnt - 1 >= n)    
                return false;
            //判断i后的cnt-1个元素是否以10开头
            for(int k = i+1; k < i + cnt; k++)
            {
                if(( ( (data[k] >> 7) & 1) == 1) && ((data[k] >> 6) & 1) ==0)
                    continue;
                return false;
            }
            
            //分为两种情况讨论
            if(cnt == 0)
                i++;
            else i+=cnt;
        }



        return true;
    }
};



时间O(N),空间O(1);

3-14 两个列表的最小索引总和


599. 两个列表的最小索引总和 - 力扣(LeetCode) (leetcode-cn.com)

查重使用哈希表,会想到key存string,value存sum,最后遍历输出最小string

如何简化使用set,改变遍历插表方式,尝试同步走策略

list1 放入0,list2放入0 ,sum=0 此时最小

list1放入0、1,list2放入0,sum=1

list1:0、1,list2:0、1,sum=1或2

若只返回第一个最小和可解,但无法判断当前string组合与上一个string最小和相同,因为该哈希表并没有存int故无法判断。

方法一:哈希表

该方法类似首先想到的,其先遍历list1放入map,然后遍历list2.

若sum比之前的最小sum小则清空数组,将新string放入。

若sum等于之前的最小sum,则直接放入到数组中。

class Solution {
public:
    vector findRestaurant(vector& list1, vector& list2) {
       unordered_map index;
       for(int i=0; i res;
       int indexSum = INT_MAX;
       for(int i=0; i

3-15 统计按位或能得到最大值的子集数目


2044. 统计按位或能得到最大值的子集数目 - 力扣(LeetCode) (leetcode-cn.com)

方法一:dfs回溯

事实上,我们可以在「枚举子集」的同时「计算相应得分」,设计void dfs ( int pos, int orVal) 的DFS函数来实现「爆搜」,其中pos为当前的搜索到nums的第几位,orVal为当前的得分情况。 对于任意一位α而言,都有「选」和「不选」两种选择,分别对应了dfs(pos + 1, orVal | nums[pos])和dfs(pos + 1, orVal)两条搜索路径,在搜索所有状态过程中,使用全局变量maxOr和cnt来记录「最大得分」以及「取得最大得分的状态数量」。 该做法将多条「具有相同前缀」的搜索路径的公共计算部分进行了复用,从而将算法复杂度下降为O(2")。

class Solution {
private:
    vector<int> nums;
    int maxOr, cnt;
public:
    int countMaxOrSubsets(vector<int>& nums) {
        this->nums = nums;
        this->maxOr = 0;
        this->cnt = 0;
        dfs(0, 0);
        return cnt;
    }
    void dfs(int pos, int orVal)
    {
        if(pos == nums.size())
        {
            if(orVal > maxOr)
            {
                maxOr = orVal;
                cnt = 1;
            }
            else if(orVal == maxOr)
            {
                cnt++;
            }
            return;
        }
        dfs(pos+1, orVal|nums[pos]);
        dfs(pos+1, orVal);
    }
};

类似的另一种写法:

class Solution {
public:
    int countMaxOrSubsets(vector<int>& nums) {
        int n = nums.size();
        int ans = 0;
        int maxVal = 0;
        for(auto& num : nums) {
            maxVal |= num;
        }
        function<void(int, int)> dfs = [&](int k, int sum) {
            if(k == n) {
                if(sum == maxVal) {
                    ++ans;
                }
                return;
            }
            dfs(k + 1, sum | nums[k]);
            dfs(k + 1, sum);
        };
        dfs(0, 0);
        return ans;
    }
};

请检查以下代码中出现的错误

class Solution {
public:
    int maxSum=INT_MIN;
    int res = 0;
    int countMaxOrSubsets(vector<int>& nums) {
        recur(nums, 0,0);
        return res;
    }
    void recur(vector<int>& nums, int preSum,int i)
    {
        if(i >= nums.size())
            return;

        int nowSum = preSum|nums[i];
        if(nowSum > maxSum)
        {
            maxSum = nowSum;
            res = 1;
        }
        else if(nowSum == maxSum)
        {
            res++;
        }

        for(int n = i+1; i<nums.size(); ++i)
        {
            recur(nums, nowSum, n);
        }
    }
};

错误信息如下:

方法二:位运算

class Solution {
public:
    int countMaxOrSubsets(vector& nums) {
        int n = nums.size(), maxVal = 0, cnt = 0;
        int stateNumber = 1<> j) & 1 == 1)
                {
                    cur |= nums[j];
                }
            }
            if(cur == maxVal)
            {
                cnt++;
            }
            else if(cur > maxVal)
            {
                maxVal = cur;
                cnt = 1;
            }
        }
        return cnt;
    }
};

3-16 全 O(1) 的数据结构(Nm)


432. 全 O(1) 的数据结构 - 力扣(LeetCode) (leetcode-cn.com)

首先要相当使用两个哈希表,一个哈希表key为字符串value为值个数,一个哈希表key为值个数value为字符串。

使用这样相互映射的方式,来完成对应的getMax与getMin功能。

但是在查找对应值的时候,需要花费O(N)的时间,不符合全O(1)的条件。

使用哈希表+双向链表的方法能够将对应的时间复杂度降为O(1),该解题方法类似LRU。

方法一:哈希表+双向链表

class AllOne {
    list, int>> lst;
    unordered_map, int>>::iterator> nodes;

public:
    AllOne() {}

    void inc(string key) {
        if (nodes.count(key)) {
            auto cur = nodes[key], nxt = next(cur);
            if (nxt == lst.end() || nxt->second > cur->second + 1) {
                unordered_set s({key});
                nodes[key] = lst.emplace(nxt, s, cur->second + 1);
            } else {
                nxt->first.emplace(key);
                nodes[key] = nxt;
            }
            cur->first.erase(key);
            if (cur->first.empty()) {
                lst.erase(cur);
            }
        } else { // key 不在链表中
            if (lst.empty() || lst.begin()->second > 1) {
                unordered_set s({key});
                lst.emplace_front(s, 1);
            } else {
                lst.begin()->first.emplace(key);
            }
            nodes[key] = lst.begin();
        }
    }

    void dec(string key) {
        auto cur = nodes[key];
        if (cur->second == 1) { // key 仅出现一次,将其移出 nodes
            nodes.erase(key);
        } else {
            auto pre = prev(cur);
            if (cur == lst.begin() || pre->second < cur->second - 1) {
                unordered_set s({key});
                nodes[key] = lst.emplace(cur, s, cur->second - 1);
            } else {
                pre->first.emplace(key);
                nodes[key] = pre;
            }
        }
        cur->first.erase(key);
        if (cur->first.empty()) {
            lst.erase(cur);
        }
    }

    string getMaxKey() {
        return lst.empty() ? "" : *lst.rbegin()->first.begin();
    }

    string getMinKey() {
        return lst.empty() ? "" : *lst.begin()->first.begin();
    }
};
struct Node {
    string s;
    int v;
    Node *pre, *nxt;
    Node(string s):s(s), v(0), pre(nullptr), nxt(nullptr) {};
};

class AllOne {
    vector> mp1;
    map mp2;
    Node *head, *tail;
public:
    AllOne():mp1(50002) {
        head = mp1[0].second = new Node("");
        tail = mp1.back().first = new Node("");
        head->nxt = tail;
        tail->pre = head;
    }
    // 将 it 节点插入 tar 节点后面
    void insert(Node *it, Node *tar) {
        it->nxt = tar->nxt;
        it->pre = tar;
        it->nxt->pre = it->pre->nxt = it;
        auto &[a, b] = mp1[it->v];
        if (!a && !b) {
            a = b = it;
        } else {
            a = it;
        }
    }
    // 将 it 节点从双向链表中删除
    void erase(Node *it) {
        it->pre->nxt = it->nxt;
        it->nxt->pre = it->pre;
        auto &[a, b] = mp1[it->v];
        if (a == it && b == it) {
            a = b = nullptr;
        } else if (a == it) {
            a = it->nxt;
        } else if (b == it) {
            b = it->pre;
        }
    }
    void inc(string key) {
        Node *cur = mp2.count(key) ? mp2[key] : new Node(key);
        if (cur->v) {
            erase(cur);
            cur->v++;
            if (mp1[cur->v - 1].second) {
                insert(cur, mp1[cur->v - 1].second);
            } else {
                insert(cur, cur->pre);
            }
        } else {
            mp2[key] = cur;
            cur->v++;
            insert(cur, head);
        }
    }
    
    void dec(string key) {
        Node *cur = mp2[key];
        erase(cur);
        if (cur->v == 1) {
            mp2.extract(key);
            delete cur;
        } else {
            cur->v--;
            if (mp1[cur->v].first) {
                insert(cur, mp1[cur->v].first->pre);
            } else {
                insert(cur, cur->pre);
            }
        }
    }
    
    string getMaxKey() {
        return tail->pre->s;
    }
    
    string getMinKey() {
        return head->nxt->s;
    }
};

3-17 词典中最长的单词 --[Nm]


词典中最长的单词 - 词典中最长的单词 - 力扣(LeetCode) (leetcode-cn.com)

方法一:哈希集合

首先将words数组按照字符串长度进行升序排序:

sort(words.begin(), words.end(), [](stirng & a, string & b){
   if(a.size() != b.size()){
		return a.size() < b.size();
   } else{
       return a > b;	//当字符串长度相等时,保持顺序不变
   }
});

其次,将空字符串降入到哈希集合中,其思路为遍历words数组时每次截取前n-1个字符为一个字符串,然后去哈希表中进行查找。

这样既能解决单个字符问题,也能解决字符串长度相同时先得到字典序靠前的答案。

该题实现代码如下:

class Solution {
public:
    string longestWord(vector& words) {
        sort(words.begin(), words.end(), [](const string & a, const string & b){
            if(a.size() != b.size()){
                return a.size() < b.size();
            }else{
                return a > b;
            }
        });

        string longest = "";
        unordered_set cnt;
        cnt.emplace("");

        for(auto & word:words)
        {
            if(cnt.count(word.substr(0, word.size()-1))){
                cnt.emplace(word);
                longest = word;
            }
        }
        return longest;
    }
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SwJvg10e-1649921548876)(C:\Users\FOX\AppData\Roaming\Typora\typora-user-images\image-20220317091218001.png)]

方法二:字典树

实现该方法需要先了解字典树解法的使用方法:实现 Trie (前缀树) - 实现 Trie (前缀树) - 力扣(LeetCode) (leetcode-cn.com)

然后再详细查看:词典中最长的单词 - 词典中最长的单词 - 力扣(LeetCode) (leetcode-cn.com)

3-18 简易银行系统


2043. 简易银行系统 - 力扣(LeetCode) (leetcode-cn.com)

该题为简单模拟。

class Bank {
private:
    vector balance;
    int n;
public:
    Bank(vector& balance):balance(balance),n(balance.size()) {
    }
    
    bool transfer(int account1, int account2, long long money) {
        if(account1 >n || account2 > n || balance[account1-1] < money)
            return false;
        balance[account1 -1] -= money;
        balance[account2-1] += money;
        return true;
    }
    
    bool deposit(int account, long long money) {
        if(account > n) return false;
        else{
            balance[account-1] += money;
        } 
        return true;
    }
    
    bool withdraw(int account, long long money) {
        if(account > n || balance[account-1]transfer(account1,account2,money);
 * bool param_2 = obj->deposit(account,money);
 * bool param_3 = obj->withdraw(account,money);
 */

初始化空间复杂度为O(N),其他操作复杂度均为O(1)

3-20 网络空闲的时刻 --[Nm]


2039. 网络空闲的时刻 - 力扣(LeetCode) (leetcode-cn.com)

方法一:图+BFS

class Solution {
public:
    int networkBecomesIdle(vector>& edges, vector& patience) {
        int n = patience.size();   //记录服务器个数    
        vector> adj(n); //创建权值为1的图
        vector visit(n, false);  
        for (auto & v : edges) {
            adj[v[0]].emplace_back(v[1]);
            adj[v[1]].emplace_back(v[0]);
        }

        queue qu;  //使用队列来遍历
        qu.emplace(0);  //从主服务器开始进行广度优先搜索
        visit[0] = true;
        int dist = 1;
        int ans = 0;    //最长结束时间
        while (!qu.empty()) {
            int sz = qu.size();

            for (int i = 0; i < sz; ++i) {
                int curr = qu.front();
                qu.pop();
                for (int & v : adj[curr]) {
                    //之前到达的点时间一定最短,跳过当前被遍历过的点
                    if (visit[v]) {
                        continue;
                    }
                    qu.emplace(v);
                    int di = 2*dist, t = patience[v];
                    int time = ((di - 1) / t) * t + di ;
                    ans = max(ans, time);
                    visit[v] = true;
                }
            }

            dist++; //当前服务器距主服务器的距离
        }
        return ans+1; //0~1秒轮空
    }
};

时间和空间复杂度皆为O(M+N),其中N为节点数目,M为edge数组大小。

3-21 两数之和 IV - 输入 BST


两数之和 IV - 输入 BST - 两数之和 IV - 输入 BST - 力扣(LeetCode) (leetcode-cn.com)

该题使用哈希表查找差值解决。

也可以通过遍历元素进数组,再使用双指针。值得注意的是二叉搜索树的中序遍历为升序。

该方法还可以被扩展成为直接对二叉树进行双指针操作,但是需要一个栈来进行辅助。

方法一:深度优先+哈希

该方法使用递归,可以使用栈来进行替换。

同样的,使用广度优先也可以解决问题,使用队列。

如下为该方法的代码:

class Solution {
    unordered_set val;
public:
    bool findTarget(TreeNode* root, int k) {
        if(root == nullptr) return false;
        int curVal = root->val;
        if(val.count(k-curVal)) return true;
        else val.insert({curVal});

        return findTarget(root->left, k) || findTarget(root->right, k);
    }
};

3-22 如果相邻两个颜色均相同则删除当前颜色


2038. 如果相邻两个颜色均相同则删除当前颜色 - 力扣(LeetCode) (leetcode-cn.com)

贪心策略:

class Solution {
public:
    bool winnerOfGame(string colors) {
        int cnt = 0, n = colors.size();
        for(int i = 1; i < n - 1; ++i)
            if(colors[i] == colors[i-1] && colors[i] == colors[i+1])
                cnt += colors[i] == 'A' ? 1 : -1;
        return cnt > 0;
    }
};

3-23 字典序的第K小数字 --[Nm]


440. 字典序的第K小数字 - 力扣(LeetCode) (leetcode-cn.com)

首先,我们先了解什么是字典序:

即:根据数字的前缀进行排序

如 10的前缀为1,9的前缀为9,故 10<9

112 < 12,因为11<12

故而尝试进行1~20的排序 [ 1,10,11,12,13,14,15,16,17,18,19,2,20,3,4,5,6,7,8,9 ]

通过以上排序可见一定规律,即同一前缀按序排在一起,可看作派生出的子树。

通过创建一个字典树即可看出,前序遍历该树即可得到从小到大的数字序列。

而遍历到第K个节点即为第k小的数字。

我们假设某一个节点 ni 为第 i 小数字,以该节点为根节点的子树下的子节点总数为M,则该节点右侧值为 ni+1 的节点为第 i+M+1 小的数字。

即若寻找第k个最小数字,而当前节点为第i小数字,需要从该节点开始再寻找 k-i 个数字即可。

有以下几种情况:

(1)M <= k-i:这代表第k小数字不在节点 ni 的子树中,跳过所有节点,向 ni+1 开始寻找到第 k-i-M 个数字。

(2)M >= k-i:这代表第k小数字在节点 ni 的子树中,直接向下寻找,向下第一个为 ni * 10

如何计算M:

第i层数字个数count :LAST i - FIRST i + 1

last和first 皆为上一层 *10 得到

LAST i = LAST i-1 * 10

FIRST i = FRIST i-1 *10

需要注意的是,给定的范围最大值为n

则当前层最右边的孩子节点为: LAST i = min (LAST i ,n);

迭代直到当前层最左边孩子节点 FIRST i > n

迭代结束

class Solution {
public:
    int findKthNumber(int n, int k) {
        int curr = 1;
        k--;
        while(k > 0){
            int steps = getSteps(curr, n);
            if(steps <= k){
                k -= steps;
                curr++;
            }else {
                curr = curr * 10;
                k--;
            }
        }
        return curr;
    }
    // 获取当前节点的孩子节点总个数
    int getSteps(int curr, long n){
        int steps = 0;
        long first = curr;
        long last = curr;
        while( first <= n){
            steps += min(last , n) - first +1;
            first = first * 10;
            last = last * 10 + 9;
        }
        return steps;
    }
};

3-24 图片平滑器


661. 图片平滑器 - 力扣(LeetCode) (leetcode-cn.com)

该题为遍历模拟问题。

class Solution {
public:
    vector> imageSmoother(vector>& img) {
        int m = img.size(), n = img[0].size();
        vector> res = img;
        int vec[9][2] = {{0,-1},{-1,-1},{1,-1},{-1,0},{0,0},{1,0},{-1,1},{0,1},{1,1}};
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                int sum = 0,num = 0;
                for(auto dir:vec){
                    int x = i+dir[0],y = j+dir[1];
                    if(x >= 0 && x < m && y >= 0 && y < n){
                        sum += img[x][y];
                        num ++;
                    }
                }
                res[i][j] = sum/num;
            }
        }
        return res;
    }
};

忽略有限,空间为O(1),时间O(m*n);

2-25 阶乘后的零 --[Nm]


172. 阶乘后的零 - 力扣(LeetCode) (leetcode-cn.com)

该题需要使用数学的方法来进行运算。

寻找0的个数,即寻找质因子中10的个数。

即寻找质因子为 2或5 中的较小者。

经过证明,质因子为5的个数不会大于质因子为2的个数。

class Solution {
public:
    int trailingZeroes(int n) {
        int res = 0;
        for(int i = 5; i <= n; i += 5){
            for(int x = i; x % 5 == 0; x /= 5){
                res ++;
            }
        }
        return res;
    }
};

时间复杂度为O(N),空间复杂度为O(1)

2-26 棒球比赛


682. 棒球比赛 - 力扣(LeetCode) (leetcode-cn.com)

该题很容易想到使用栈,但却需要不断记录上一个栈顶元素,故使用数组模拟。

值得注意的是,switch()内判断为int类型,无法判断string。

以下为代码:

class Solution {
public:
    int calPoints(vector& ops) {
        vector vec;
        int res = 0;
        for(string &str:ops){
            int n = vec.size();
            switch(str[0]){
                case '+':
                    res += vec[n-1] + vec[n-2];
                    vec.push_back(vec[n-1] + vec[n-2]);
                    break;
                case 'D':
                    res += 2 * vec[n-1];
                    vec.push_back(2 * vec[n-1]);
                    break;
                case 'C':
                    res -= vec[n-1];
                    vec.pop_back();
                    break;
                default:
                    res += stoi(str);
                    vec.push_back(stoi(str));
                    break;
            }
        }
        return res;
    }  
};

双复杂度为O(N)

2-27 找出缺失的观测数据


2028. 找出缺失的观测数据 - 力扣(LeetCode) (leetcode-cn.com)

方法为使用模拟

以下为我的代码版本:

class Solution {
public:
    vector missingRolls(vector& rolls, int mean, int n) {
        int m = rolls.size();
        int sum = m + n;
        int preSum = 0;
        for(int &num:rolls){
            preSum += num;
        }
        int check = sum * mean;
        if(check  6*n + preSum){
            return {};
        }
        int dif = check - preSum;
        vector res;
        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= 6; ++j){
                if(dif-j >= n-i && dif-j <= 6*(n-i)){
                    res.push_back(j);
                    dif -= j;
                    break;
                }
            }
        }
        return res;
    }
};

以下为官方代码版本

class Solution {
public:
    vector missingRolls(vector& rolls, int mean, int n) {
        int m = rolls.size();
        int sum = mean * (n + m);
        int missingSum = sum;
        for (int & roll : rolls) {
            missingSum -= roll;
        }
        if (missingSum < n || missingSum > 6 * n) {
            return {};
        }
        int quotient = missingSum / n, remainder = missingSum % n;
        vector missing(n);
        for (int i = 0; i < n; i++) {
            missing[i] = quotient + (i < remainder ? 1 : 0);
        }
        return missing;
    }
};

时间复杂度为O(N+M),空间复杂度为O(1)

3-28 交替位二进制数


693. 交替位二进制数 - 力扣(LeetCode) (leetcode-cn.com)

该题使用位运算模拟即可解答。

class Solution {
public:
    bool hasAlternatingBits(int n) {
        int flg = -1;
        while(n){
            if( (n & 1) != flg){
                flg = n & 1;
                n >>= 1;
            }
            else{
                return false;
            }
        }
        return true;
    }
};

时间复杂度为O(logN),输入的数字n共有logn位

3-29 考试的最大困扰度


2024. 考试的最大困扰度 - 力扣(LeetCode) (leetcode-cn.com)

3-30 找到处理最多请求的服务器


1606. 找到处理最多请求的服务器 - 力扣(LeetCode) (leetcode-cn.com)

3-31 自除数


728. 自除数 - 力扣(LeetCode) (leetcode-cn.com)

该题目直接模拟即可

正确代码如下:

class Solution {
public:
    vector selfDividingNumbers(int left, int right) {
        vector ans;
        for(int i = left; i <= right; i++){
            if(isSelfDividing(i)){
                ans.emplace_back(i);
            }
        }
        return ans;
    }
     bool isSelfDividing(int num){
        int temp = num;
        while(temp > 0){
            int dight = temp % 10;
            if(dight == 0 || num % dight != 0){
                    return false;
            }
                temp /= 10;
        }
            return true;
     }
};

时间复杂度:O(nlog right),其中n是范围内的整数个数,right是范围内的最大整数。对于范围内的每个整数,需要O(log right)的时间判断是否为自除数。

以下代码运行超时,请检查错误

class Solution {
public:
    vector selfDividingNumbers(int left, int right) {
        vector res;
        for(int num = left; num <= right; num++){
            int dig = num;
            while(dig != 0){
                int n = dig % 10;
                if(n == 0 || num % n != 0){
                    continue;	//该位置使用continue无意义,但使用break同样无法过滤当前num
                }
                dig /= 10;
            }
            //建议设置一个flag标志,用于检查当前num是否合法
            res.push_back(num);
        }
        return res;
    }
};

你可能感兴趣的:(刷题记录,c++,数据结构,算法)