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

115 阅读4分钟

图片.png

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

剑指 Offer 55 - II. 平衡二叉树

思路

(递归) O(n)

二叉平衡二叉树的定义是:任意节点的左右子树的深度相差不超过1,而上一道题是求解二叉树的深度,因此我们可以在求解二叉树的深度过程中判断此树是不是平衡二叉树。

具体过程如下:

  • 1、首先递归左右子树,求出左右子树的最大深度,我们记为lhrh
  • 2、然后判断两棵子树是否是平衡的,即两棵子树的最大深度的差是否不大于1
  • 3、在递归的过程中记录每棵树的最大深度值,为左右子树的最大深度值加一,即max(lh , rh) + 1;
  • 4、具体实现细节看代码

时间复杂度分析: 每个节点仅被遍历一次,且判断的复杂度是 O(1)。所以总时间复杂度是 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 Solution {
 public:
     bool res = true;
     bool isBalanced(TreeNode* root) {
         dfs(root);
         return res;
     } 
     int dfs(TreeNode* root){   //求二叉树
         if(!root) return 0;
         int lh = dfs(root->left), rh = dfs(root->right);
         if(abs(lh - rh) > 1) res = false;  //判断
         return max(lh, rh) + 1;
     } 
 };

剑指 Offer 56 - I. 数组中数字出现的次数

思路

(位运算) O(n)

前置知识:

相同数异或为0,比如a ^ a = 0

假设这两个只出现一次的数字分别为xy,根据上述性质,我们将nums数组中的所有数都异或一遍,可以得到x ^ y。两个不同的数,其二进制表示中必然有一位是不同的,因此我们在x ^ y随便找到一个不同点,假设找到了为1的第k位。

我们可以根据第k位的不同,将序列分为两个集合,第k位为1的集合和第k位不是1的集合,其中xy分别在这两个集合,且相同的元素是在同一个集合里面,于是将其转化成了求重复数字中的单个数值的问题。

我们将第k位为1的集合的所有数异或一遍,相同数相消,剩下的就是只出现一次的数,另一个数则为x ^ y再异或刚才我们求出的数。

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

c++代码

 class Solution {
 public:
     // 相同数异或为0,a ^ a = 0
     /**
     异或得到 sum = x^y
     取 x与y中第k位为1的数
     将数分为两个集合,第k位为1的集合和第k位不是1的集合
     其中x y分别在这两个集合,且相同的元素是在同一个集合里面
     于是将其转化成了求重复数字中的单个数值的问题
     **/
     vector<int> singleNumbers(vector<int>& nums) {
         int sum = 0;
         for(int x : nums) sum ^= x; // sum = first ^ second;
         int k = 0;
         while(!(sum >> k & 1)) k++; //找到sum中为1的一位
         int first = 0;      //记录第一个数
         for(int x : nums){
             if(x >> k & 1)
                 first ^= x;
         }
         return {first, sum ^ first};
     }
 };
 ​

剑指 Offer 56 - II. 数组中数、字出现的次数 II

思路

(位运算) O(n)

如果一个数字出现3次,它的二进制每一位也出现的3次。如果把所有的出现3次的数字的二进制表示的每一位都分别加起来,那么每一位都能被3整除。 我们把数组中所有的数字的二进制表示的每一位都加起来。如果某一位能被3整除,那么这一位对只出现一次的那个数的这一肯定为0。如果某一位不能被3整除,那么只出现一次的那个数字的该位置一定为1

因此,考虑二进制每一位上出现 01 的次数,如果出现 1 的次数为 3k + 1,则证明答案中这一位是 1

具体过程:

  • 1、定义bit,从0枚举到31,相当于考虑数字的每一位。
  • 2、遍历数组nums,统计所有数字bit位出现1的个数,记录到cnt中。
  • 3、如果bit1出现次数不是3的倍数,则说明答案在第i位是1,否则说明答案的bit位是0

时间复杂度分析: 仅遍历 32 次数组,故时间复杂度为 O(n)。

c++代码

 class Solution {
 public:
     int singleNumber(vector<int>& nums) {
         int n = nums.size();
         int res = 0;
         for(int bit = 0; bit < 32; bit++){
             int cnt = 0; //统计所有数字bit位上1的个数
             for(int i = 0; i < nums.size(); i++){
                 if(nums[i] >> bit & 1) cnt++;
             }
             if(cnt % 3 != 0) res += 1 << bit;
         }
         return res;
     }
 };