剑指Offer 第34-44题

AcWing 46. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。

如果是则返回true,否则返回false。

假设输入的数组的任意两个数字都互不相同。

样例
输入:[4, 8, 6, 12, 16, 14, 10]

输出:true

题解:

  1. 中序(二叉搜索树的中序是从小到大的结果)+后序建树
  2. 后序遍历最后一个数为根,二叉搜索树的性质,我们直接从头开始找到比根小的值,这便是左子树;右边部分便是右子树,如果不符合便是错误。
class Solution {
public:
    vector<int>  seq;
    bool verifySequenceOfBST(vector<int> sequence) {
        seq = sequence;
        if(seq.empty()) return true;
        return dfs(1, seq.size() - 1);
    }
    bool dfs(int l, int r){
        if(l >= r) return true;
        int root = seq[r];
        int  k = 0;
        while(k < r && seq[k] < root) k++;
        for(int i = k; i < r; i++)
        {A
            if(seq[i] < root) return false;
        }
        return dfs(l, k - 1) && dfs(k, r - 1);
    }
};

AcWing 47. 二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。

从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

样例
给出二叉树如下所示,并给出num=22。
剑指Offer 第34-44题
dfs

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> findPath(TreeNode* root, int sum) {
        if(root == NULL) return res;
        vector<int> path;
        dfs(root, sum, path);
        return res;
    }
    void dfs(TreeNode* root, int sum, vector<int>&path){
        if(root->left == NULL && root->right == NULL)
        {
            if(sum - root->val == 0)
            {
                path.push_back(root->val);
                res.push_back(path);
                path.pop_back();
            }
            return;
        }
        path.push_back(root->val);
        if(root->left != NULL) dfs(root->left, sum - root->val, path);
        if(root->right != NULL) dfs(root->right, sum - root->val, path);
        path.pop_back();
    }
};

AcWing 48. 复杂链表的复刻

请实现一个函数可以复制一个复杂链表。

在复杂链表中,每个结点除了有一个指针指向下一个结点外,还有一个额外的指针指向链表中的任意结点或者null。

注意:

函数结束后原链表要与输入时保持一致。

  1. hash表做映射 时间O(n) 空间O(n)
  2. 剑指Offer 第34-44题
/**
 * Definition for singly-linked list with a random pointer.
 * struct ListNode {
 *     int val;
 *     ListNode *next, *random;
 *     ListNode(int x) : val(x), next(NULL), random(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *copyRandomList(ListNode *head) {
        if(!head) return head;
        unordered_map<ListNode*, ListNode*> pos;
        for(auto p = head; p; p = p -> next)
        {
            pos[p] = new ListNode(p->val);
        }
        
        pos[NULL] = NULL;
        for(auto p = head; p; p = p -> next)
        {
            pos[p]->next = pos[p->next];
            pos[p]->random = pos[p->random];
        }
        return pos[head];
    }
};
/**
 * Definition for singly-linked list with a random pointer.
 * struct ListNode {
 *     int val;
 *     ListNode *next, *random;
 *     ListNode(int x) : val(x), next(NULL), random(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *copyRandomList(ListNode *head) {
        // 每一个节点后面加复制
        for(auto p = head; p; p = p->next)
        {
            auto np = new ListNode(p->val);
            auto next = p->next;
            p->next = np;
            np->next = next;
            p = np;
        }
        
        // 复制random指针
        for(auto p = head; p; p = p->next->next)
        {
            if(p->random)
                p->next->random = p->random->next;
        }
        
        // 分离两个链表
        auto dummy = new ListNode(-1);
        auto cur = dummy;
        for(auto p = head; p; p = p->next)
        {
            cur->next = p->next;
            cur = cur->next;
            p->next = p->next->next;
        }
        return dummy->next;
    }
};

AcWing 49. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。

要求不能创建任何新的结点,只能调整树中结点指针的指向。

注意:

需要返回双向链表最左侧的节点。
例如,输入下图中左边的二叉搜索树,则输出右边的排序双向链表。
剑指Offer 第34-44题
解题思路
实现返回每一个子树的两端节点即可

AcWing 50. 序列化二叉树

AcWing 51. 数字排列(good)

输入一组数字(可能包含重复数字),输出其所有的排列方式。

样例
输入:[1,2,3]

输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

题解

根以前的枚举不一样,需要用占位的思想去枚举,预先开出n个坑位,将nums每一个数往里面放,一个数只能放在前一个相同数字的后面。

class Solution {
public:
    int n = 0;
    vector<vector<int> > ans;
    vector<int> path;//位置
    //u表示枚举到nums第几个数了,start表示从空位的第几位开始可以放
    void dfs(int u, int start, int state, vector<int>& nums)
    {
        if(u == n)
        {
            ans.push_back(path);
            return;
        }
        
        // 头一个数或者与上一个数不同
        if(u == 0 || nums[u] != nums[u-1]) start = 0;
        for(int i = start; i < n; i++)
        {
            if(!(state>>i&1))
            {
                path[i] = nums[u];
                dfs(u + 1, i + 1, state+(1<<i), nums);
            }
        }
    }
    vector<vector<int>> permutation(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        n = nums.size();
        path.resize(n);
        dfs(0, 0, 0, nums);
    
        return ans;
    }
};

库函数

class Solution {
public:
    vector<vector<int>> permutation(vector<int>& nums) {
        sort(nums.begin(), nums.end());

        vector<vector<int>> res;
        do
        {
            res.push_back(nums);
        }while (next_permutation(nums.begin(),nums.end()));
        // do res.push_back(nums); while (next_permutation(nums.begin(), nums.end()));

        return res;
    }
};

AcWing 52. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

假设数组非空,并且一定存在满足条件的数字。

思考题:

假设要求只能使用 O(n) 的时间和额外 O(1) 的空间,该怎么做呢?
样例
输入:[1,2,1,1,3]

输出:1

题解

抵消(配对)的思想。
循环数组

  1. count == 0: value=a[i], count=1
  2. count != 0:
    • value != a[i] : count -= 1
    • value == a[i] : count += 1

n个村民
好人数量 > n/2 (回答的是正确)
坏蛋数量 < n/2 (回答随机)
o(n)次询问找出坏蛋。

count: 当前人被信任的次数,0
val: 人是谁,-1

从头循环,

  1. count == 0,首先问自己是否被信任,
    回答是好人,count += 1,val = id
    回答坏蛋的一定是坏蛋,好人只会回答自己是好人。
  2. count != 0, 问val十分是好人,若val是好人,则count += 1。
    若是坏人,则count -= 1。count减到0,则替换
    好人的信任值。

好人的信用量一定大于坏人的信用量,消耗一个好人肯定需要消耗一个坏人。

class Solution {
public:
    int moreThanHalfNum_Solution(vector<int>& nums) {
        int cnt = 0, val = -1;
        for(auto it: nums)
        {
            if(cnt == 0)
            {
                val = it;
                cnt ++;
            }
            else
            {
                if(val == it) cnt ++;
                else cnt --;
            }
        }
        return val;
    }
};

AcWing 53. 最小的k个数

输入n个整数,找出其中最小的k个数。

注意:

数据保证k一定小于等于输入数组的长度;
输出数组内元素请按从小到大顺序排序;
样例
输入:[1,2,3,4,5,6,7,8] , k=4

输出:[1,2,3,4]

用大根堆维护一下即可,(优先队列)

class Solution {
public:

    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> heap;
        for(auto it : input)
        {
            
            if(heap.size() >= k)
            {
               
                if(it < heap.top())
                {
                    
                    heap.pop();
                    heap.push(it);
                }
            }
            else
                heap.push(it);
           
        }
        vector<int> res;
        for(int i = 0; i < k; i++)
        {
            res.push_back(heap.top());
            heap.pop();
        }
        reverse(res.begin(), res.end());
        return res;
    }
};
class Solution {
public:
    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> heap;
        for (auto x : input)
        {
            if (heap.size() < k || heap.top() > x) heap.push(x);
            if (heap.size() > k) heap.pop();
        }
        vector<int> res;
        while (heap.size()) res.push_back(heap.top()), heap.pop();
        reverse(res.begin(), res.end());
        return res;
    }
};


AcWing 54. 数据流中的中位数

如何得到一个数据流中的中位数?

如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。

如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

样例
输入:1, 2, 3, 4

输出:1,1.5,2,2.5

解释:每当数据流读入一个数据,就进行一次判断并输出当前的中位数。

题解
维护两个堆,大根堆维护较小的那部分,小根堆维护较大那部分,保证大跟堆的数量最多只比小根堆的数量多1个即可,代码中实现的是比较简捷的做法。

class Solution {
public:
    priority_queue<int, vector<int>, greater<int> > heap_u;
    priority_queue<int> heap_d;
    int n = 0;
    void insert(int num){
        n++; 
        heap_d.push(num);
      
        while(heap_u.size() != 0 && heap_d.top() > heap_u.top())
        {
            int maxx = heap_d.top(), minn = heap_u.top();
            heap_d.pop(), heap_u.pop();
            heap_d.push(minn), heap_u.push(maxx);
        }
       
        
        if(heap_d.size() - heap_u.size() > 1)
        {
            heap_u.push(heap_d.top());
            heap_d.pop();
        }
    }

    double getMedian(){
        if(n & 1)
            return heap_d.top();
        else
            return (heap_d.top() + heap_u.top()) / 2.0;
    }
};

AcWing 55. 连续子数组的最大和

输入一个 非空 整型数组,数组里的数可能为正,也可能为负。

数组中一个或连续的多个整数组成一个子数组。

求所有子数组的和的最大值。

要求时间复杂度为O(n)。

样例
输入:[1, -2, 3, 10, -4, 7, 2, -5]

输出:18

题解
状态表示:
集合划分:以a[k] 为结尾的连续子数组

  1. 只有a[k]一个数
  2. 至少两个数,一定选a[k],所以可以去掉a[k]
    剑指Offer 第34-44题
    相当于f(k-1) + a[k]

f(k) = max{a[k], f(k-1) +a[k]}
f(k) = a[k] + max(0, f(k-1))
S = a[k] + max(0, S)

AcWing 56. 从1到n整数中1出现的次数

上一篇:【调优工具】MAT内存分析工具


下一篇:堆Heap