牛客和leetcode有很多重复
语言时而c++ 时而go
目录
子序列问题
最长递增子序列II
输出arr的最长递增子序列,如果多个,输出字典序最小的。
- 二分+dp
二分找到对应位置,maxLen记录当前位置i结尾的最长子序列值
后续找最小字典序,从后向前寻找即可。
vector<int> LIS(vector<int>& arr) {
// write code here
if(arr.size()<1) return {};
vector<int>res; //保留临时结果,单非最终结果
vector<int>maxLen; //存放以i处数字结尾的最长子序列长度
res.push_back(arr[0]);
maxLen.push_back(1);
for (int i=1;i<arr.size();i++)
{
if(arr[i]>res.back())//大于则放入res即可
{
res.push_back(arr[i]);
maxLen.push_back(res.size()); //i处长度增加
}else{
//找到大于等于val的元素位置放入
//不手写二分可使用 int pos = lower_bound(res.begin(),res.end(),arr[i])-res.begin();
int pos = binary(res,0,res.size()-1,arr[i]);
res[pos] = arr[i];
maxLen.push_back(pos+1); //坐标+1为长度
}
}
//字典序最小的
for(int i=arr.size()-1,j = res.size();j>0;i--)
{
if(maxLen[i]==j)//长度为j res位置坐标为j-1
{
res[--j] = arr[i];
}
}
return res;
}
//二分寻找大于等于target位置
int binary(vector<int> res,int low,int high,int target)
{
int mid;
while(low<=high)
{
mid = low+(high-low)/2;
if(res[mid]>=target)
{
if(mid==0||res[mid-1]<target)
{
return mid;
}
else{
high = mid-1;
}
}else{
low = mid+1;
}
}
return low;
}
最长递增子序列I
求最长递增子序列长度
上面会了,此题秒解,返回res长度即可 ,maxLen不需要
- 二分
int lengthOfLIS(vector<int>& arr) {
if(arr.size()<1) return {};
vector<int>res; //保留临时结果,单非最终结果
res.push_back(arr[0]);
for (int i=1;i<arr.size();i++)
{
if(arr[i]>res.back())//大于则放入res即可
{
res.push_back(arr[i]);
}else{
//找到大于等于val的元素位置放入
//不手写二分可使用 int pos = lower_bound(res.begin(),res.end(),arr[i])-res.begin();
int pos = binary(res,0,res.size()-1,arr[i]);
res[pos] = arr[i];
}
}
return res.size();
}
//二分寻找大于等于target位置
int binary(vector<int> res,int low,int high,int target)
{
int mid;
while(low<=high)
{
mid = low+(high-low)/2;
if(res[mid]>=target)
{
if(mid==0||res[mid-1]<target)
{
return mid;
}
else{
high = mid-1;
}
}else{
low = mid+1;
}
}
return low;
}
最长递增子序列个数
leetcode673
求最长递增子序列个数
- 双循环 时间O(n^2)
dp 为i位置结尾最大子序列(LIS)长度
count 为i位置最大子序列组合数
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
if(n<=0) return 0;
vector<int>dp(n,1); //i位置结尾的最大子序列
vector<int>count(n,1); //i结尾最大子序列的组合个数
for(int i=1;i<n;i++)
for (int j = 0;j<i;j++)
{
if(nums[i]>nums[j])
{
if(dp[j]+1>dp[i])//第一次遍历到
{
dp[i] = dp[j]+1;
count[i] = count[j];
}
else if(dp[j]+1==dp[i])//第二次遍历
{
count[i]+=count[j];
}
}
}
int tmp = 0;
int res = 0;
for(int i=0;i<n;i++)
{
tmp = max(tmp,dp[i]);
}
for(int i=0;i<n;i++)
{
if(tmp==dp[i])
res+=count[i];
}
return res;
}
数组最长连续子序列
无序数组反最长连续子序列长度
[100,4,200,1,3,2] 返回4
- 排序计算
复杂度应该是排序的O(nlogn)
排序,后比前面大1,count++,全局记录最大值
int MLS(vector<int>& arr) {
// write code here
if(arr.empty()) return 0;
int res =0;
int count=1;
//sort即可
FastThree(arr,0,arr.size()-1);
//sort(arr.begin(),arr.end());
for(int i=0;i<arr.size()-1;i++)
{
if(arr[i+1]-arr[i]==1)
count++;
else if(arr[i+1]==arr[i])
continue;
else
count=1;
res = max(res,count);
}
return res;
}
//快排写着玩的
void Fast(vector<int>&arr,int low,int high){
if(low>high)return ;
int start = low;
int end = high;
int tmp = arr[low];
while(start<end)
{
while(start<end&&arr[end]>=tmp) end--;
arr[start] = arr[end];
while(start<end&&arr[start]<=tmp) start++;
arr[end] = arr[start];
}
arr[start] = tmp;
Fast(arr, low, start-1);
Fast(arr,start+1,high);
}
//三路快排熟悉一下,因为有重复元素 注意边界值
void FastThree(vector<int>&arr,int low ,int high)
{
if(low>=high)return ;
int tmp = arr[low];
int i=low;
int j=low;
int k = high;
while(i<=k)
{
if (arr[i]<tmp) swap(arr[i++], arr[j++]);
else if(arr[i]>tmp) swap(arr[i],arr[k--]);
else i++;
}
FastThree(arr,low,j);
FastThree(arr,k+1,high);
}
- set去重排序
时空O(n)
int MLS(vector<int>& arr) {
if(arr.empty())return 0;
int res =0;
set<int>hash;
for(int i=0;i<arr.size();i++)
hash.insert(arr[i]);
set<int>::iterator it;
int num = 0;
for(it = hash.begin();it!=hash.end();it++)
{
//包含比当前小1,则计算过,跳过
if(hash.count(*it-1))continue;
int end = *it; //记录值
while(hash.count(end+1)) //有比当前+1的,一直找到最后
end++;
res = max(res,end-*it+1);//保留最大值
}
return res;
}
- 并查集
待完善
树相关
恢复二叉搜索树
- 法一
中序遍历保留每个节点,排序后重新赋值 空间O(n)
var res []int
var list [] *TreeNode
func recoverTree(root *TreeNode) {
//记得初始化
res = make([]int,0)
list = make([]*TreeNode,0)
dfs(root)
sort.Ints(res)
for i:=0;i<len(res);i++{
list[i].Val = res[i]
}
}
//中序遍历
func dfs (root *TreeNode) {
if root==nil{
return
}
dfs(root.Left)
res = append(res,root.Val)
list = append(list,root)
dfs(root.Right)
}
- 法二 不用全部排序,找到前面和后面的交换位置 空间仍然是O(N)
- 法三
Morris遍历 空间O(1)
判断二叉搜索树,平衡树
- 二叉搜索树
中序为顺序,小于上一个不符合
//c++
long pre = LONG_MIN;
bool isValidBST(TreeNode* root) {
if(root==NULL)
return true;
if(!isValidBST(root->left))//左子数非真
return false;
if(root->val<=pre)//中序,当前要大于等于之前
return false;
pre =root->val;//记录上一个
return isValidBST(root->right);
}
- 平衡树
bool flag = true;
bool IsBalanced_Solution(TreeNode* pRoot) {
dfs(pRoot);
return flag;
}
int dfs(TreeNode*root)
{
if(!root)
return 0;
int left = dfs(root->left);
int right = dfs(root->right);
if(abs(left-right)>1)
{
flag = false;
}
return right>left?right+1:left+1;
}
判断镜像
bool isSymmetric(TreeNode* root) {
if(root==NULL)
return true;
return equal(root->left, root->right);
}
bool equal(TreeNode*left,TreeNode*right)
{
if(left==NULL&&right==NULL)
return true;
if(left==NULL||right==NULL)
return false;
if(left->val!=right->val)
return false;
//左子树右==右子树左,左子树左==右子树右
return equal(left->left,right->right)&&equal(left->right,right->left);
}
序列化二叉树
每个value以!结尾,空用#表示
牛客和leetcode有略微不同,牛客是char*需要和string进行转换方便写题
char* Serialize(TreeNode *root) {
string res;
queue<TreeNode*>que; //队列放置
que.push(root);
while(!que.empty())
{
TreeNode*tmp = que.front();
que.pop();
if(tmp==NULL) //NULL跳过
{
res.push_back('#');
res.push_back('!');
continue;
}
res+=to_string(tmp->val);
res.push_back('!');
que.push(tmp->left);
que.push(tmp->right);
}
res.pop_back();//去除最后一个!
char *str = new char[res.length()+1]; //长度+1
strcpy(str, res.c_str());
return str;
}
TreeNode* Deserialize(char *str) {
if(str==NULL)return NULL;
string ret(str);
if(ret.size()==0||ret[0]=='#')
return NULL;
vector<TreeNode*>res;
int i=0;
while(i<ret.size())
{
string tmp = "";
//!为分割
while(i<ret.size()&&ret[i]!='!')
{
tmp.push_back(ret[i]);
i++;
}
if(tmp=="#")
{
TreeNode*node = NULL;
res.push_back(node);
}
else{
TreeNode*node = new TreeNode(NULL);
node->val = stoi(tmp);
res.push_back(node);
}
i++;
}
//转换为二叉树形式
int j = 1;
for(int i=0;j<res.size();i++)
{
if(!res[i])
continue;
if(i<res.size())
{
res[i]->left = res[j];
j++;
}
if(i<res.size())
{
res[i]->right =res[j];
j++;
}
}
return res[0];
}
前中,中后构建二叉树
重点是对于边界值的判断
- 已知前序中序
func buildTree(preorder []int, inorder []int) *TreeNode {
n:=len(preorder)
if n==0{
return nil
}
return dfs(preorder,inorder,0,n-1,0,n-1)
}
func dfs(preorder []int, inorder []int,preleft,preright ,inoleft,inoright int)*TreeNode{
if preleft>preright||inoleft>inoright{
return nil
}
pos:= 0 //中序位置
for i:=inoleft;i<=inoright;i++{
if inorder[i]==preorder[preleft]{
pos = i
break
}
}
//左边长度
length:=pos-inoleft
root:=new(TreeNode)
root.Val = preorder[preleft]
root.Left = dfs(preorder,inorder,preleft+1,preleft+length,inoleft,pos-1)
root.Right = dfs(preorder,inorder,preleft+1+length,preright,pos+1,inoright)
return root
}
- 已知中序后续
func buildTree(inorder []int, postorder []int) *TreeNode {
n:=len(inorder)
if n==0{
return nil
}
return dfs(inorder,postorder,0,n-1,0,n-1)
}
func dfs(inorder []int,postorder []int,inst,inen,post,poen int) *TreeNode {
if inst>inen||post>poen{
return nil
}
root:=new(TreeNode)
root.Val = postorder[poen]
//寻找中序位置
pos:=0
for i:=inst;i<=inen;i++{
if inorder[i]==postorder[poen]{
pos = i
break
}
}
//右半部分长度
length:=inen-pos
root.Left = dfs(inorder,postorder,inst,pos-1,post,poen-length-1)
root.Right = dfs(inorder,postorder,pos+1,inen,poen-length,poen-1)
return root
}
前中后序非递归遍历
开始不要放入
- 前序
func preorderTraversal(root *TreeNode) []int {
var res []int
rt:=root
var stack []*TreeNode
//开始不装入
for len(stack)!=0||rt!=nil{
for rt!=nil{
res = append(res,rt.Val)
stack = append(stack,rt)
rt = rt.Left
}
if len(stack)!=0{
tmp:=stack[len(stack)-1]
stack = stack[:len(stack)-1]
rt = tmp.Right
}
}
return res
}
- 中序
和前序类似,在下面装入
func inorderTraversal(root *TreeNode) []int {
res:=make([]int,0)
stack:=make([]*TreeNode,0)
rt:=root
for len(stack)!=0||rt!=nil{
for rt!=nil{
stack = append(stack,rt)
rt = rt.Left
}
tmp:=stack[len(stack)-1]
stack = stack[:len(stack)-1]
//记录数据放在这里
res = append(res,tmp.Val)
rt = tmp.Right
}
return res
}
- 后序
map记录是否访问,或者判断右子树是否访问过
func postorderTraversal(root *TreeNode) []int {
res := make([]int,0)
//记录访问过的节点
hash:=make(map[*TreeNode]bool)
stack:=make([]*TreeNode,0)
rt:=root
for len(stack)!=0||rt!=nil{
for rt!=nil{
stack = append(stack,rt)
rt = rt.Left
}
//取出栈顶元素
tmp:=stack[len(stack)-1]
if tmp!=nil&&hash[tmp]{
//用过则加入结果,去除栈顶元素
stack = stack[:len(stack)-1]
res = append(res,tmp.Val)
}else{
//没用过,记录下,向右
hash[tmp] = true
rt = tmp.Right
}
}
return res
}
之字型遍历二叉树
- 递归,巧妙,每一层一个数组保存数据
var res [][]int //全局变量
func zigzagLevelOrder(root *TreeNode) [][]int {
//dfs
res = make([][]int,0)
if root==nil{
return res
}
dfs(root,0)
return res
}
func dfs(root *TreeNode,deepth int){
if root==nil{
return
}
if deepth>=len(res){ //进入1层,增加相应的[]int
res = append(res,[]int{})
}
//0开始偶数鞥正常顺序
if deepth&1==0{
res[deepth] = append(res[deepth],root.Val)
}
if deepth&1==1{ //奇数层逆序
res[deepth] = append([]int{root.Val},res[deepth]...)
}
dfs(root.Left,deepth+1)
dfs(root.Right,deepth+1)
}
- 迭代,两个队列,第二次从右向左
func zigzagLevelOrder(root *TreeNode) [][]int {
//双队列
if root==nil{
return [][]int{}
}
res:=make([][]int,0)
stack1:=make([]*TreeNode,0)
stack2:=make([]*TreeNode,0)
stack1 = append(stack1,root)
for len(stack1)!=0||len(stack2)!=0{
ret:=make([]int,0)
size:=len(stack1)
for i:=0;i<size;i++{
tmp :=stack1[0]
stack1 = stack1[1:]
ret = append(ret,tmp.Val)
if tmp.Left!=nil{
stack2 = append(stack2,tmp.Left)
}
if tmp.Right!=nil{
stack2 = append(stack2,tmp.Right)
}
}
if len(ret)!=0{
res = append(res,ret)
}
//右向左
size = len(stack2)
ret = make([]int,0)
for i:=size-1;i>=0;i--{
tmp:=stack2[len(stack2)-1]
stack2 = stack2[:len(stack2)-1]
ret = append(ret,tmp.Val)
if tmp.Right!=nil{
stack1 = append([]*TreeNode{tmp.Right},stack1...)
}
if tmp.Left!=nil{
stack1 = append([]*TreeNode{tmp.Left},stack1...)
}
}
if len(ret)!=0{
res = append(res,ret)
}
}
return res
}
N叉树层序遍历
bfs放入队列,每次队列长度循环,每个节点的子节点都放入队列
func levelOrder(root *Node) [][]int {
var res [][]int
if root==nil{
return res
}
que:=make([]*Node,0)
que = append(que,root)
for len(que)!=0{
lenght:=len(que)
tmp:=make([]int,0)
for i:=0;i<lenght;i++{
//每次取第一个,因为下面已经去除第一个
tmp = append(tmp,que[0].Val)
node:=que[0]
que = que[1:]
for j:=0;j<len(node.Children);j++{
if node.Children[j]!=nil{
que = append(que,node.Children[j])
}
}
}
res = append(res,tmp)
}
return res
}
数组相关(滑动窗口,二分)
lt56 合并区间
二维数组合并区间,先排序再合并
func merge(intervals [][]int) [][]int {
//排序合并
if len(intervals)<=1{
return intervals
}
fast(intervals,0,len(intervals)-1) //手撕快排
var res [][]int
//前一个尾部大于后一个头,则合并,合并的尾为两个尾部最大 [1,3],[2,6】 是[1,6]
for i:=0;i<len(intervals);i++{
if len(res)==0||intervals[i][0]>res[len(res)-1][1]{
res = append(res,intervals[i])
}else{
res[len(res)-1][1] = max(res[len(res)-1][1],intervals[i][1])
}
}
return res
}
func max(x,y int)int {
if x>y{
return x
}else{
return y
}
}
func fast(arr [][]int,low,high int) {
start:=low
end:=high
if start>end{
return
}
tmp:=arr[low] //赋值在判断之后
for start<end{
for start<end&&arr[end][0]>=tmp[0]{
end--
}
arr[start] = arr[end]
for start<end&&arr[start][0]<=tmp[0]{
start++
}
arr[end] = arr[start]
}
arr[start] = tmp
fast(arr,low,start-1)
fast(arr,start+1,high)
}
滑动窗口最大值
leetcode 239 单调栈
func maxSlidingWindow(nums []int, k int) []int {
res:=make([]int,0)
que:=make([]int,0)//存储索引值
for i:=0;i<len(nums);i++{
if len(que)!=0{
//超出窗口范围则去除
if i-que[0]>=k{
que = que[1:]
}
for len(que)!=0&&nums[i]>=nums[que[len(que)-1]]{
que = que[:len(que)-1]
}
}
que = append(que,i)
if i+1>=k{
res = append(res,nums[que[0]])
}
}
return res
}
子数组最大累加和 牛客
连续累加最大和
全局记录,和<0则归零
int maxsumofSubarray(vector<int>& arr) {
if (arr.empty())
return 0;
int res = INT_MIN;
int count = 0;
for(int i=0;i<int(arr.size());i++)
{
count+=arr[i];
res = max(res,count);
count = count<0?0:count; //和小于0则归零
}
return res;
}
字符串最长无重复子串长度
[2,2,3,4,3] 为3
set存储从左到右
int maxLength(vector<int>& arr) {
int n = arr.size();
if(n==0)return 0;
int res = 0;
set<int>hash; //保存,重复则从前向后去除元素,直到没有重复
int l = 0;
int r = 0;
while(r<n)
{
if(!hash.count(arr[r]))
{
hash.insert(arr[r]);
r++;
}else{
hash.erase(arr[l]);
l++;
}
res = max(res,int(hash.size()));
}
return res;
}
le11 盛水最多容器
左右双指针,左右最小的向中间移动,移动时更新最大值
func maxArea(height []int) int {
if len(height)==0{
return 0
}
res:=0
l:=0
r:=len(height)-1
for l<r{
tmp:=(r-l)*min(height[l],height[r])
res = max(res,tmp)
if height[l]>height[r]{
r--
}else{
l++
}
}
return res
}
func min(x,y int)int {
if x>y{
return y
}else{
return x
}
}
func max(x,y int)int {
if x>y{
return x
}else{
return y
}
}
接雨水
多种方法解决
- 双指针
其他
合并两个排序链表(递归)
- 递归
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL) return l2;
if(l2==NULL) return l1;
if(l1->val<l2->val)
{
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}else{
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}
k个排序链表合并
leetcode 23
- 分治法
每次合并两个,将结果放入队列尾部,直到队列只剩下一个
面试遇到说这个不是最优解的,我感觉优先队列和这个复杂度差不多
func mergeKLists(lists []*ListNode) *ListNode {
if len(lists)==0{
return nil
}
//合并放尾部,去除头部两个,直到队列剩余为1
for len(lists)>1{
lists = append(lists,merge(lists[0],lists[1]))
lists = lists[2:]
}
return lists[0]
}
//合并两个链表
func merge(one *ListNode ,two *ListNode)*ListNode {
dummy:=new(ListNode)
cur:=dummy
for one!=nil&&two!=nil{
if one.Val<two.Val{
cur.Next = one
one = one.Next
}else{
cur.Next = two
two = two.Next
}
cur = cur.Next
}
if one!=nil{
cur.Next = one
}else{
cur.Next = two
}
return dummy.Next
}
- 优先队列
手动实现优先队列 我吐了
虽然标准库有优先队列,还是手动踩坑锻炼下
调试差点吐了,还是参考下标准库里heap的实现方式吧
func mergeKLists(lists []*ListNode) *ListNode {
if len(lists)==0{
return nil
}
var heap []*ListNode
for i:=0;i<len(lists);i++{
if lists[i]!=nil{
heap = push(heap,lists[i])
}
}
sink(heap,0)//形成堆
dummy:=new(ListNode)
cur:=dummy
for len(heap)!=0{
var tmp *ListNode
heap,tmp = pop(heap)//取出堆顶
cur.Next = tmp
cur = cur.Next
if tmp.Next!=nil{ //非空放入下一个
heap = push(heap,tmp.Next)
}
}
return dummy.Next
}
//节点下沉
func sink(arr []*ListNode,start int) {
length:=len(arr)-1
for{
next:=start*2+1
if next>length{
return
}
if next+1<=length&&arr[next+1].Val<arr[next].Val{
next++
}
if arr[start].Val<arr[next].Val{
break
}
arr[start],arr[next] = arr[next],arr[start]
start = next
}
}
//节点上升只需要找父节点
func swim(arr []*ListNode,start int){
for {
parent:=(start-1)/2
if start==parent||arr[parent].Val<=arr[start].Val{
break
}
arr[parent],arr[start] = arr[start],arr[parent]
start = parent
}
}
//insert
func push(arr []*ListNode,root *ListNode)[]*ListNode {
//上升节点
arr = append(arr,root)
swim(arr,len(arr)-1)
return arr
}
//取出优先高的,结尾放头部,减少容量,下沉节点
func pop(arr []*ListNode)([]*ListNode,*ListNode) {
tmp:=arr[0]
arr[0] = arr[len(arr)-1]
arr = arr[:len(arr)-1]
sink(arr,0)
return arr,tmp
}
//数组创建链表,方便debug
func Creat(arr []int)*ListNode {
root:=new(ListNode)
cur:=root
for i:=0;i<len(arr);i++{
tmp:=new(ListNode)
tmp.Val = arr[i]
cur.Next = tmp
cur = cur.Next
}
return root.Next
}
牛客 合并两个有序数组
从后向前,把B放入A中,不开辟新的空间
void merge(int A[], int m, int B[], int n) {
int i = m-1,j = n-1,len = m+n-1;
while(i>=0||j>=0)
{
if((i>=0&&A[i]>=B[j])||j<0)
A[len--]=A[i--];
else{
A[len--]=B[j--];
}
}
}
lt面试题08.06 汉诺塔问题
n个数字,a移动到c
递归问题 时O(2^n) 空O(1)
func hanota(A []int, B []int, C []int) []int {
n:=len(A)
move(n,&A,&B,&C)
return C
}
func move(n int,a *[]int, b *[]int,c *[]int) {
if n<=0{
return
}
//a的n-1个经过c移动到b
move(n-1,a,c,b)
//a上层移动到c
*c = append(*c,(*a)[len(*a)-1])
*a = (*a)[:len(*a)-1]
//b上的n-1个经过a移动到c
move(n-1,b,a,c)
}
leetcode69 x的平方根
二分法和牛顿法
- 二分法
func mySqrt(x int) int {
mid:=0
l:=0
r:=x
for l<r{
//(l+r+1)这是为了防止当l = r - 1时,出现死循环的情况
mid = (l+r+1)>>1
if mid*mid<=x{
l = mid
}else{
r = mid-1
}
}
return r
}
- 牛顿法
主要是迭代公式x = (x + n/x) / 2
func mySqrt(x int) int {
res:=x
for res*res>x{
res = (res+x/res)/2
}
return res
}