C++ : 力扣_Top(295-344)

C++ : 力扣_Top(295-344)

文章目录

  • C++ : 力扣_Top(295-344)
      • 295、数据流的中位数(困难)
      • 297、二叉树的序列化与反序列化(困难)
      • 300、最长上升子序列(中等)
      • 315、计算右侧小于当前元素的个数(困难)
      • 322、零钱兑换(中等)
      • 324、摆动排序II(中等)
      • 326、3的幂(简单)
      • 328、奇偶链表(中等)
      • 329、矩阵中的最长递增路径(困难)
      • 334、递增的三元子序列(中等)
      • 341、扁平化嵌套列表迭代器(中等)
      • 344、反转字符串(简单)


295、数据流的中位数(困难)

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2

进阶:
如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

class MedianFinder {
    priority_queue<int> max; // 大顶堆
    priority_queue<int, vector<int>, greater<int>> min; // 小顶堆
public:
    MedianFinder() {
    }
    void addNum(int num) {
        if(max.size()==0) max.push(num);
        else if((max.size()+min.size())%2==0){ // 该放入到左侧大顶堆
            if(min.top()<num){ // 如果新数大于右侧小顶堆最小值
                max.push(min.top()); // 先移动两个顶堆的头部元素,再push到最小堆
                min.pop();
                min.push(num);
            }
            else max.push(num); // 直接移入大顶堆
        }
        else{ // 该放入到右侧小顶堆
            if(max.top()>num){ // 如果新数小于左侧大顶堆最大值
                min.push(max.top()); // 先移动顶部元素,再push到最大堆
                max.pop();
                max.push(num);
            }
            else min.push(num); // 直接移入小顶堆
        }
    }
    double findMedian() {
        if(max.size()==min.size()){
            return ((double)max.top()+double(min.top())) / 2; 
        }
        return max.top();
    }
};

思路:剑指offer的原题,利用大顶堆和小顶堆来做比较好,最大堆用来存放较小的一半数据,最小堆用来存放较大的一半数据,轮流插入,中位数利用两个顶堆的顶部元素就可以计算出了。不必使用make_heap,使用基于vector最大堆的适配器proirity_queue就行了,注意默认是最大堆,最小堆创建是:priority_queue min; 注意中间还有一个 vector 的参数;另外插入时注意顶部元素转换的问题就可以了;


297、二叉树的序列化与反序列化(困难)

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化为 "[1,2,3,null,null,4,5]"

提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。

class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(!root) return "";
        deque<TreeNode*> list;
        list.push_back(root);
        string result = "[";
        while(!list.empty()){ // 利用双端队列按层遍历树,并添加到result中
            TreeNode * tmp = list.front();
            list.pop_front();
            if(!tmp){ // 是空节点
                result.append("null,");
                continue;
            }
            list.push_back(tmp->left);
            list.push_back(tmp->right);
            result.append(to_string(tmp->val)+",");
        }
        int index = result.size()-1;
        for(; index>=0; --index){ // 从右往左定位到最后一个数字字符
            if(result[index]>='0' && result[index]<='9'){
                break;
            }     
        }
        return result.substr(0,index+1) + "]";
    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        TreeNode * root = nullptr;
        deque<TreeNode*> list; // 存放创建节点的双端队列
        int l = 0, r = 0;
        pair<int,int> p = find_next_num(data, l); // 查找第一个数字的范围
        if(p.second < 0) return nullptr; // 第一个是null或者没有对应字段
        else{
            l = p.first; r = p.second;
            root = new TreeNode(stoi(data.substr(l, r-l))); // 创建根节点
            list.push_back(root); // 添加到队列
        }
        while(!list.empty()){ // 循环队列
            TreeNode * tmp = list.front(); // 获取队首元素
            list.pop_front();
            if(!tmp) continue; // 节点为空,直接跳过
            
            p = find_next_num(data, r); // 创建当前节点的左子节点
            l = p.first; r = p.second;
            if(l>=0){ // 当前字符串还有值
                if(r<0){
                    r = l + 4;
                    list.push_back(nullptr); // 当前是null
                }
                else{ // 当前是数字
                    tmp->left = new TreeNode(stoi(data.substr(l,r-l)));
                    list.push_back(tmp->left);
                }
            }
            else continue; // 字符串已搜索完毕,直接继续循环弹出队列剩余节点

            p = find_next_num(data, r); // 创建右子节点
            l = p.first; r = p.second;
            if(l>=0){ // 当前字符串还有值
                if(r<0){
                    r = l + 4;
                    list.push_back(nullptr); // 当前是null
                }
                else{ // 当前是数字
                    tmp->right = new TreeNode(stoi(data.substr(l,r-l)));
                    list.push_back(tmp->right);
                }
            }
            else continue; // 字符串已搜索完毕,直接继续循环弹出队列剩余节点
        }
        return root;
    }
    pair<int,int> find_next_num(string & data, int index){ // 返回字符串中下一个代表数字或null的下标范围
        int left = index;
        while(left<data.size() && (data[left]<'0' || data[left]>'9') ){ 
            if(data[left]=='n' || data[left]=='-') break; // 遇到'n'或复数的情况
            ++left; // 如果当前不是'n'或不是数字,继续遍历
        }
        if(left>=data.size()) return pair<int,int>(-1,-1); // 如果后面没有数字或null了,返回-1,-1
        if(data[left]=='n') return pair<int,int>(left, -1); // 当前是null的话pair的第二位设为-1
        int right = left + 1; // 当前有数字,查找数字范围
        while(right<=data.size() && data[right]<='9' && data[right]>='0'){
            ++right;
        }
        return pair<int,int>(left, right); // 返回数字的范围下标
    }
};

思路:本题没有太大难度,在BFS二叉树的层序遍历算法基础之上就可以完成。问题在于代码量比较大、复杂,string字符串处理的时候比较繁琐,需要时间和细心来完成;具体思路见注释;


300、最长上升子序列(中等)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

// 动态规划O(n^2)
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.empty()) return 0;
        int size = nums.size();
        vector<int> bp(size, 1); // 初始化动态规划矩阵,所有初始子序列长度为1
        for(int i=0; i<size; ++i){
            for(int j=0; j<i; ++j){
                if(nums[j]<nums[i])
                {
                    bp[i] = max(bp[j]+1, bp[i]);
                }
            }
        }
        int result = 0;
        for(auto v : bp){
            result = max(result, v);
        }
        return result;
    }
};
// 贪心算法O(nlogn)
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.empty()) return 0;
        int size = nums.size();
        // 表示长度为 i 的最长上升子序列的末尾元素的最小值,注意该数组为单调递增的数组
        vector<int> dd(size+1, 0); 
        int index = 1; // 表示目前的上升子序列长度i
        dd[index] = nums[0]; // 长度为1的子序列的最小值就是第一个数
        for(int i=0; i<size; ++i){ // 遍历数组
            if(nums[i] > dd[index]){ // 如果新的数比当前所有长度的上升子序列最后元素最小值要大
                dd[++index] = nums[i]; // 延伸出新的子序列,并且最后元素最小值就是当前遍历到的数
            }
            else{ // 当前不是最大的数,二分搜索当前单调的dd数组,插入到合适的位置
                int left = 1, right = index, pos = 0;
                while(left <= right){
                    int mid = (left + right) >> 1;
                    if(nums[i] > dd[mid]){
                        left = mid + 1;
                        pos = mid;
                    } 
                    else{
                        right = mid - 1;
                    }
                }
                dd[pos+1] = nums[i]; // 对应长度的子序列末尾元素更新,替换掉原先更大的那个数
            }
        }
        return index;
    }
};

思路:首先明确这道题是找最大长度的上升子序列,序列顺序必须是从左到右,且可以不连续。动态规划方法是遍历每个点i时遍历它前面的所有点j,bp[]数组存放了以该点为结束点的最长上升子序列的长度,即 nums[i]>nums[j] && bp[i]=max(bp[j]+1, bp[i]),遍历求解即可;更巧妙的方法是贪心方法,但比较复杂,复杂度是O(nlogn),设定了数组d[i]表示长度为 i 的最长上升子序列的末尾元素的最小值,可以证明d[i]是递增的,遍历数组nums,并更新d[i]数组,由于d[i]是递增的,所以可以用二分搜索进行搜索要更新的位置,具体的思路比较复杂,可以通过代码理解;


315、计算右侧小于当前元素的个数(困难)

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

输入: [5,2,6,1]
输出: [2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        if(nums.size()==0){
            return {};
        }
        vector<int> res(nums.size(), 0);
        vector<pair<int, int> > tmp; // 利用pair数组存储每个元素的下标位置
        for(int i=0; i<nums.size(); ++i){
            tmp.push_back(make_pair(nums[i], i));
        }
        MergeSort(tmp, 0, tmp.size()-1, res); // 直接对对pair数组进行归并排序
        return res;
    }
    void MergeSort(vector<pair<int, int>>& tmp, int left, int right, vector<int>& res){
        if(left < right){
            int mid = (left + right) >> 1;
            MergeSort(tmp, left, mid, res);
            MergeSort(tmp, mid+1, right, res);
            Merge(tmp, left, mid, right, res);
        }
    }
    void Merge(vector<pair<int, int>>& tmp, int left, int mid, int right, vector<int>& res){
        int size1 = mid-left+1;
        int size2 = right-mid;
        vector<pair<int, int>> L(size1+1);
        vector<pair<int, int>> R(size2+1);
        for(int i=0; i<size1; ++i){
            L[i] = tmp[left + i];
        }
        L[size1] = make_pair(INT_MAX, 0);
        for(int i=0; i<size2; ++i){
            R[i] = tmp[mid + i + 1];
        }
        R[size2] = make_pair(INT_MAX, 0);
        int i = 0, j = 0;
        while(i < size1 && j < size2 && left <= right){
            if(L[i].first <= R[j].first){
                tmp[left] = L[i++];
                res[tmp[left].second] += j; // 与原归并的不同之处:将左侧数组的计数加上右侧数组中比它小的部分(这里有点绕)
                ++left;
            }
            else{
                tmp[left++] = R[j++]; // 右侧数组中不需要累加个数,因为右侧数组本来就在左侧数组之后,其元素结果不必考虑左侧数组
            }
        }
        for(; i<size1; ++i){
            tmp[left] = L[i];
            res[tmp[left].second] += j;
            ++left;
        }
        for(; j<size2; ++j){
            tmp[left++] = R[j];
        }
    }
};

思路:这道题可以使用暴力搜索求解,也可以使用记忆排序的方式:如从后往前遍历数组,将遍历的每个数插入一个排序存储的数组中,插入的过程使用二分查找即可,这样每个插入的数,只要计算其插入的数据在数组中的位置,就能知道其后面有几个比它小的;还有一种方式是使用归并排序,事先记录所有元素和其对应的下标,然后在归并排序中每次归并时,累加左侧数组逆序对的个数,是一种比较巧妙的方法,但也比较经典;


322、零钱兑换(中等)

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

输入: coins = [2], amount = 3
输出: -1

说明:
你可以认为每种硬币的数量是无限的。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        if(amount<0 || coins.empty()) return -1;
        vector<int> bp(amount+1, amount+1); // bp[n]表示总额amount等于n的时候,使用的最小硬币数量(不会超过总额值)
        bp[0] = 0; // 总额为0,使用硬币数也为0
        for(int i=1; i<=amount; ++i){ // 从1往上计算总金额
            for(auto val : coins){ // 每个金额处遍历不同数额硬币,查找组合可能
                if(val>0 && val<=i){
                    bp[i] = min(bp[i], bp[i-val]+1); // 状态转移方程,for(all val) bp[i]=min(bp[i], bp[i-val]+1);
                }
            }
        }
        return bp[amount] > amount ? -1 : bp[amount]; // 需要判断一下当前值是否符合组合要求
    }
};

思路:典型的动态规划经典题目,需要特别注意,会用、会想、会写;首先问题是求最优解,首先考虑贪心思路,但代码貌似不太好写;于是转向动态规划思路:创建动态数组防止重复子问题的计算;动态数组bp[n]表示总额为n时所需的最小硬币数量,全部初始化一个较大的数值,状态转移方程为bp[i]=min(bp[i], bp[i-val]+1); 从总额为1一直计算到总额为amount,注意最后可能无法成功组合,这种情况下bp[amount]的值应该不小于最初设定的较大值;这道题很好,要熟练掌握;


324、摆动排序II(中等)

给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。

输入: nums = [1, 5, 1, 1, 6, 4]
输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6]

输入: nums = [1, 3, 2, 2, 3, 1]
输出: 一个可能的答案是 [2, 3, 1, 3, 1, 2]
说明:
你可以假设所有输入都会得到有效的结果。

进阶:
你能用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现吗?

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        if(nums.empty()) return;
        // 利用nth_elements找到中位数,大于中位数的在右,小于中位数的在左,等于中位数的位置不确定
        nth_element(nums.begin(), nums.begin()+nums.size()/2, nums.end()); 
        auto mid = nums.begin()+nums.size()/2; // 获取中位数
        int mid_val = *mid;
        int l=0, m=0, r=nums.size()-1; // 做一次荷兰国旗三色排序,将等于中位数的所有值放到中间段
        while(m<r){
            if(nums[m]>mid_val){
                swap(nums[m], nums[r]);
                --r;
            }
            else if(nums[m]<mid_val){
                swap(nums[m], nums[l]);
                ++l;
                ++m;
            }
            else{
                ++m;
            }
        }
        if(nums.size()%2) ++mid;
        vector<int> left(nums.begin(), mid); // 创建左右两个数组,存放两部分数据
        vector<int> right(mid, nums.end());
        for(int i=0; i<left.size(); ++i){ // 将左右两部分数组逆序交叉排序
            nums[i*2] = left[left.size()-1-i];
        }
        for(int i=0; i<right.size(); ++i){
            nums[i*2+1] = right[right.size()-1-i];
        }
    }
};

思路:这道题比较难,难度应该属于困难;首先要做到大小交叉排列,可以将数组排序后,平均分为两部分数组,前一部分小,后一部分大,然后交叉排列起来;但其中存在的问题有:如果[1,1,2,2,2,3],分割为[1,1,2]和[2,2,3],最终结果为[1,2,1,2,2,3],来自A的2和来自B的2出现在了相邻位置。解决这个问题的方法可以将两个分开的数组先逆序,再交叉插入,就没问题了;其次排序操作比较费时,可以使用nth_element方法,找到中位数,将小于中位数的值都放到左侧,大于中位数的值都放到右侧,但等于中位数的值位置会随机,为了将等于中位数的值都放到一起,还需要做一次荷兰国旗三色排序,具体见代码;这里涉及到一个计算复杂度的问题,需要了解nth_element是利用paitition的遍历二分方法,单向遍历二分,所以其计算复杂度为n+0.5n+0.25n+0.125n+…=2n,属于线性复杂度,其荷兰国旗三色排序也是线性复杂度;如果要做到空间复杂度为1,则需要使用虚拟地址映射方法,比较复杂,此处不表;


326、3的幂(简单)

给定一个整数,写一个函数来判断它是否是 3 的幂次方。

输入: 27
输出: true
示例 2:

输入: 0
输出: false
示例 3:

输入: 9
输出: true
示例 4:

输入: 45
输出: false

进阶:你能不使用循环或者递归来完成本题吗?

class Solution {
public:
    bool isPowerOfThree(int n) {
        if(n<=0) return false;
        double x = log10(n) / log10(3);
        return x - (int)x > 0 ? false : true;
    }
};

思路:该题利用循环或递归的方法比较容易完成;巧妙的方法是使用数学方式:3的x次方是n,即3^x=n,则log3(n)=x; 只需要判断log3(n)=log10(n)/log10(3)的结果是不是整数即可;注意double类型不能用==,!=比较大小,需要使用x - (int)x > 0 ? false : true判断是不是整数;


328、奇偶链表(中等)

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL

说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(!head) return head;
        ListNode * even, * tmp, * odd = head; // 奇偶链表当前节点odd和even,原链表当前节点tmp
        ListNode * even_head = nullptr; // 暂时记录偶链表头部,奇链表头部就是head;
        if(head->next){ // 给偶链表头部赋值,链表中节点顺序为 odd, even, tmp...
            even = head->next;
            tmp = even->next;
            even_head = even;
        }
        else return head;
        while(tmp && tmp->next){ // 遍历拆解奇偶链表
            odd->next = tmp;
            even->next = tmp->next;
            odd = tmp;
            even = tmp->next;    
            tmp = even->next;
        }
        if(tmp){ // 如果尾部还剩一个奇节点
            odd->next = tmp;
            odd = tmp;
        }
        odd->next = even_head; // 将偶链表接在奇链表之后
        even->next = nullptr; // 将链表尾部赋空,避免出现环
        return head;
    }
};

思路:注意虽说使用原地算法完成,但在思路上是可以将其拆解为两个链表的,只要不创建新的容器或节点即可;将链表拆解成奇链表和偶链表,然后前后拼接即可;注意这道题在边界条件和具体过程的编写上稍微有点绕,需要考虑清楚再写,最后要注意将新链表尾结点的next赋值空指针,不然可能会形成环;


329、矩阵中的最长递增路径(困难)

给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

输入: nums =
[9,9,4],
[6,6,8],
[2,1,1]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。

输入: nums =
[3,4,5],
[3,2,6],
[2,2,1]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

class Solution {
public:
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        if(matrix.empty() || matrix[0].empty()) return 0;
        int rows = matrix.size(), cols = matrix[0].size(); 
        vector<int> tmp_res(rows * cols, INT_MIN); // 记忆矩阵,存放以该节点为头结点的最长递增路径
        int max_val = 0;
        for(int r=0; r<rows; ++r){ // 遍历每个坐标,查找以当前节点开头的最长的递增路径
            for(int c=0; c<cols; ++c){ 
                max_val = max(max_val, findPath(matrix, r, c, INT_MIN, rows, cols, tmp_res)); 
            }
        }
        return max_val;
    } // DFS深度路径搜索
    int findPath(vector<vector<int>>& matrix, int r, int c, int val, int rows, int cols, vector<int>& tmp_res){
        if(r<0 || r>=rows || c<0 || c>=cols) return -1;
        if(matrix[r][c] <= val) return -1;
        if(tmp_res[r*cols+c]>0){ // 如果当前节点在记忆矩阵中已经算过
            return 1 + tmp_res[r*cols+c]; // 直接返回结果
        }
        int new_step = max(0, findPath(matrix, r+1, c, matrix[r][c], rows, cols, tmp_res)); // 计算新节点最大长度
        new_step = max(new_step, findPath(matrix, r-1, c, matrix[r][c], rows, cols, tmp_res));
        new_step = max(new_step, findPath(matrix, r, c+1, matrix[r][c], rows, cols, tmp_res));
        new_step = max(new_step, findPath(matrix, r, c-1, matrix[r][c], rows, cols, tmp_res));
        tmp_res[r*cols+c] = new_step; // 更新当前节点的记忆矩阵
        return 1 + new_step; // 返回1+当前节点最大路径
    }
};

思路:一道DFS路径搜索问题,由于路径搜索过程中会出现重复计算某一个节点,所以可以利用一个记忆矩阵记录已经计算过的节点数据,等到再次遍历到该节点时,可以直接利用之前的结果;具体思路见代码;


334、递增的三元子序列(中等)

给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。数学表达式如下:

如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。

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

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

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        if(nums.size()<3) return false;
        int min = INT_MAX, mid = INT_MAX; // 提前保存最小值和最小中间值
        for(int i=0; i<nums.size(); ++i){
            if(nums[i] <= min){ // 如果当前元素小于最小值,则更新最小值
                min = nums[i];
            }
            else if(nums[i] <= mid){ // 如果当前元素大于最小值,但小于中间值,则更新中间值
                mid = nums[i];
            }
            else{ // 当前元素大于中间值,则说明出现了三元递增组
                return true;
            }
        }
        return false;
    }  
};

思路:这道题可以用非常简单的方法完成,但想清楚背后的原理比较巧妙;类似于贪心思想,暂存当前遍历到的最小值和最小的中间值,如果新的元素小于最小值,则更新最小值;如果新的元素大于最小值,但小于中间值,就更新中间值;必须要想明白的是这两个操作都不会影响找第三个递增的数:假如当前的 small 和 mid 为 [3, 5],这时又来了个 1。假如不将 small 替换为 1,当下一个数字是 2,后面再接上一个 3 的时候,我们就没有办法发现这个 [1,2,3] 的递增数组了!也就是说,我们替换最小值,是为了后续能够更好地更新中间值;另外,即使更新了 small ,这个 small 在 mid 后面,没有严格遵守递增顺序,但它隐含着的真相是,有一个比 small 大比 mid 小的前·最小值出现在 mid 之前。因此,当后续出现比 mid 大的值的时候,我们一样可以通过当前 small 和 mid 推断的确存在着长度为 3 的递增序列。 所以,这样的替换并不会干扰我们后续的计算!


341、扁平化嵌套列表迭代器(中等)

给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。
列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。

输入: [[1,1],2,[1,1]]
输出: [1,1,2,1,1]
解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,1,2,1,1]。

输入: [1,[4,[6]]]
输出: [1,4,6]
解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,4,6]。

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * class NestedInteger {
 *   public:
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector &getList() const;
 * };
 */

class NestedIterator {
    stack<int> stk;
public:
    NestedIterator(vector<NestedInteger>& nestedList) {
        for(auto iter=nestedList.rbegin(); iter!=nestedList.rend(); ++iter){
            ParseData(nestedList, iter); // 从后往前遍历每一项嵌套列表
        }
    }
    void ParseData(vector<NestedInteger>& nestedList, vector<NestedInteger>::reverse_iterator iter){
        if( (*iter).isInteger() ){
            stk.push( (*iter).getInteger() ); // 如果是单个整数,直接入栈
        }
        else{
            auto list = (*iter).getList(); // 获取嵌套的vector
            for(auto it=list.rbegin(); it!=list.rend(); ++it){
                ParseData(list, it); // 从后往前遍历每一项嵌套列表
            }
        }
    }
    int next() {
        int res = stk.top();
        stk.pop();
        return res;
    }
    bool hasNext() {
        return !stk.empty();
    }
};

/**
 * Your NestedIterator object will be instantiated and called as such:
 * NestedIterator i(nestedList);
 * while (i.hasNext()) cout << i.next();
 */

思路:这道题没有太多算法技巧,更多的是对给出一个嵌套类的新概念的理解和使用;在元素依次打印方面,可以设置一个全局栈,然后反向迭代器遍历嵌套数组,如果元素是一个整数,直接入栈,如果元素还是一个嵌套数组,则嵌套循环利用反向迭代器进行解析,直到解析到整数并入栈,然后从栈中依次读取就可以了;


344、反转字符串(简单)

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

输入:[“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

class Solution {
public:
    void reverseString(vector<char>& s) {
        for(int i=0; i<s.size()/2; ++i){
            swap(s[i], s[s.size()-1-i]);
        }
    }
};

思路:头尾两两互换就行了,或者用泛型函数 reverse(s.begin(), s.end()); 也可以。


你可能感兴趣的:(C++ : 力扣_Top(295-344))