持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
287. 寻找重复数
思路
(二分,抽屉原理) O(nlogn)
抽屉原理: n+1 个苹果放在 n 个抽屉里,那么至少有一个抽屉中会放两个苹果。
在这个题目里,一共有 n+1 个数,每个数的取值范围是1到n,所以至少会有一个数出现两次。
然后我们采用分治的思想,将每个数的取值的区间[1, n]划分成[1, n/2]和[n/2+1, n]两个子区间,然后分别统计两个区间中数的个数。 注意这里的区间是指 数的取值范围,而不是 数组下标。
划分之后,左右两个区间里一定至少存在一个区间,区间中数的个数大于区间长度。 这个可以用反证法来说明:如果两个区间中数的个数都小于等于区间长度,那么整个区间中数的个数就小于等于n,和有n+1个数矛盾。
因此我们可以把问题划归到左右两个子区间中的一个,而且由于区间中数的个数大于区间长度,根据抽屉原理,在这个子区间中一定存在某个数出现了两次。
依次类推,每次我们可以把区间长度缩小一半,直到区间长度为1时,我们就找到了答案。
时间复杂度分析: 每次会将区间长度缩小一半,一共会缩小 O(logn) 次。每次统计两个子区间中的数时需要遍历整个数组,时间复杂度是 O(n)。所以总时间复杂度是 O(nlogn)。
c++代码
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int l = 1, r = nums.size() - 1; //取值范围
while(l < r){
int mid = (l + r) / 2;
int s = 0;
for(int x : nums) s += x >= l && x <= mid; //左区间数的个数
if(s > mid - l + 1) r = mid;
else l = mid + 1;
}
return r;
}
};
297. 二叉树的序列化与反序列化
思路
(前序遍历序列化) O(n)
- 序列化:对整个二叉树进行先序遍历的序列存起来,同时需要把每个结点的空节点使用
"#"进行标记,例如样例的顺序是1,2,#,#,3,4,#,#,5,#,# - 反序列化:对整个字符串按照
","进行分割,把所有的元素按序存到链表中(链表元素的顺序是先序序列),按先序遍历的方式拿链表的元素,每次拿第一个元素作为根结点,并删除链表中的第一个元素,然后递归到左儿子做同样的操作,递归到右儿子做同样的操作。注意:若第一个元素是"#",表示该节点是null,直接返回`null
时间复杂度分析: 每个节点仅遍历两次,故时间复杂度为 O(n)。
c++代码
// 前序遍历 DFS
class Codec {
public:
string path;
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
// if (root) dfs_s(root); 加if (root) 是错的
dfs_s(root); // 不能加 if (root),因为空树 也要编码成"#,"
return path;
}
// 序列化 dfs_s() 的返回类型 是 void
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); // 这里不能直接 dfs_s(data, 0),0是常量,应该是变量
}
// 反序列化 dfs_d() 的 返回类型是 TreeNode*, 参数 是 字符串data,和 当前 字符串下标
TreeNode* dfs_d(string& data, int& idx){ // 第1个&防止拷贝,第2个&相当于把idx作为全局变量
if (data[idx] == '#'){
idx += 2;
return NULL;
} else {
int k = idx;
while (data[idx] != ',') idx ++; // 跳出while循环时,data[idx] == ','
auto root = new TreeNode(stoi(data.substr(k, idx - k))); // stoi 和 data.substr(k, num) 用法
idx ++ ; // 从',' 往下 跳一位
root->left = dfs_d(data, idx);
root->right = dfs_d(data, idx);
return root; // return root; 不要写在下面
}
// return root;
}
};
300. 最长递增子序列
思路
(动态规划) O(n^2)
状态表示: f[i]表示以nums[i]为结尾的严格递增子序列的最大长度。
集合划分: 以nums[i]为结尾的严格递增子序列前一个数是nums[0],nums[1],,,nums[i-1]
状态计算: f[i] = max(f[i],f[j] + 1) (j<i && nums[j] < nums[i])
图示说明:
时间复杂度分析: 状态数量为O(n),状态计算为O(n),故时间复杂度为O(n^2)。
c++代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int>f(n + 1);
int res = 0;
for(int i = 0; i < n; i++){
f[i] = 1;
for(int j = 0; j < i; j++)
if(nums[i] > nums[j])
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
return res;
}
};
- ```