算法

1.

算法复杂度

时间 > 空间 >系数

归并排序
//找中间,递归排序左边,递归排序右边,合并两边 O(N*log(N))
public static void f(int[] arr,int L,int R){
    if(L==R)
    {
        return arr[L];
    }
    int mid = L + (R-L)>>1;
    f(arr,L,mid);
    f(arr,mid+1,R);
    merge(arr,L,mid,R);
}
public static void merge(int[] arr,int L,int mid,int L){
    int[] help = new int[R-L+1];
    int i = 0;
    int p1 = L;
    int p2 = mid+1;
    while(p1<=mid&&p2<=R)
    {
        help[i++] = arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<=mid)
    {
        help[i++] == arr[p1++];
    }
    while(p2<=R){
        help[i++] = arr[p2++];
    }
    for(i=0;i<help.length;i++)
    {
        arr[L+i] == help[i];
    }
}
  • 小数和:

    给一个数组arr,针对每一个arr[i],把arr[i]左边的比arr[i]小的数求和,最后得出最终的总和

    分析:对于arr[i],可以记录在arr[i]右边的数组中,有几个比arr[i]大的数,即最后的结果要加几个arr[i]

    在merge函数中,对于[left,mid],[mid+1,right],合并时,左侧小于右侧时,小和加上当前左侧数字*右侧当前数后面的数子个数 如左侧[1,2,5],右侧[3,4,6],对于左侧的1,右侧的3,左侧的1小于3,所以右侧的3会产生一个1,4会产生一个1,6会产生一个1,即小和中会有 3、4、6这3个1,即1 * 3

  • 荷兰国旗

    给一个数组arr,一个数num,把数组中小于等于num的放左边,大于num的放右边

    时间O(N),空间O(1)

    分析:arr[i] 小于num,把小于区边界(初始化为-1)下一个位置与arr[i]交换,小于区边界加一,i++

    ​ arr[i]大于num,i++;

    给一个数组arr,一个数num,把数组中小于num的放左边,大于num的放右边,等于的放中间

    时间O(N),空间O(1)

    分析:arr[i] 小于num,把小于区边界(初始化为-1)下一个位置与arr[i]交换,小于区边界加一,i++

    ​ arr[i]大于num,把大于区边界(初始化为n)前一个位置与arr[i]交换,大于区边界减一,i不变

    ​ arr[i]等于num,i++

    快排:

    随机选一个数与最后一个替换,然后小于区、大于区做递归,荷兰国旗 空间O(logN)

桶排序

桶结构

大根堆:根节点为最大的完全二叉树,任意子树的根节点也是子树中最大的

保证是一个大根堆:如果当前节点大于父节点,交换两者,直到小于父节点或到根节点

heapinsert : 添加新节点时,如果大于父节点,交换两者,直到小于父节点或到根节点

swap:返回最大值并移除:移除第一个节点,把最后一个节点移到最前面,

heapify : 把左右孩子中的最大值与该节点比较,如果父节点小,交换,直到大于左右孩子最大值或没有左右孩子

PriorityQueue<Integer> heap = new PriorityQueue<>();
heap.add(8);
heap.poll();

冒泡、插补、二分

二分法
//>>右移 除 <<左移 乘
mid = L +((R-L)>>1);//mid = ( L + R )/2;
x = 2*x+1;//x = (x<<1)|1 
  • 有序数组

    • 查找某个数
    • 大于等于某个数的最左侧位置
    • 小于等于某个数的最右侧位置
  • 局部最小值

    局部最小

    无序数组,相邻不等,返回任意一局部最小

    1. 判断0处和n处是否为局部最小
    2. 若两侧都不是局部最小,则中间必有局部最小
    3. 来到中间,判断是否为局部最小,如果是,直接返回,如果不是,则定有一边比它小,选择比它小的一边,有构成了2的条件,继续找中间

有某个原则可以确定舍弃一边时就可用二分

异或运算

异或运算:二进制无进位相加

0^N = N N^N =0

有交换律和结合律

//交换两个数,a和b的内存不是一个区域,否则会出错,都变成0
a = a ^ b;
b = a ^ b;
a = a ^ b;

常用:

//取数的二进制最右侧的一个1
N&((~N)+1)
//数组中有两个数出现了奇数次,寻找这两个数
  1. 数组全部异或的 eor
  2. eor 取最右端的1作为rightOne
  3. 数组与rightOne 相与,得到该位是1或不是1两种情况

2.二叉树

class Node{
    int val;
    Node left;
    Node right;
}
递归序(重要)

每一个节点都会经历三次

先序、中序、后序都是递归序加工的结果

先序——>递归 栈实现 头 左 右

  1. 弹出时打印
  2. 有右,压入
  3. 有左,压入

头 右 左

  1. 弹出时打印
  2. 有左,压入
  3. 有右,压入

后序:上面反过来即是后序 (左 右 头 )

中序:

  1. 当前节点下全部左边界压入栈
  2. 1结束后,弹出一个 打印 压入右节点,进行1

后序遍历:(炫技版)

定义两个变量 h c

h表示刚打印的节点 初始化为head;

c表示当前节点 初始化为null

  1. 如果h是c的左孩子,表示左孩子已处理完,把c的右孩子入栈
  2. 如果h是c的右节点,表示右节点已处理完,打印c,更新 h ,把c出栈
  3. 如果都不是 表示c还没到最后的左子树,把c的左子树入栈
栈先入头节点;
c表示当前节点 初始化为null;
h表示刚打印的一个点 初始化为head;
while(栈不空){
    更新c为栈顶节点;
    如果h是c的左孩子,表示左孩子已处理完,把c的右孩子入栈;
    如果h是c的右节点,表示右节点已处理完,打印c,更新 h ,把c出栈;
    如果都不是 表示c还没到最后的左子树,把c的左子树入栈;
}
层序遍历

队列

树的宽度

//队列
queue.add();
Node a = queue.poll();
//根节点入队
//当前节点h
//设置两个变量 当前层、当前层节点数
//设置一个map记录节点所在层数
设置根节点所在层数为1
while(队不空){
    h=出队;
    如果有左子树,设置左子节点所在的层数,入队;
    如果有右子树,设置右子节点所在的层数,入队;
    当前节点所在层数==当前层数:
        当前层节点数+1;
    否则:
        //当前层已结束,现在是下一层
        更新宽度;
        当前层加1;
        当前层节点数=1;
}
//最后一层时没有更新宽度
更新宽度
  1. 不用Map

    //设置当前层最右节点,下一层最右节点
    

序列化和反序列化

  • 序列化

    子节点为空:加入数组,不加队列

    不为空:既加入数组,又加入队列

  • 反序列化

3.二叉树进阶

  1. 打印一棵树的打印函数

  2. 有父节点,找后继 后继:中序遍历下,一个节点的下一个遍历对象称为其后继

    • 有右子树,右子树的最左子节点即为后继
    • 没有右子树,向上寻找父节点,判断是否为其左子节点,如果是,该父节点即是后继,如果不是,继续向上,直到找到是其父节点的左孩子的节点
  3. 纸条的折痕

    递归模拟二叉树

    // i 当前层数 N 共折N次 down是凹还是凸 true时表示凹
    public void f(int i,int N,boolean down){
        if(i>N){
            return ;
        }
        //相当于用递归模拟了二叉树的建立
        f(i+1,N,true);
        System.out.println(down?"凹":"凸");
        f(i+1,N,false);
    }
    
  4. 搜索二叉树

    //判断是否为搜索二叉树
    public static int preValue = Integer.MIN_VALUE;
    public static boolean f(Node root){
        if(root==null)
        {
            return true;
        }
        //要先判断左子树,因为在判断左子树时会更新preValue
        boolean b = f(root.left);
        if(!b)
        {
            return false;
        }
        if(root.val<= preValue)
        {
            return false;
        }
        else
        {
            preValue = root.Val;
        }
        return f(root.right);
        
    }
    

4.二叉树的递归套路

1.给定一个二叉树的头节点,返回这个二叉树是不是平衡树

平衡树:左子树的高度与右子树的高度不超过1 <=1

2.返回最大距离

5.暴力递归到动态规划

1。尝试

子串:连续的 两层循环

子序列:按顺序的

寻找全部的子序列

不重复:设置HashSet

全排列:str[0……i-1]都已决定好,str[i……]都有机会来到i位置,与之交换,这是一个循环过程

不重复的全排列:假设只有26个字符组成字符串,设置一个Hash<Character ,boolean>,

1.从左往右的尝试模型

背包问题

两种递归方式

  • 以 int[] w,int[] v,int index,int already,int bag为参数
  • 以 int[] w,int[] v,int index,int rest 为参数
2.范围上尝试模型
  • 纸牌

    //先手在l到r范围上获取的最大分数
    public static int f(int[] arr,int l,int r){
       if(l==r)
       {
           return arr[l];
       }
       //先手有两种挑法,返回最大的那种
       return Math.max(arr[l]+s(arr,l+1,r),arr[r]+s(arr,l,r-1));
    }
    public static int s(int[] arr,int l,int r)
    {
       if(l==r)
       {
           return 0;
       }
       else{
           //
           return Math.min(f(arr,l+1,r),f(arr,l,r-1));
       }
    }
    
    //返回最终获胜者的分数
    return Math.max(f(arr,0,r),s(arr,0,r));
    
3.N皇后
public static int f(int[] record,int index,int N){
    if(index == N)
    {
        return 1;
    }
    int res = 0;
    for(int j=0;j < N;j++)
    {
        if(isValid(record,index,j))
        {//判断是否共列、共斜线  |a-b| ==|c-d|
            record[i] = j;
            res = res+f(record,index+1,N);
        }
    }
}

优化常数项(位运算)

以八皇后为例

//列限制:colLim : 00000...0000| 00000000

//左斜线限制: leftLim  00000...0000| 00000000

//右斜线限制:rightLim 00000...0000| 00000000
//最右边有8个0,表示可以放皇后
limit = 000...000 | 11111111;
public static int f(int limit,int colLim,int leftLim,int rightLim){
    if(colLim==limit)
    {
        return 1;
    }
    //去除8个0之前的0的干扰
    //后八位中为0不可放,为1可放
    //某行可选的位置就是lim后8位中为1的位置
    lim = limit & (~(colLim | leftLim | rightLim));
    //依次提取最右侧的1  a & ( ~a + 1 )
    while(lim!=0){//如果中间某行不能放皇后,lim会等于0
        mostRIght = lim & (~lim + 1);
        mostRIght = lim & (~lim + 1);
        res = res + f(limit,colLim|mostRight,(leftLim|mostRight)<<1,(rightLim|mostRight)>>>1)
    }
    return res;
}

4.记忆化搜索

在递归时加上dp数组,到达边界情况时先记录dp,

暴力递归不一定转成动态规划,但动态规划一定来自于暴力递归

机器人走路

先做暴力递归,不用想转移方程,要找出某种选择下返回之间的联系,据此找出转移方程

5.记忆化搜索与动态规划的差别

递归函数中没有枚举行为(即没有循环)时记忆化搜索与动态规划没区别

而在有枚举行为时,动态规划可以省去记忆化搜索的枚举行为

6单链表

快慢指针:快指针一步走两个,慢指针一步走一个

环的入口处

快指针一次走两步,慢指针一次走一步,肯定会在环上相遇,相遇后,慢指针继续一次走一步,快指针回到头节点,变成一次走一步,再相遇时必定在环入口处

寻找两链表相交处

  • 两链表都没有环

    1. 遍历两链表,记录长度A、B与最后一个节点

    2. 两个最后一个节点 不是同一个 返回,无交点

      是同一个,长的那个先走 |A-B|步,然后两个肯定会同时走到交点处 (a.next == b.next)

  • 一个有环,一个无环:必不相交

  • 都有环 环入口处分别为loop1 、loop2

    1. loop1==loop2,与无环相同

    2. loop1 != loop2 : 遍历loop1环的过程中没有遇到 loop2,不相交

      ​ 遇到了,返回loop1或loop2都可以

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值