剑指offer 打卡计划 | 每日进步一点点 | 第十四天

77 阅读2分钟

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

剑指 Offer 37. 序列化二叉树

思路

(前序遍历序列化) O(n)

  1. 序列化:对整个二叉树进行先序遍历的序列存起来,同时需要把每个结点的空节点使用"#"进行标记,例如样例的顺序是1,2,#,#,3,4,#,#,5,#,#,
  2. 反序列化:对整个字符串按照","进行分割,把所有的元素按序存到链表中(链表元素的顺序是先序序列),按先序遍历的方式拿链表的元素,每次拿第一个元素作为根结点,并删除链表中的第一个元素,然后递归到左儿子做同样的操作,递归到右儿子做同样的操作。注意:若第一个元素是"#",表示该节点是null,直接返回null

时间复杂度分析: 每个节点仅遍历两次,故时间复杂度为 O(n)。

c++代码

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  * };
  */
 class Codec {
 public:
 ​
     string path;
     // Encodes a tree to a single string.
     string serialize(TreeNode* root) {
         dfs_s(root);
         return path;
     }
 ​
     void dfs_s(TreeNode* root){
         if(!root) path += "#,";
         else{
             path += to_string(root->val) + ',';
             dfs_s(root->left);
             dfs_s(root->right);
         }
     }
 ​
     // Decodes your encoded data to tree.
     TreeNode* deserialize(string data) {
         int idx = 0;
         return dfs_d(data, idx);
     }
 ​
     TreeNode* dfs_d(string &data, int& idx){
         if(data[idx] == '#') {
             idx += 2;
             return NULL;
         }else{
             int k = idx;
             while(data[idx] != ',') idx++;
             TreeNode* root = new TreeNode(stoi(data.substr(k, idx - k)));       idx++;
             root->left = dfs_d(data, idx);
             root->right = dfs_d(data, idx);
             return root;
         }
     }
 };
 ​
 // Your Codec object will be instantiated and called as such:
 // Codec ser, deser;
 // TreeNode* ans = deser.deserialize(ser.serialize(root));

剑指 Offer 38. 字符串的排列

思路

(全排列 + 去重) O(n * n!)

数字全排列的模板,对一个数组进行全排列,数组中不包含重复元素,比如 nusm = {1, 2, 3}时,我们输出:

 1 2 3
 1 3 2
 2 1 3
 2 3 1
 3 1 2
 3 2 1

c++代码模板

 void dfs(int u)
 {
     if(u == nums.size()) 
     {
         res.push_back(path);
         return ;
     }
     for(int i = 0; i < nums.size(); i++)
     {
         if(!st[i])
         {
             path[u] = nums[i];
             st[i] = true;
             dfs(u + 1);
             st[i] = false;                                  ;
         }
     }
 }

现在我们要对一个字符串进行全排列,但这个字符串中可能包含重复元素,比如s = aab,此时a1a2ba2a1b就属于同一方案,而题目要求答案数组里面不能有重复元素,因此我们要考虑去重。

如何去重

1、先将所有字符从小到大排序,这样相同的字符会排在一起。

2、对于相同的字符,我们人为去定义一种相对顺序,即优先使用出现顺序在前的字符,其次使用相对顺序在后的字符,比如a1a2b,我们按a1a2去使用。因此,如果我们先使用了a2,再a1,就要按下列代码减去剪枝。

 if(i && s[i] == s[i - 1] && !st[i - 1]) continue;

时间复杂度分析: O(n*n!)

c++代码

 class Solution {
 public:
     vector<string> res;
     vector<bool> st;
     vector<string> permutation(string s) {
        sort(s.begin(), s.end()); // 排序一下
        st = vector<bool>(s.size(), false);
        dfs(s, 0, "");
        return res; 
     }
     void dfs(string s, int u, string str){
         if(u == s.size()){
             res.push_back(str);
         }
         for(int i = 0; i < s.size(); i++){
             if(!st[i]){
                 if(i && s[i] == s[i - 1] && !st[i - 1]) continue; //定义一个使用的相对顺序    
                 st[i] = true;
                 dfs(s, u + 1, str + s[i]);
                 st[i] = false;
             }     
         }
     }
 };

剑指 Offer 39. 数组中出现次数超过一半的数字

思路

(投票算法) O(n)

当一个国家的总统候选人r的支持率大于50%的话,即使每个反对他的人都给他投一个反对票,抵消掉他的支持票,他的支持票也不会被完全消耗掉。因此,我们可以假定和r相同的数都是支持票,和r不同的数都是反对票。

维护两个变量:候选人和他的票数

  • 1、候选人初始化为r = 0,票数c初始化为0,遍历整个数组
  • 2、当候选人的票数为0时,更换候选人,并将票数重置为1
  • 3、当候选人的值和当前元素相同时,票数加1,否则减1
  • 4、最后维护的候选人即是答案

时间复杂度分析: O(n) ,n是数组的大小。

空间复杂度分析: 仅使用了两个变量,故需要 O(1) 的额外空间。

c++代码

 class Solution {
 public:
     int majorityElement(vector<int>& nums) {
         int r = 0, c = 0;
         for(int x : nums){
             if(c == 0) r = x, c = 1;
             else if(r == x) c++;
             else c--;
         }
         return r;
     }
 };