Datawhale & 阿里云天池LeetCode基础训练营
Task1: 数组
课后习题
1. 删除有序数组中的重复项(Easy)
题意: 给一个有序的数组,其中有若干数是重复出现的,如 [1,1,2,2,2,4,] 删除重复项后为 [1,2,4]。
现要求你将重复项删除,并返回新的数组长度。
示例 1:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
STL 做法
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
auto last = unique(nums.begin(),nums.end());
nums.erase(last,nums.end());
return nums.size();
}
};
我们每次将重复的个数统计起来,然后前移cnt个位置,就能保证前面的值都是唯一的了
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int len = nums.size(), cnt = 0;
for(int i = 1; i < nums.size(); i++){
if(nums[i] == nums[i-1]){
cnt++;
len--;
}else{
nums[i-cnt] = nums[i];
}
}
return len;
}
};
2. 移除元素(Easy)
**题意:**给你一个数组和值val,从数组中移除所有等于val的元素
示例 1:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
和上题一样的思路,这里不再赘述
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int cnt = 0;
for(int i = 0; i < nums.size(); i++){
if(val == nums[i]){
len--;
cnt++;
continue;
}
nums[i-cnt] = nums[i];
}
return len;
}
};
3. 三数之和(Medium)
**题意:**给定一数组,按任意顺序返回三数之和为0 的三元组,其中元素各不重复,且返回的三元组也不重复
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
这道题的难点即在于三元组判重,通过观察可发现,若要三元组不重复,其实就是使三元组的前两个元素不重复出现,
熟悉STL的朋友可以使用set<pair<int,int>> 来判重,然后第三个元素我们通过二分来查找即可
不过Set判重的复杂度也不低,可以优化一下做法:
考虑到我们将数组排序后,元素皆有序,那么问题就变成了有序数组中寻找所有不重复的二元组问题
显然,二元组的第一个元素不枚举重复的,(否则必然导致重复答案)第二个元素也是(同理)。
我实在是想不通为什么我的二分比他们枚举还慢这么多,如果有知道的大佬劳请指点一二
STL做法
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
if(n < 3) return {};
sort(nums.begin(),nums.end());
set<pair<int,int>> st;
vector<vector<int>> ans;
for(int i = 0; i < n; i++){
if(nums[i] > 0) break;
for(int j = i+1; j < n; j++){
int l = j + 1, r = n - 1;
while(l < r){
int mid = (l+r)/2;
if(nums[i] + nums[j] + nums[mid] == 0){
l = mid;
break;
}
else if(nums[i] + nums[j] + nums[mid] > 0) r = mid-1;
else l = mid+1;
}
if(l < n && nums[i] + nums[j] + nums[l] == 0){
if(st.find(make_pair(nums[i],nums[j])) == st.end()){
ans.push_back(vector{nums[i],nums[j],nums[l]});
st.insert(make_pair(nums[i],nums[j]));
}
}
}
}
return ans;
}
};
优化版:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
if(n < 3) return {};
sort(nums.begin(),nums.end());
vector<vector<int>> ans;
for(int i = 0; i < n; i++){
if(i > 0 && nums[i] == nums[i-1]) continue;//第一个位置判重
for(int j = i+1; j < n; j++){
if(j > i+1 && nums[j] == nums[j-1]) continue;//第二个位置判重
int l = j+1, r = n-1;
while(l < r){
int mid = (l+r)/2;
if(nums[i] + nums[j] + nums[mid] == 0){
l = mid;
break;
}
else if(nums[i] + nums[j] + nums[mid] > 0) r = mid-1;
else l = mid+1;
}
if(l < n && nums[i] + nums[j] + nums[l] == 0){
ans.push_back(vector{nums[i],nums[j],nums[l]});
}
}
}
return ans;
}
};
Task2:链表
课后习题
1.合并两个有序链表(Easy)
**题意:**给你两个有序的链表,合成一个有序的新链表
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
思路:递归返回或者迭代合并再返回
递归版:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(!list1) return list2;
else if(!list2) return list1;
else if(list1->val < list2->val){
list1->next = mergeTwoLists(list1->next,list2);
return list1;
}else{
list2->next = mergeTwoLists(list1,list2->next);
return list2;
}
}
};
2.相交链表(Easy)
**题意:**给你两个链表,其中有一个节点往后他们都相同,求该节点
示例 2:
[外链图片转存中…(img-sz5pZ068-1645112683925)]
如该处的相交处为2这个节点
思路:直接开哈希表记录结点即可
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *t = headA;
unordered_set< ListNode* > st;
while(t){
st.insert(t);
t = t->next;
}
t = headB;
while(t){
if(st.count(t)) return t;
t = t->next;
}
return nullptr;
}
};
3.删除排序链表中的重复元素 II(Medium)
**题意:**将链表中所有出现1次以上的值的节点删除并返回新链表
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
思路:前后指针(或者叫左右指针), 首先用一个指针指向前指针,然后比较前后指针的值是否相等,是则一直移动后指针,直到不相等。然后更新左右指针,继续下次循环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return nullptr;
ListNode *prev = new ListNode(-123);
ListNode *cur = prev;
while (head) {
int cnt = 0;
while (head->next && head->val == head->next->val) {
cnt++;
head = head->next;
} // 循环结束后,head 的值与下个节点的值不一样
if (cnt == 0) {
cur->next = new ListNode(head->val);
cur = cur->next;
}
head = head->next;
}
return prev->next;
}
};
Task3:栈
课后习题
1.最小栈(Easy)
**题意:**设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
push(x)
—— 将元素 x 推入栈中。pop()
—— 删除栈顶的元素。top()
—— 获取栈顶元素。getMin()
—— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
利用另外一个栈来保存前缀最小值即可,这里便不模拟栈了,直接使用stl内的栈
class MinStack {
public:
stack<int> stk,min_stk;
MinStack() {
min_stk.push(INT_MAX);
}
void push(int val) {
stk.push(val);
min_stk.push(min( min_stk.top(), val));
}
void pop() {
stk.pop();
min_stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min_stk.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
2.比较含退格的字符串(Easy)
题意:‘ # ’ 字符表示退格操作,给你两个字符串,问退格操作后两字符串是否相等
示例 1:
输入:s = "ab#c", t = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 “”。
遇到字母就入栈,遇到# 就退栈,最后一次比较每个出栈的字符即可
class Solution {
public:
bool backspaceCompare(string s, string t) {
stack<int> s_stk, t_stk;
for(auto &c : s){
if(c != '#'){
s_stk.push(c);
}else{
if(!s_stk.empty()){
s_stk.pop();
}
}
}
for(auto &c : t){
if(c != '#'){
t_stk.push(c);
}else{
if(!t_stk.empty()){
t_stk.pop();
}
}
}
while(!s_stk.empty() && !t_stk.empty()){
if(s_stk.top() != t_stk.top()) return false;
s_stk.pop();
t_stk.pop();
}
return s_stk.empty() && t_stk.empty();
}
};
3.基本计算器 II(Medium)
**题意:**给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
由于这里的运算比较简单,我们将其转化为一个加法栈,最后求和即可。
我们来看看每组数字前面的运算符
加号 就直接把数字入栈
减号 就把相反数入栈
乘除 就把该组数字与栈顶的数字相运算即可。
最后对加法栈进行求和就是答案。
class Solution {
public:
int calculate(string s) {
stack<int> stk;
int val = 0;
char pre_op = '+';
int len = s.length();
for (int i = 0; i < len; ++i) {
if (isdigit(s[i])) {
val = val * 10 + (s[i] - '0');
}
if (!isdigit(s[i]) && s[i] != ' ' || i == len - 1) {//防止除0和读完数字的情况
switch (pre_op) {
case '+':{
stk.push(val);
break;
}
case '-':{
stk.push(-val);
break;
}
case '*':{
stk.top() *= val;
break;
}
case '/':{
stk.top() /= val;
break;
}
}
pre_op = s[i];
val = 0;
}
}
int ans = 0;
while(!stk.empty()){
ans += stk.top();
stk.pop();
}
return ans;
}
};
Task4:字符串
课后习题
1.验证回文字符串 Ⅱ
给定一个非空字符串 s
,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: s = "aba"
输出: true
示例 2:
输入: s = "abca"
输出: true
解释: 你可以删除c字符。
又由于回问串的特殊性质(回文串的子串也是回文串),所以我们可以从字符串两端开始判断,若此时两端字符不同,则分别判断去掉左右字符后是否回文即可。
class Solution {
public:
bool check(int l, int r, string s){
int i = l, j = r;
while (i < j){
if (s[i] != s[j]) return false;
i ++, j --;
}
return true;
}
bool validPalindrome(string s) {
int n = s.length();
int l = 0, r = n - 1;
while (l < r){
if (s[l] == s[r]) l ++, r --;
else{
l ++;
if (check(l, r, s)) return true;
l--, r--;
return check(l, r, s);
}
}
return true;
}
};
2.Excel表列名称
整数 columnNumber
,返回它在 Excel 表中相对应的列名称。
例如:
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28
...
容易看出是一个10进制转26进制的问题,直接处理即可(按权展开)
class Solution {
public:
string convertToTitle(int columnNumber) {
string ans;
while (columnNumber > 0) {
int a0 = (columnNumber - 1) % 26 + 1;
ans += a0 - 1 + 'A';
columnNumber = (columnNumber - a0) / 26;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
3.字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
记录括号前的数字值,然后循环将括号内的数字复制即可
class Solution {
public:
string decodeString(string s) {
stack<char> sta;
for(auto ch : s){
if(ch != ']')sta.push(ch);
else{
stack<char> tmp;
while(!sta.empty() && sta.top() != '['){tmp.push(sta.top());sta.pop();}
sta.pop();
//找到它前面的倍数
string number;
while(!sta.empty() && sta.top() >= '0' && sta.top() <= '9'){
number += sta.top();sta.pop();
}
reverse(number.begin(),number.end());
int nth = stoi(number);
for(int i = 0; i < nth;++i){
auto current = tmp;
while(!current.empty()){sta.push(current.top());current.pop();}
}
}
}
string res;
while(!sta.empty()){
res += sta.top();sta.pop();
}
reverse(res.begin(),res.end());
return res;
}
};
Task5:树
课后习题
1.二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
思路:
深搜,每次在左右子树之间选择一个深度最低的值。
若根为空,则最小深度为0,否则初始化根的深度为INT_MAX 然后再进行深搜
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int dfs(TreeNode *root){
if(!root) return INT_MAX;
if(root->left == nullptr && root->right == nullptr) return 1;
return min(dfs(root->left), dfs(root->right)) + 1;
}
int minDepth(TreeNode* root) {
if(!root) return 0;
return dfs(root);
}
};
2.路径总和
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
思路:
深搜:若当前节点为叶子节点且值等于targetsum,返回true
否则,每次往下搜一层,目标值减去当前节点的值
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root) return false;
if(!root->left && !root->right
&& targetSum == root->val) return true;
return hasPathSum(root->left, targetSum - root->val)
|| hasPathSum(root->right, targetSum - root->val);
}
};
3.二叉搜索树迭代器
题意:类似于设计一个java的迭代器,只不过这里的迭代器是按中序遍历的顺序来迭代
思路,由于二叉树可以通过链式结构转为线性结构,所以我们按中序遍历将值存到数组里面
然后便可轻松判断hasNext()和next()
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class BSTIterator {
public:
BSTIterator(TreeNode* root) {
idx = 0;
to_arr(root, v);
}
void to_arr(TreeNode *root, vector<int> &vv){
if(!root) return ;
to_arr(root->left, vv);
vv.push_back(root->val);
to_arr(root->right, vv);
}
int next() {
return v[idx++];
}
bool hasNext() {
return idx != v.size();
}
vector<int> v;
int idx;
};
/**
* Your BSTIterator object will be instantiated and called as such:
* BSTIterator* obj = new BSTIterator(root);
* int param_1 = obj->next();
* bool param_2 = obj->hasNext();
*/
Task6:位运算
课后习题:
1.2 的幂
给你一个整数 n
,请你判断该整数是否是 2 的幂次方。如果是,返回 true
;否则,返回 false
。
如果存在一个整数 x
使得 n == 2x
,则认为 n
是 2 的幂次方。
思路:
由于计算机采用2进制,所以我们可以根据二进制的特点来进行计算
因为2的幂在2进制的表示下为 10000… ,所以判断一个数是否是2的幂只需判断其除了高位外是否都是0即可
因为 1 & 0 = 1 ,所以我们可以通过 n &(n-1) == 0 即可判断是否低位都是0
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (n & (n-1)) == 0;
}
};
2.只出现一次的数字 II
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。
思路:
由于每个数字只出现三次,所以我们可以通过枚举每一位二进制值,若该位的二进制值和为3的整数倍,则说明答案值该位为0,否则为1,数据范围在 int32位以下,所以我们枚举32位即可。
或运算的性质就类似于 按位不进位加法 , 因为 1 | 0 = 1 ,1 | 1 = 1,所以我们对答案求和时就可用或来按位求和
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans = 0;
for(int i = 0; i < 32; i++){
int val = 0;
for(auto &c : nums){
val += (c >> i) & 1;
}
if(val % 3){
ans |= 1 << i;
}
}
return ans;
}
};
3.子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
思路:
这里我们使用状态枚举即可,由于二进制的性质,每一位上不是0就是1,所以我们可以通过枚举 1 - 2^n的值来选择每个值,0一般被用于表示不选,1则表示选。
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> res;
for(int i = 0; i < (1 << n); i++){
int t = i;
vector<int> temp;
for(int j = 0; j < n; j++){
if((t >> j) & 1){
temp.push_back(nums[j]);
}
}
res.push_back(temp);
}
return res;
}
};
Task7: 双指针
课后习题
1.删除排序链表中的重复元素
题意:将排序链表中重复出现的多余的元素删除,使其只出现一次
若下一个节点不空且相同,则一直更新结点即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return nullptr;
ListNode *prev = new ListNode(-123);
ListNode *cur = prev;
while (head) {
while (head->next && head->val == head->next->val) {
head = head->next;
}
cur->next = new ListNode(head->val);
cur = cur->next;
head = head->next;
}
return prev->next;
}
};
2.环形链表
题意:判断该链表是否有环
set判重即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
set< ListNode* > st;
ListNode *temp = head;
while(temp){
if(st.count(temp)) return true;
st.insert(temp);
temp = temp->next;
}
return false;
}
};
3.排序链表
题意:将给定链表排序并返回排序后的链表
将所有值保存并排序再回到链表全部替换顺序即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
priority_queue<int,vector<int>,greater<int>> que;
auto temp = head;
while(temp){
que.push(temp->val);
temp = temp->next;
}
temp = head;
while(temp){
temp->val = que.top();
que.pop();
temp = temp->next;
}
return head;
}
};
Task8:搜索
课后习题
1.对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
示例 1:
由于是镜像对称,所以我们递归时拿根节点的左子树和右子树比较即可
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool search(TreeNode *l, TreeNode *r){
if(!l && !r) return true;
if(!l || !r || l->val != r->val) return false;
return search(l->left, r->right) && search(l->right, r->left);
}
bool isSymmetric(TreeNode* root) {
return search(root, root);
}
};
2.二叉树的中序遍历
给定一个二叉树的根节点 root
,返回它的 中序 遍历。
中序遍历即:左根右
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void search(TreeNode *root){
if( root == NULL ) return ;
search(root->left);
res.push_back(root->val);
search(root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
res.clear();
search(root);
return res;
}
vector<int> res;
};
3.二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root
,和一个整数 k
,请你设计一个算法查找其中第 k
个最小元素(从 1 开始计数)。
由于二叉树搜索树的特征就是左子节点值小于根节点,右子节点值大于根节点,那么我们按照中序遍历即可知道第k小的元素
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void inorder(TreeNode *root, vector<int>& ans){
if(!root) return ;
inorder(root->left, ans);
ans.emplace_back(root->val);
inorder(root->right, ans);
}
int kthSmallest(TreeNode* root, int k) {
vector<int> ans;
inorder(root, ans);
return ans[k-1];
}
};
Task9:排序
课后习题
1.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
求完平方再排序
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res;
for(auto n : nums){
res.emplace_back(n*n);
}
sort(begin(res),end(res));
return res;
}
};
2.数组的相对排序
给你两个数组,arr1
和 arr2
,arr2
中的元素各不相同,arr2
中的每个元素都出现在 arr1
中。
对 arr1
中的元素进行排序,使 arr1
中项的相对顺序和 arr2
中的相对顺序相同。未在 arr2
中出现过的元素需要按照升序放在 arr1
的末尾。
用pair记录相对位置,重新排序即可,未出现的数字权值可以设置成索引下标值
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
map<int,int> mp;
for(int i = 0; i < arr2.size(); i++){
mp[arr2[i]] = i;
}
sort(begin(arr1), end(arr1),[&](int a, int b){
if(mp.find(a) == mp.end() && mp.find(b) == mp.end()){
return a < b;
}
if(mp.find(a) == mp.end()) return false;
if(mp.find(b) == mp.end()) return true;
return mp[a] < mp[b];
});
vector<int> ans = arr1;
return ans;
}
};
3. 对链表进行插入排序
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
偷个懒,排序完再存回链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void insert_sort(vector<int> &v){
int i, j, temp;
for(i = 1; i < v.size(); i++){
temp = v[i];
for(j = i -1; j >= 0 && v[j] > temp; j--){
v[j+1] = v[j];
}
v[j+1] = temp;
}
}
ListNode* insertionSortList(ListNode* head) {
ListNode *t = head;
vector<int> v;
while(t){
v.push_back(t->val);
t = t->next;
}
insert_sort(v);
reverse(begin(v), end(v));
t = head;
while(t){
t->val = v.back();
v.pop_back();
t = t->next;
}
return head;
}
};
课后习题
1.买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
记录第i天前的最低价格,然后在第i天卖出的价格,取最大值就是答案
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n <= 1) return 0;
int min_price = INT_MAX, max_ans = 0;
for(int i = 0; i < n; i++){
if(prices[i] < min_price) min_price = prices[i];
else max_ans = max(max_ans, prices[i] - min_price);
}
return max_ans;
}
};
2.买卖股票的最佳时机 II
给定一个数组 prices
,其中 prices[i]
表示股票第 i
天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
每天有两个状态,持有一股或零股,每天又是一个状态,所以我们我们设
d p [ i ] [ 0 ] 为 第 i 天 持 有 0 股 股 票 所 能 获 得 的 最 大 利 润 dp[i][0] 为第i天持有0股 股票所能获得的最大利润 dp[i][0]为第i天持有0股股票所能获得的最大利润
d p [ i ] [ 1 ] 为 第 i 天 持 有 1 股 股 票 所 能 获 得 的 最 大 利 润 dp[i][1] 为第i天持有1股 股票所能获得的最大利润 dp[i][1]为第i天持有1股股票所能获得的最大利润
那么 d p [ i ] [ 0 ] 就 可 能 由 d p [ i − 1 ] [ 0 ] 和 d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] 转 移 而 来 dp[i][0] 就可能由dp[i-1][0] 和 dp[i-1][1] + prices[i] 转移而来 dp[i][0]就可能由dp[i−1][0]和dp[i−1][1]+prices[i]转移而来
(这里表示我们前一天的股票还是0股,和 前一天有1股,但今天卖掉了,所以是 + prices[i])
d p [ i ] [ 1 ] 则 是 由 d p [ i − 1 ] [ 1 ] 和 d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] dp[i][1] 则是由 dp[i-1][1] 和 dp[i-1][0] - prices[i] dp[i][1]则是由dp[i−1][1]和dp[i−1][0]−prices[i] 转移而来
初始状态: d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][0] = 0, dp[0][1] = -prices[0] dp[0][0]=0,dp[0][1]=−prices[0]
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n,vector<int>(2));
dp[0][0] = 0, dp[0][1] = -prices[0];
for(int i = 1; i < n; i++){
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
}
return dp[n-1][0];
}
};
3.最长回文子序列给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
因为回文序列具有和dp一样的子结构特性,所以我们可以设 d p [ i ] [ j ] 为 第 i 个 位 置 到 第 j 个 位 置 的 最 长 回 文 子 序 列 长 度 dp[i][j] 为 第i个位置到第j个位置的最长回文子序列长度 dp[i][j]为第i个位置到第j个位置的最长回文子序列长度
则 d [ i ] [ i ] = 1 d[i][i] = 1 d[i][i]=1 (单个字符就是一个回文串)
那么当 s [ i ] = = s [ j ] 时 , d p [ i ] [ j ] 应 等 于 d p [ i + 1 ] [ j − 1 ] + 2 s[i] == s[j] 时, dp[i][j] 应等于 dp[i+1][j-1] + 2 s[i]==s[j]时,dp[i][j]应等于dp[i+1][j−1]+2
而当 s [ i ] ! = s [ j ] 时 , d p [ i ] [ j ] 应 等 于 m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) s[i] != s[j] 时,dp[i][j]应等于max(dp[i+1][j], dp[i][j-1]) s[i]!=s[j]时,dp[i][j]应等于max(dp[i+1][j],dp[i][j−1])
因为我们要求整个字符串的最长回文子序列,所以最后答案应该是 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n−1]
那么我们两重循环,外层从后往前计算即可
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> dp(n,vector<int>(n));
for(int i = n-1; i >= 0; i--){
dp[i][i] = 1;
for(int j = i+1; j < n; j++){
if(s[i] != s[j]){
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}else{
dp[i][j] = dp[i+1][j-1] + 2;
}
}
}
return dp[0][n-1];
}
};
Task11:分治
课后习题
1. 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
根据题意可知排序后这个元素一定是位于数组中间的,所以我们排序后取中间的哪个值即可
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
};
2.最大子数组和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
dp:由于要求子数组连续,故我们可以在子数组变小时将新的子数组和 从当前值开始求和
然后求解最大值即可
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans = nums[0], n = nums.size(), sum = 0;
for(int i = 0; i < n; i++){
sum = max(sum + nums[i], nums[i]);
ans = max(ans, sum);
}
return ans;
}
};
3.从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
因为前序的第一个节点就是根节点,故可以在中序节点中找到根节点,然后划分成左右子树部分,然后先对左子树继续进行寻根,一直把所以节点找完,然后就是我们的二叉树了
class Solution {
public:
TreeNode* recurTree(int leftin,int rightin,int leftpre,int rightpre,vector<int>&preorder, vector<int>&inorder){
if(leftpre>rightpre || leftin>rightin){return nullptr;}
TreeNode* root=new TreeNode(preorder[leftpre]);
int k;
for(int i=leftin;i<inorder.size();i++){
if(inorder[i]==root->val){k=i;break;}
}
root->left=recurTree(leftin,k-1,leftpre+1,leftpre+(k-leftin),preorder,inorder);
root->right=recurTree(k+1,rightin,leftpre+(k-leftin)+1,rightpre,preorder,inorder);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return recurTree(0,inorder.size()-1,0,preorder.size()-1,preorder,inorder);
}
};
Task12:哈希表
课后习题
1.存在重复元素 II
给你一个整数数组 nums
和一个整数 k
,判断数组中是否存在两个 不同的索引 i
和 j
,满足 nums[i] == nums[j]
且 abs(i - j) <= k
。如果存在,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [1,2,3,1], k = 3
输出:true
哈希表记录每个值的位置,然后判断即可
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> ump;
for(int i = 0; i < nums.size(); i++){
int n = nums[i];
if(ump.count(n) && i - ump[n] <= k) return true;
ump[n] = i;
}
return false;
}
};
2.宝石与石头
给你一个字符串 jewels
代表石头中宝石的类型,另有一个字符串 stones
代表你拥有的石头。 stones
中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。
字母区分大小写,因此 "a"
和 "A"
是不同类型的石头。
示例 1:
输入:jewels = "aA", stones = "aAAbbbb"
输出:3
哈希表记录哪些石头是宝石,然后判断石头是否是宝石之一即可
class Solution {
public:
int numJewelsInStones(string jewels, string stones) {
unordered_map<char,int> ump;
int ans = 0;
for(auto &c : jewels){
ump[c] = 1;
}
for(auto &c : stones){
if(ump.count(c)){
ans++;
}
}
return ans;
}
};
3.子域名访问计数
网站域名 "discuss.leetcode.com"
由多个子域名组成。顶级域名为 "com"
,二级域名为 "leetcode.com"
,最低一级为 "discuss.leetcode.com"
。当访问域名 "discuss.leetcode.com"
时,同时也会隐式访问其父域名 "leetcode.com"
以及 "com"
。
计数配对域名 是遵循 "rep d1.d2.d3"
或 "rep d1.d2"
格式的一个域名表示,其中 rep
表示访问域名的次数,d1.d2.d3
为域名本身。
- 例如,
"9001 discuss.leetcode.com"
就是一个 计数配对域名 ,表示discuss.leetcode.com
被访问了9001
次。
给你一个 计数配对域名 组成的数组 cpdomains
,解析得到输入中每个子域名对应的 计数配对域名 ,并以数组形式返回。可以按 任意顺序 返回答案。
示例 1:
输入:cpdomains = ["9001 discuss.leetcode.com"]
输出:["9001 leetcode.com","9001 discuss.leetcode.com","9001 com"]
解释:例子中仅包含一个网站域名:"discuss.leetcode.com"。
按照前文描述,子域名 "leetcode.com" 和 "com" 都会被访问,所以它们都被访问了 9001 次。
其实就是统计每个串出现了多少次,同样哈希记录求和即可,最后按任意顺序输出
tips:注意域名可能是d2.d3这种情况即可
class Solution {
public:
vector<string> subdomainVisits(vector<string>& cpdomains) {
vector<string> res;
unordered_map<string,int> ump;
for(auto &s : cpdomains){
string num, domain, d2, d3;
for(int i = 0; i < s.length(); i++){
if(s[i] == ' '){
num = s.substr(0,i);
domain = s.substr(i+1);
break;
}
}
int cnt = 1;
for(int i = 0; i < domain.length(); i++){
if(domain[i] == '.'){
if(cnt == 1){
cnt++;
d2 = domain.substr(i+1);
}else{
cnt++;
d3 = domain.substr(i+1);
break;
}
}
}
ump[string(" " + domain)] += stoi(num);
ump[string(" " + d2)] += stoi(num);
if(cnt == 3) ump[string(" " + d3)] += stoi(num);
}
for(auto &[k,v] : ump){
res.push_back(string(to_string(v) + k));
}
return res;
}
};