❤ 作者主页:Java技术一点通的博客
❀ 个人介绍:大家好,我是Java技术一点通!( ̄▽ ̄)~*
❀ 微信公众号:Java技术一点通
记得点赞、收藏、评论⭐️⭐️⭐️
认真学习!!!
排序数组中的搜索问题,首先想到 二分法 解决。
假设我们缺失的元素数值为 x x x,那么对于 x x x 左边的元素(若有)必然是完整且连续的,也就是其必然满足 n u m s [ i ] = i nums[i] = i nums[i]=i,而其右边的元素(若有)必然由于 x 的缺失,导致必然不满足 n u m s [ i ] = i nums[i] = i nums[i]=i,因此在以缺失元素为分割点的数轴上,具有二段性,我们可以使用 「二分」 来找该分割点。
同时由于缺失元素可能是 [ 0 , n − 1 ] [0, n - 1] [0,n−1] 范围内的最大值,因此我们需要 二分结束后 再 c h e c k check check 一次,若不满足,说明缺失的元素值为 n − 1 n - 1 n−1。
class Solution {
public:
int missingNumber(vector& nums) {
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r + 1) / 2;
if (nums[mid] == mid) l = mid;
else r = mid - 1;
}
if (nums[r] == r) r ++;
return r;
}
};
基于此性质: 二叉搜索树的中序遍历为 递增序列 。
算法流程:
dfs(root->right)
;dfs(root->left)
。/**
* 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:
int res;
int kthLargest(TreeNode* root, int k) {
dfs(root, k);
return res;
}
void dfs(TreeNode* root, int &k) {
if (root == NULL) return;
dfs(root->right, k);
if (--k == 0) {
res = root->val;
return;
}
dfs(root->left, k);
}
};
关键点: 此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度 与 右子树的深度 中的 最大值 +1 。
算法流程:
maxDepth(root->left)
;maxDepth(root->right)
;max(maxDepth(root->left), maxDepth(root->right)) + 1
。/**
* 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:
int maxDepth(TreeNode* root) {
if (!root) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
平衡二叉树的定义是:二叉树的每个节点的左右子树的高度差的绝对值不超过 1,则二叉树是平衡二叉树。
根据定义,一棵二叉树是平衡二叉树,当且仅当其所有子树也都是平衡二叉树,因此可以使用递归的方式判断二叉树是不是平衡二叉树。
定义函数 h e i g h t height height, 用于计算二叉树中的任意一个节点 p p p 的高度:
h e i g h t ( p ) = { 0 , p 是空节点 m a x ( h e i g h t ( p − > l e f t ) , h e i g h t ( p − > r i g h t ) ) + 1 , p 是非空节点 height(p)= \begin{cases} 0, p是空节点\\ max(height(p->left),height(p->right))+1, p是非空节点 \end{cases} height(p)={0,p是空节点max(height(p−>left),height(p−>right))+1,p是非空节点
有了计算节点高度的函数,即可判断二叉树是否平衡。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差是否不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。
/**
* 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 isBalanced(TreeNode* root) {
if (!root) return true;
else return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
int height(TreeNode* root) {
if (!root) return 0;
else return max(height(root->left), height(root->right)) + 1;
}
};
设整型数组 n u m s nums nums 中出现一次的数字为 x x x ,出现两次的数字为 a , a , b , b , . . . a, a, b, b, ... a,a,b,b,... ,即: n u m s = [ a , a , b , b , . . . , x ] nums=[a,a,b,b,...,x] nums=[a,a,b,b,...,x]
异或运算有个重要的性质,两个相同数字异或为 0 ,即对于任意整数 a a a 有 a ⊕ a = 0 a \oplus a = 0 a⊕a=0 。因此,若将 n u m s nums nums 中所有数字执行异或运算,留下的结果则为 出现一次的数字 x x x ,即:
a ⊕ a ⊕ b ⊕ b . . . ⊕ x a \oplus a \oplus b \oplus b ... \oplus x a⊕a⊕b⊕b...⊕x
= 0 ⊕ 0 ⊕ . . . ⊕ x 0 \oplus 0 \oplus ... \oplus x 0⊕0⊕...⊕x
= x x x
本题难点:数组 n u m s nums nums 有 两个 只出现一次的数字,因此无法通过异或直接得到这两个数字。
设两个只出现一次的数字为 x , y x , y x,y ,由于 x ≠ y x \ne y x=y ,则 x x x 和 y y y 二进制至少有一位不同(即分别为 0 和 1 ),根据此位可以将 n u m s nums nums 拆分为分别包含 x x x 和 y y y 的两个子数组。
易知两子数组都满足 「除一个数字之外,其他数字都出现了两次」。因此,仿照以上简化问题的思路,分别对两子数组遍历执行异或操作,即可得到两个只出现一次的数字 x x x, y y y 。
算法流程:
遍历 n u m s nums nums 执行异或:
循环左移计算 m m m :
根据异或运算定义,若整数 x ⊕ y x \oplus y x⊕y 某二进制位为 1 ,则 x x x 和 y y y 的此二进制位一定不同。换言之,找到 x ⊕ y x \oplus y x⊕y 某为 1 的二进制位,即可将数组 n u m s nums nums 拆分为上述的两个子数组。根据与运算特点,可知对于任意整数 a a a 有:
因此,初始化一个辅助变量 m = 1 m = 1 m=1 ,通过与运算从右向左循环判断,可 获取整数 x ⊕ y x \oplus y x⊕y 首位 1 ,记录于 m m m 中;
拆分 n u m s nums nums 为两个子数组:
分别遍历两个子数组执行异或:
返回值: 返回只出现一次的数字 x , y x, y x,y 即可。
class Solution {
public:
vector singleNumbers(vector& nums) {
int x = 0, y = 0, n = 0, m = 1;
for (int num : nums) n ^= num;
while ((n & m) == 0) m <<= 1;
for (int num : nums) {
if (num & m) x ^= num;
else y ^= num;
}
return vector {x, y};
}
};
状态转移:
本题与前一题思路类似,前一题中,其他数都出现了两次,因此需要的状态转移方式是,如果出现两个 1 就抵消为 0 ,用一个变量和异或运算即可实现,而本题是需要 1 出现三次时才会抵消,因此有三种状态,即 1 出现的次数为 3 k , 3 k + 1 , 3 k + 2 3k, 3k + 1, 3k + 2 3k,3k+1,3k+2 次。
逐个位来看,要设计一个两位的状态转移,出现三个 1 时,循环抵消,出现 0 时不变,一个变量只能记录两种状态,因此要用两个变量来记录状态,用 o n e one one 和 t w o two two 两个变量来记录1出现次数。
00 表示 1 出现 3 k 3k 3k 次,01表示 1 出现 3 k + 1 3k + 1 3k+1 次,10 表示 1 出现 3 k + 2 3k + 2 3k+2 次。
真值表
two one x two one
0 0 1 0 1
0 1 1 1 0
1 0 1 0 0
0 0 0 0 0
0 1 0 0 1
1 0 0 1 0
先看 o n e one one 的状态转移方程:
one = (~one & ~two & x) | (one & ~two & ~x)
= ~two & ((~one & x) | (one & ~x))
= ~two & (one ^ x)
同理,再用转移后的 o n e one one 来求 t w o two two 的状态转移方程。
这里, o n e one one 为当且仅当 1 出现次数为 3 k + 1 3k + 1 3k+1, t o w tow tow 为当且仅当 1 出现次数为 3 k + 2 3k + 2 3k+2。
因此如果题目改为,有一个数出现了两次,则返回 t w o two two即可。
class Solution {
public:
int findNumberAppearingOnce(vector& nums) {
int one=0,two=0;
for(auto x:nums)
{
one=(one^x)&~two;
two=(two^x)&~one;
}
return one;
}
};
创建一个哈希表,对于每一个 x x x,我们首先查询哈希表中是否存在 t a r g e t − x target - x target−x,如果有返回 [ x , t a r g e t − x ] [x, target - x] [x,target−x]。如果没有,将 x x x 插入到哈希表中。
class Solution {
public:
vector twoSum(vector& nums, int target) {
unordered_set hash;
for (auto x : nums)
{
if (hash.count(target - x)) return {target - x, x};
hash.insert(x);
}
return {};
}
};
滑动窗口(双指针):
设连续正整数序列的左边界 i i i 和右边界 j j j ,则可构建滑动窗口从左向右滑动。循环中,每轮判断滑动窗口内元素和与目标值 t a r g e t target target 的大小关系,若相等则记录结果,若大于 t a r g e t target target 则移动左边界 i i i (以减小窗口内的元素和),若小于 t a r g e t target target 则移动右边界 j j j (以增大窗口内的元素和)。
算法流程:
初始化: 左边界 i = 1 i = 1 i=1,有边界 j = 2 j = 2 j=2,元素和 s = 3 s = 3 s=3,结果列表 r e s res res;
循环:当 i ≥ j i \geq j i≥j时跳出;
返回值: 返回结果列表 r e s res res。
class Solution {
public:
vector> findContinuousSequence(int target) {
int i = 1, j = 2, s = 3;
vector> res;
while (i < j) {
if (s == target) {
vector ans;
for (int k = i; k <= j; k ++ )
ans.push_back(k);
res.push_back(ans);
}
if (s >= target) {
s -= i;
i ++;
} else {
j ++;
s += j;
}
}
return res;
}
};
解题方法:双指针
算法流程:
class Solution {
public:
string reverseWords(string s) {
string res;
int n = s.size();
if (!n) return res;
int right = n - 1;
while (right >= 0) {
//从后往前寻找第一个字符
while (right >= 0 && s[right] == ' ') right --;
if (right < 0) break;
// 从后往前寻找第一个空格
int left = right;
while (left >= 0 && s[left] != ' ') left --;
// 添加单词到结果集
res += s.substr(left + 1, right - left);
res += ' ';
right = left;
}
// 去除最后一个字符空格
if (!res.empty()) res.pop_back();
return res;
}
};
算法流程:
class Solution {
public:
string reverseLeftWords(string s, int n) {
string res;
for (int i = n; i < s.size(); i ++ ) {
res += s[i];
}
for (int i = 0; i < n; i ++ ) {
res += s[i];
}
return res;
}
};
创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。