刷爆leetcode

文章目录

  • 算法
    • 评估复杂度
    • 对数器
      • Math.random()
      • 对数器的使用
        • 生成长度随机最大值随机的随机数组
    • 位运算
      • 异或运算
        • 不申请额外变量交换两个数
        • 整型数提取最右侧的1
        • 找到出现奇数次的数并打印
        • 统计二进制中的1的个数
      • 32位int打印
      • 求相反数
    • 二分法
      • 有序数组中寻数
      • 有序数组中找到≥某个数的最左侧位置
      • 有序数组中找到≤某个数的最右侧位置
      • 无序且相邻不相等的数组中找到局部最小数
    • 递归
    • 排序算法
      • 选择排序
      • 冒泡排序
      • 插入排序
      • 归并排序
        • 求数组小和
        • 求数组中的所有降序对
      • 快速排序
        • partition
      • 桶排序
        • 计数排序
        • 基数排序
      • 排序算法稳定性
      • 堆结构
      • 堆排序
      • 堆相关题
    • 比较器
    • 前缀树
    • 链表
      • 快慢指针
      • 哈希表
    • 二叉树
      • 后序遍历
      • 层次遍历
      • 统计二叉树宽度
      • 二叉树的序列化和反序列化
      • 打印树
      • 含父指针的二叉树
      • 折纸凹痕问题
      • 递归套路
    • 打表技巧
    • 矩阵处理技巧
      • zigzag打印矩阵
      • 转圈打印矩阵
      • 原地旋转正方形矩阵
  • 数据结构
    • 动态数组ArrayList
    • 哈希表
    • 有序表
    • 设计数据结构记录数组中的累加和
    • 链表
      • 单向链表
        • 节点的结构定义
      • 双向链表
        • 节点的结构定义
      • 反转
      • 删除给定值
      • K个节点的组内逆序
      • 链表相加
      • 合并有序链表
      • 双向链表实现
      • 数组实现
      • 实现getMin(),时间复杂度为O(1)
    • 队列
      • 单链表实现队列和栈
      • 双向链表实现双端队列
      • 双向链表实现队列
      • 数组实现循环队列
    • 栈实现队列
    • 队列实现栈

算法

评估复杂度

常数时间操作:执行时间固定的操作,与数据量无关
刷爆leetcode_第1张图片

  1. 时间复杂度:按最坏计算
  2. 额外空间复杂度:功能无关,代码相关的空间
  3. 常数项时间
    最优解:满足时间复杂度的前提下空间复杂度尽量低

对数器

刷爆leetcode_第2张图片

Math.random()

Math.random()方法等概率返回[0,1)上的随机数

  1. 返回[0,100)上的整数:(int)Math.random()*100
  2. 返回[0,100]上的整数:(int)Math.random()*101
  3. 返回[1,100]上的整数:(int)Math.random()*100+1
  4. 将[0,x)上的数出现的概率从x调整为x2:Math.max(Math.random(), Math.random()),不能用Math.min,否则概率会调整为1-(1-x)2
    函数f(x)在[1,5]上等概率返回一个随机整数,如何将这个范围调整为[1,7]:
  5. 将[1,5]等概率转换为{0,1}等概率生成函数h(x):若生成1/2,则返回0,生成4/5,则返回1,生成3,则重新再生成
  6. 三次独立调用h(x)生成三个位数a,b,c,组成一个三位二进制数,在[000,111]上等概率生成——g(x)
  7. g(x)若生成的结果为7,则重新调用g(x)——生成的结果在[0,6]上等概率
  8. 最终g(x)+1的生成结果就是在[1,7]上等概率生成
    函数f(x)在{0,1}上不等概率生成,如何调整为等概率:
    两次调用f(x),若两次结果相同,则重新生成,否则,返回第一次生成的结果即可
    (f(x)生成0的概率为p,生成1的概率为1-p)

对数器的使用

生成长度随机最大值随机的随机数组
public static int[] randomArray(int maxLen, int maxValue){
    int len = (int)(Math.random()*maxLen);
    int[] arr = new int[len];
    for (int i = 0; i < len; i++) {
        arr[i] = (int)(Math.random()*maxValue);
    }
    return arr;
}

验证上千或上万次:

  1. 随机生成数组
  2. 拷贝数组(不能直接用=赋值,否则内存地址相同,表示同一个对象)
  3. 两种方法分别处理两个数组
  4. 判断两个数组处理后的结果是否相同(同样不能用==直接判断),若不同,可以打印自定义方法处理前后的数组内容,查看问题所在

位运算

左移乘2,右移除2

异或运算

异或:相同为0,不同为1(无进位相加
同或:相同为1,不同为0
例:0011^1011=1000
性质:

  1. 0^N=N
  2. N^N=0
  3. 异或运算满足交换律和结合律
不申请额外变量交换两个数

前提:两个数内存不同,否则就是自己异或自己

a = a^b;//a=a^b b=b
b = a^b;//a=a^b b=a^b^b=a
a = a^b;//a=a^b^a=b b=a
整型数提取最右侧的1

返回一个除最右侧的1,其余都是0的数:

N = N&((~N)+1)//取反+1后,除最右边的1之外,其余位置都相反或是0
找到出现奇数次的数并打印

数组中只有一种数出现奇数次,其他数都是偶数次:
所有数字都异或得到的结果就是唯一奇数次的数字
数组中有2种数出现奇数次,其他数都是偶数次:

  1. 所有数字异或,得到eor=a^b,且eor≠0,则eor的最右侧1必然是a和b一个是1另一个不是1的位置
  2. rightone=eor&((~eor)+1),取eor的最右侧1
  3. eor2,只异或数组中满足rightone位置上是1的数(或只异或rightone位置上不是1的数),偶数次的数被筛去——a
  4. eor2^eor——b
统计二进制中的1的个数
int count=0;
whilt(N!=0){
	int rightone = N&((~N)+1);//提取最右侧1
	count++;
	N^=rightone;//抹去最右侧1
}
return count;

32位int打印

最高位为符号位,其余31位正数是本身,负数的实际值是位值取反+1
负数取反+1是为了统一位运算操作,正负数加法均用相同的加法操作

for(int i=31;i>=0;i--){
	System.out.print( (num & (1<<i) ) == 0 ? 0 : 1);
}

求相反数

取反+1
Integer.MIN_VALUE和0取反+1后还是它自己

a = ~b+1

二分法

有序数组中寻数

public static int binaryFind(int[] arr, int value){
    if(arr.length==0||arr==null)
        return -1;
    int L=0,R=arr.length-1;
    while(L<=R){
        int mid = (L+R)/2;
        if(arr[mid]==value)
            return mid;
        else if(arr[mid]>value)
            R = mid-1;
        else
            L=mid+1;
    }
    return -1;
}

有序数组中找到≥某个数的最左侧位置

public static int find(int[] arr, int value){
    if(arr.length==0||arr==null)
        return -1;
    int L=0,R=arr.length-1,ans=-1;
    while(L<=R){
        int mid = (L+R)/2;
        if(arr[mid]>=value){
            ans = mid;
            R = mid-1;
        }else {
            L = mid+1;
        }
    }
    return ans;
}

有序数组中找到≤某个数的最右侧位置

与上题大致相同

无序且相邻不相等的数组中找到局部最小数

**注意:**不一定是有序数组才能用二分法,只要能够排除另半边的情况就行
==mid的计算:==如果希望mid-1和mid+1都在数组长度范围内,使用mid=(L+R)/2会导致在取mid+1或mid-1时可能会出现越界,因此while退出循环的条件为L 例:{3,2,3,2,3},mid=2->mid=0,则此时取mid-1时出现越界

public static int findLocalMin(int[] arr){
    if(arr==null||arr.length==0)
        return -1;
    int N = arr.length;
    if(N==1||arr[1]>arr[0])
        return 0;
    if(arr[N-1]<arr[N-2])
        return N-1;
    int L=0,R=N-1;
    while(L<R-1){
        int mid=(L+R)/2;
        if(arr[mid]<arr[mid-1]&&arr[mid]<arr[mid+1])
            return mid;
        else if(arr[mid]>arr[mid-1]){
            R=mid-1;
        }else {
            L=mid+1;
        }
    }    
    return arr[L]<arr[R]?L:R;
}

递归

刷爆leetcode_第3张图片

排序算法

选择排序

public static void selectsort(int[] arr){
    if(arr==null||arr.length<2)//先考虑边界条件
            return ;
    int N = arr.length;
    for(int i=0;i<N;++i){
        int minindex=i;//记录最小值下标即可
        for(int j=i+1;j<N;++j){
            minindex = arr[j]<arr[minindex]?j:minindex;
        }
        int tmp = arr[minindex];//交换过程可以另外写成一个函数
        arr[minindex] = arr[i];
        arr[i] = tmp;
    }
}

冒泡排序

public static void bubblesort(int[] arr){
    if(arr==null||arr.length<2)
        return ;
    int N=arr.length;
    for (int i=N-1;i>=0;--i){
        for(int j=0;j<i-1;++j){
            if(arr[j]>arr[j+1])
                swap(arr, j, j+1);
        }
    }
}
public static void swap(int[] arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

插入排序

public static void insertsort(int[] arr){
    if(arr==null||arr.length<2)
            return ;
    int N=arr.length;
    for(int i=1;i<N;++i){
        int idx = i;
        while((idx-1>=0)&&arr[idx-1]>arr[idx]){
            swap(arr, idx-1, idx);
            idx--;
        }
    }
}
public static void insertsort2(int[] arr){
    if(arr==null||arr.length<2)
            return ;
    int N=arr.length;
    for(int i=1;i<N;++i){
        for(int j=i-1;j>=0&&arr[j]>arr[j+1];--j)
            swap(arr, j, j+1);
    }
}

归并排序

public static void mergeSort(int[] arr){
    if(arr==null||arr.length<2)
        return;
    merge(arr,0,arr.length-1);
}
public static void merge(int[] arr, int l, int r) {
    if(l==r)
        return;
    int mid=l+((r-l)>>1);
    merge(arr,l,mid);
    merge(arr,mid+1,r);
    mergeAll(arr,l,mid,r);
}
public static void mergeAll(int[] arr, int l, int mid, int r) {
    int[] tmp = new int[r-l+1];
    int i=0,p=l,q=mid+1;
    while(p<=mid&&q<=r){
        tmp[i++]=arr[p]<=arr[q]?arr[p++]:arr[q++];
    }
    while (p<=mid)
        tmp[i++]=arr[p++];
    while (q<=r)
        tmp[i++]=arr[q++];
    for (int j = 0; j < tmp.length; j++) {
        arr[l+j] = tmp[j];
    }
}
//非递归实现
public static void mergeSort2(int[] arr){
    if(arr==null||arr.length<2)
        return;
    int N=arr.length,mergesize=1;
    while (mergesize<N){
        int l=0;
        while (l<N){//分组合并
            int mid=l+mergesize-1;
            if(mid>=N)
                break;
            int r=Math.min(mid+mergesize, N-1);
            mergeAll(arr,l,mid,r);
            l=r+1;
        }
        if(mergesize>N/2)//防止mergesize*2后溢出
            break;
        mergesize<<=1;
    }
}
求数组小和

在一个数组中,数左边比它小的数的总和叫做数的小和,数组中所有数的小和累加起来叫做数组小和
使用归并排序,每次mergeAll的时候产生小和:
左半边当前数a<右半边当前数b时,产生小和,数量为右半边大于a的数的个数
将上述过程中产生的所有小和累加,即为数组的小和
通过归并排序将时间复杂度降到O(N*logN),避免浪费大量的比较

public static int LessSum(int[] arr){
    if(arr==null||arr.length<2)
        return 0;
    return mergeLessSum(arr,0,arr.length-1);
}

public static int mergeLessSum(int[] arr, int l, int r) {
    if(l==r)
        return 0;
    int mid=l+((r-l)>>1);
    return mergeLessSum(arr,l,mid)+mergeLessSum(arr,mid+1,r)+mergeAllSum(arr,l,mid,r);
}

public static int mergeAllSum(int[] arr, int l, int mid, int r) {
    int[] help = new int[r-l+1];
    int i=0,p=l,q=mid+1;
    int res=0;
    while(p<=mid&&q<=r){
        res+=arr[p]<arr[q]?(r-q+1)*arr[p]:0;//计算当前arr[q]的小和
        help[i++]=arr[p]<arr[q]?arr[p++]:arr[q++];//这里只能是<,不是<=,因为需要保留左半边的较小数
    }
    while(p<=mid)
        help[i++] = arr[p++];
    while(q<=r)
        help[i++] = arr[q++];
    for (int j = 0; j < help.length; j++) {
        arr[l+j] = help[j];
    }
    return res;
}
public static void main(String[] args) {
    int res = LessSum(new int[]{1,5,2,6,3,7,4});
    System.out.println(res);
}
求数组中的所有降序对

使用归并排序:
若左半边当前数a>右半边当前数b,则记录a及其以后的数的个数作为b的当前降序对数

快速排序

partition
  • 给定数组arr和整数a,将小于等于a的数放在数组左边,大于a的数放在数组右边:

i从0开始遍历,j=-1记录<=a的区域边界:

  1. arr[i]<=a:arr[i]和arr[++k]交换
  2. arr[i]>a:continue
  • 给定数组arr和整数a,将小于a的数放在数组左边,等于a的放在中间,大于a的数放在数组右边:

k从0开始遍历,i=-1、j=arr.length记录小于区和大于区的边界:

  1. arr[k]==a:continue
  2. arr[k]
  3. arr[k]>a:arr[k]与arr[–j]交换,此时i停在原地
# 荷兰国旗
public static int[] netherlandsFlag(int[] arr, int l, int r){
    if(l>r)
        return new int[]{-1,-1};
    if(l==r)
        return new int[]{l,r};
    int less=l-1,more=r,index=l;
    while(index<more){
        if(arr[index]==arr[r])
            index++;
        else if(arr[index]<arr[r]){
            swap(arr, index++, ++less);
        }else{
            swap(arr, index, --more);
        }
    }
    swap(arr,more,r);
    return new int[]{less+1,more};
}
#快速排序
public static void sort(int[] arr, int l, int r){
    if(l>=r)
        return;
    swap(arr, l+(int)Math.random()*(r-l+1), r);
    int[] equalArea = netherlandsFlag(arr, l, r);
    sort(arr, l, equalArea[0]-1);
    sort(arr, equalArea[1]+1, r);
}

桶排序

计数排序

一般要求数组是整数,范围较窄

遍历数组,数字对应位置计数+1;最后根据计数情况打印相应个数的数字

例如:{1,2,3,4,2,2,3,0,2,1,4,2,5,6,2,0,3,6,5,3,5,6}

计数数组结果为[2,2,6,4,2,3,3],排序结果为[0,0,1,1,2,2,2,2,2,2,3,3,3,3,4,4,5,5,5,6,6,6]

基数排序

一般要求数组是十进制正整数

例如:{123,9,234,32,15,24,35,47,332}

准备十个桶(队列),根据个位数字进桶:[[32,332],[123],[234,24],[15,35],[47],[9]]

依次出桶:{32,332,123,234,24,15,35,47,9}

根据十位数字进桶:[[9],[15],[123,24],[32,332,234,35],[47]]

依次出桶:{9,15,123,24,32,332,234,35,47}

根据百位数进桶:[[9,15,24,32,35,47],[123],[234],[332]]

依次出桶:{9,15,24,32,35,47,123,234,332}

public class radixSort {
    public static void sort(int[] arr){
        if(arr==null||arr.length<2)
            return;
        radixSort(arr,0,arr.length,maxbits(arr));
    }
    private static int maxbits(int[] arr) {
        int max = Integer.MIN_VALUE;
        for(int i=0;i<arr.length;++i){
            max = Math.max(max, arr[i]);
        }
        int ans=0;
        while(max!=0){
            ans++;
            max/=10;
        }
        return ans;
    }
    public static void radixSort(int[] arr, int l, int r, int digit){
        final int radix=10;
        int i=0,j=0;
        int[] help = new int[r-l];
        for (int d = 1; d <= digit; d++) {
            int[] count = new int[radix];
            for(i=l;i<r;++i){
                j = getDigit(arr[i], d);
                count[j]++;
            }
            for(i=0;i<radix-1;++i){
                count[i+1]+=count[i];
            }
            for(i=r-1;i>=l;--i){
                j = getDigit(arr[i], d);
                help[count[j]-1] = arr[i];
                count[j]--;
            }
            for(i=l,j=0;i<r;++i,++j)
                arr[i] = help[j];
        }
    }
    private static int getDigit(int num, int d) {
        while(--d>0){
            num/=10;
        }
        return num%10;
    }
    public static void main(String[] args) {
        int[] a = new int[]{2,32,14,23,245,324,11,24,21,71};
        sort(a);
        for (int i : a) {
            System.out.print(i+" ");
        }
    }
}

排序算法稳定性

对非基础类型来说有重要意义,对基础类型来说稳定性没有意义

有的排序算法可以实现成稳定的,有的不行

算法 时间复杂度 额外空间复杂度 稳定性
选择排序 O(N2) O(1)
冒泡排序 O(N2) O(1)
插入排序 O(N2) O(1)
归并排序 O(N*logN) O(N)
随机快排 O(N*logN) O(logN)
堆排序 O(N*logN) O(1)
计数排序 O(N) O(M)
基数排序 O(N) O(N)

系统提供的排序方法先通过反射判断数据类型,若是基础类型,则采用快排,若是引用类型,为了保持稳定性,采用归并排序

堆结构

堆结构是用数组实现的完全二叉树,第i个节点的左孩子下标为2i+1,右孩子下标为2i+2,父节点下标为(i+1)/2;若数组0位置弃用,则节点i的左孩子为2i,右孩子为2i+1,父节点为i/2,所有计算可以用位运算实现

特殊的堆有大根堆和小根堆

# 大根堆
public class Heap {
    private int heapsize;
    private int[] heap;
    private int MAX_LEN=100;
    public Heap() {
        heapsize = 0;
        heap=new int[MAX_LEN];
    }
    public void push(int value){
        if(heapsize==MAX_LEN)
            throw new IndexOutOfBoundsException("heap full");
        heap[heapsize] = value;
        heapInsert(heap, heapsize++);
    }
    public int pop(){
        if(heapsize==0)
            throw new IndexOutOfBoundsException("heap empty");
        int ans = heap[0];
        swap(heap, 0, --heapsize);
        heapify(heap, 0, heapsize);
        return ans;
    }
    private void heapify(int[] heap, int i, int heapsize) {
        int left=2*i+1;
        while(left<heapsize){
            int largest = left+1<heapsize&&heap[left+1]>heap[left]?left+1:left;
            largest = heap[largest]>heap[i]?largest:i;
            if(largest==i)
                break;
            swap(heap, i, largest);
            i = largest;
            left = 2*i+1;
        }
    }
    private void heapInsert(int[] heap, int i) {
        while(heap[i]>heap[(i-1)/2]){
            swap(heap, i, (i-1)/2);
            i = (i-1)/2;
        }
    }
    private void swap(int[] heap, int i, int i1) {
        int tmp = heap[i];
        heap[i] = heap[i1];
        heap[i1] = tmp;
    }
}

堆排序

  1. 遍历数组,建立大根堆(或直接调整堆)
  2. 每次交换0和倒数位置的数,并调整堆

堆相关题

给定几乎排好序的数组,每个数字移动不超过k步即可有序:

对前k个数调整为小根堆,每输出一个数,加入后一个数进行堆的调整

比较器

实质是重载比较运算符

系统提供的堆PriorityQueue默认是小根堆,需要写一个Comparable接口的实现类比较器传入PriorityQueue,更改为大根堆

前缀树

public class PrefixTree {
    public static class Node{
        public int pass;
        public int end;
        public Node[] nexts;
        public Node(){
            pass=0;
            end=0;
            nexts=new Node[26];
        }
    }
    public static class prefixTree{
        private Node root;
        public prefixTree(){
            root = new Node();
        }
        public void insert(String word){
            if(word==null)
                return;
            char[] chs = word.toCharArray();
            Node node = root;
            node.pass++;
            int index=0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i]-'a';
                if(node.nexts[index]==null)
                    node.nexts[index] = new Node();
                node = node.nexts[index];
                node.pass++;
            }
            node.end++;
        }
        public int countStr(String word){
            if(word==null)
                return 0;
            char[] chs=word.toCharArray();
            Node node = root;
            int index=0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i]-'a';
                if(node.nexts[index]==null)
                    return 0;
                node = node.nexts[index];
            }
            return node.end;
        }
        public int countPrefix(String prefix){
            if(prefix==null)
                return 0;
            char[] chs=prefix.toCharArray();
            Node node = root;
            int index=0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i]-'a';
                if(node.nexts[index]==null)
                    return 0;
                node = node.nexts[index];
            }
            return node.pass;
        }
        public void delete(String word){
            if(countStr(word)!=0){
                char[] chs=word.toCharArray();
                Node node = root;
                node.pass--;
                int index=0;
                for (int i = 0; i < chs.length; i++) {
                    index = chs[i]-'a';
                    if(--node.nexts[index].pass==0) {
                        node.nexts[index]=null;
                        return;
                    }
                    node = node.nexts[index];
                }
                node.end--;
            }
        }
    }
}

链表

笔试时优先考虑时间复杂度

快慢指针

  1. 输入链表头节点,奇数长度返回中点,偶数长度返回上中点

    public static Node midOrUpMid(Node head){
        if(head==null||head.next==null||head.next.next==null)
            return head;
        Node slow = head.next;
        Node fast = head.next.next;
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    
  2. 输入链表头节点,奇数长度返回中点,偶数长度返回下中点

    public static Node midOrDownMid(Node head){
        if(head==null||head.next==null)
            return head;
        Node slow = head.next;
        Node fast = head.next;
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    
  3. 输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个

    public static Node midOrUpMidPre(Node head){
        if(head==null||head.next==null||head.next.next==null)
            return null;
        Node slow = head;
        Node fast = head.next.next;
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    
  4. 输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个

public static Node midOrDownMidPre(Node head){
    if(head==null||head.next==null)
        return null;
    if(head.next.next==null)
        return head;
    Node slow = head;
    Node fast = head.next;
    while(fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}
  1. 判断链表是否为回文
//利用栈
public static boolean isPalindrome(Node head){
    if(head==null||head.next==null)
        return true;
    Stack<Node> stack = new Stack<>();
    Node slow = head, fast=head.next;
    while(fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    slow = slow.next;
    while(slow!=null){
        stack.push(slow);
        slow = slow.next;
    }
    while(stack.peek()!=null){
        if(head.val!=stack.pop().val)
            return false;
        head = head.next;
    }
    return true;
}
//空间复杂度为O(1) 修改后半部分指向
public static boolean isPalindrome2(Node head){
    if(head==null||head.next==null)
        return true;
    Node n1=head,n2=head;
    while(n2.next!=null&&n2.next.next!=null){
        n1 = n1.next;
        n2 = n2.next.next;
    }
    n2 = n1.next;
    n1.next = null;
    Node n3=null;
    while(n2!=null){
        n3 = n2.next;
        n2.next = n1;
        n1 = n2;
        n2 = n3;
    }
    n3 = n1;
    n2 = head;
    boolean ans = true;
    while(n2!=null&&n1!=null){
        if(n1.val!=n2.val){
            ans = false;
            break;
        }
        n1 = n1.next;
        n2 = n2.next;
    }
    n1 = n3.next;
    n3.next = null;
    while(n1!=null){//恢复原状
        n2 = n1.next;
        n1.next = n3;
        n3 = n1;
        n1 = n2;
    }
    return ans;
}
  1. 对链表进行荷兰国旗划分

    public static Node partition(Node head, int pivot){
        Node lessHead=null, lessTail=null;
        Node equalHead=null, equalTail=null;
        Node moreHead=null, moreTail=null;
        Node next;
        while(head!=null){
            next = head.next;
            head.next=null;
            if(head.val<pivot){
                if(lessHead==null){
                    lessHead=head;
                    lessTail=head;
                }else {
                    lessTail.next=head;
                    lessTail=head;
                }
            }else if(head.val==pivot){
                if(equalHead==null){
                    equalHead=head;
                    equalTail=head;
                }else{
                    equalTail.next=head;
                    equalTail=head;
                }
            }else{
                if(moreHead==null){
                    moreHead=head;
                    moreTail=head;
                }else{
                    moreTail.next=head;
                    moreTail=head;
                }
            }
            head = next;
        }
        if(lessTail!=null){
            lessTail.next=equalHead;
            equalTail = equalTail==null?lessTail:equalTail;
        }
        if(equalTail!=null){
            equalTail.next = moreHead;
        }
        return lessHead!=null?lessHead:(equalHead!=null?equalHead:moreHead);
    }
    

哈希表

  1. 复制一个无环链表,其中链表的结点还多了一个rand指针指向任意结点或Null
//方法一:使用哈希表存放老节点和新节点,
//第一次遍历新建所有新节点;
//第二次遍历按照老节点的指向修改新节点的next和rand指针
//方法二:第一次遍历,新建所有新节点并放置于每两个老节点之间;
//第二次遍历,每次取出新老节点,新节点的next修改为老节点的next的next,rand修改为老节点的rand的next;
//第三次遍历,将新老节点在next方向上分离出来
public class LinkCopy {
    public static class Node{
        int val;
        Node next;
        Node rand;
        public Node(int val){
            this.val=val;
        }
    }
    public Node copy(Node head){
        if(head==null)
            return null;
        Node cur = head, next = null;
        //第一次遍历,插入新节点
        while(cur!=null){
            next = cur.next;
            cur.next = new Node(cur.val);
            cur.next.next = next;
            cur = next;
        }
        cur = head;
        Node curCpoy = null;
        //第二次遍历,修改rand指针
        while(cur!=null){
            next = cur.next.next;
            curCpoy = cur.next;
            curCpoy.rand = cur.rand!=null?cur.rand.next:null;
            cur = next;
        }
        Node newHead = head.next;
        cur = head;
        //第三次遍历,分离新老链表
        while (cur!=null){
            next = cur.next.next;
            curCpoy = cur.next;
            cur.next = next;
            curCpoy.next = next!=null?next.next:null;
            cur = next;
        }
        return newHead;
    }
}
  1. 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null。【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
//找到链表中入环的节点,
//方法一:HashSet记录节点,第一个重复的节点即为入环处
//方法二:快慢指针分别每次走一步和两步,相遇后,fast指针从头开始,与slow一起每次走一步,最后会相遇在入环处
public Node getLoopNode(Node head){    if(head==null||head.next==null||head.next.next==null){
        return null;
    }
    Node slow = head.next, fast = head.next.next;
    while(slow!=fast){
        if(fast.next==null||fast.next.next==null)
            return null;
        slow = slow.next;
        fast = fast.next.next;
    }
    fast = head;
    while(fast!=slow){
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}
//通过getLoopNode方法获得两个入环结点;
//情况1. 两个无环链表求交点:哈希表法;长度差法
public Node getNoLoopCrossNode(Node head1, Node head2){
    if(head1==null||head2==null){
        return null;
    }
    Node cur1 = head1, cur2 = head2;
    int n=0;
    while(cur1.next!=null){
        cur1 = cur1.next;
        n++;
    }
    while(cur2.next!=null){
        cur2 = cur2.next;
        n--;
    }
    if(cur1!=cur2)
        return null;
    cur1 = n>0?head1:head2;
    cur2 = cur1==head1?head2:head1;
    n = Math.abs(n);
    while(n!=0){
        n--;
        cur1 = cur1.next;
    }
    while (cur1!=cur2){
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return cur1;
}
//情况2. 一个有环一个无环链表求交点:不可能有交点
//情况3. 两个都有环的链表求交点:
//可能不相交;可能在同一个结点入环;可能在不同结点入环
public Node getLoopCrossNode(Node head1, Node loop1, Node head2, Node loop2){
    Node cur1=null, cur2=null;
    if(loop1==loop2){
        cur1=head1;
        cur2=head2;
        int n=0;
        while(cur1!=loop1){
            n++;
            cur1 = cur1.next;
        }
        while (cur2!=loop2){
            n--;
            cur2 = cur2.next;
        }
        cur1 = n>0?head1:head2;
        cur2 = cur1==head1?head2:head1;
        n = Math.abs(n);
        while(n!=0){
            n--;
            cur1 = cur1.next;
        }
        while (cur1!=cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }else{
        cur1 = loop1.next;
        while (cur1!=loop1){
            if(cur1==loop2)
                return loop1;
            cur1 = cur1.next;
        }
        return null;
    }
}

  1. 不给头节点,只给想删除的节点就将其从单链表中删除

    不可行方法:将下一个节点的值赋给要删除的节点,再删除下一个节点;

该方法不适用节点内容多或删除最后一个节点的情况

二叉树

递归序会三次访问一个结点,在第一次进行处理即为先序,在第二次进行处理即为中序,在第三次进行处理即为后序;

所有递归都可以通过栈转化为非递归

后序遍历

先序:头左右->头右左->左右头,即为后序

public void postPrint2(BTNode root){
    if(root!=null){
        Stack<BTNode> s1 = new Stack<>();
        Stack<BTNode> s2 = new Stack<>();
        s1.push(root);
        while(!s1.isEmpty()){
            root = s1.pop();
            s2.push(root);
            if(root.left!=null)
                s1.push(root.left);
            if(root.right!=null)
                s1.push(root.right);                
        }
        while (!s2.isEmpty()){
            System.out.println(s2.pop().val);
        }
    }
}
public void postPrint3(BTNode root){
    if(root!=null){
        Stack<BTNode> stack = new Stack<>();
        stack.push(root);
        BTNode c=null;
        while(!stack.isEmpty()){
            c = stack.peek();
            if(c.left!=null&&root!=c.left&&root!=c.right){//左右子树均未处理
                stack.push(c.left);
            }else if(c.right!=null&&root!=c.right){//右子树未处理
                stack.push(c.right);
            }else{//左右子树处理完
                System.out.println(stack.pop().val);
                root = c;//root记录上一次打印的结点
            }
        }
    }
}

层次遍历

利用队列

public void levelPrint(BTNode root){
    if(root==null)
        return;
    Queue<BTNode> q = new LinkedList<>();
    q.add(root);
    while(!q.isEmpty()){
        BTNode cur = q.poll();
        System.out.println(cur.val);
        if(cur.left!=null)
            q.add(cur.left);
        if(cur.right!=null)
            q.add(cur.right);
    }
}

统计二叉树宽度

  1. 利用map记录每个结点及其对应的层数,用结点层数是否等于当前层数来判断新层是否开始以及是否更新最大结点数
  2. 记录当前层最右结点和下一层最右结点,判断当前层是否结束
 public int maxWidth(BTNode root){
     if(root==null)
         return 0;
     Queue<BTNode> q = new LinkedList<>();
     q.add(root);
     BTNode curEnd=root;//当前层最右结点
     BTNode nextEnd = null;//下一层最右结点
     int max=0, curLevelNodes=0;
     while(!q.isEmpty()){
         BTNode cur = q.poll();
         if(cur.left!=null){
             q.add(cur.left);
             nextEnd = cur.left;
         }
         if(cur.right!=null){
             q.add(cur.right);
             nextEnd = cur.right;
         }
         curLevelNodes++;
         if(cur==curEnd){
             max = Math.max(max, curLevelNodes);
             curLevelNodes=0;
             curEnd=nextEnd;
         }
     }
     return max;
 }

二叉树的序列化和反序列化

  1. 先序/中序/后序/层次进行序列化
public Queue<Integer> preSerialize(BTNode root){
    Queue<Integer> q = new LinkedList<>();
    preS(root, q);
    return q;
}
public void preS(BTNode root, Queue<Integer> q) {
    if(root==null)
        q.add(null);
    else{
        q.add(root.val);
        preS(root.left, q);
        preS(root.right, q);
    }
}
public Queue<Integer> levelSerialize(BTNode root){
    Queue<Integer> ans = new LinkedList<>();
    if(root==null)
        ans.add(null);
    else{
        ans.add(root.val);
        Queue<BTNode> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            root = q.poll();
            if(root.left!=null){
                ans.add(root.left.val);
                q.add(root.left);
            }else {
                ans.add(null);
            }
            if(root.right!=null){
                ans.add(root.right.val);
                q.add(root.right);
            }else {
                ans.add(null);
            }
        }
    }
    return ans;
}
  1. 反序列化
public BTNode preQueue2BTree(Queue<Integer> prelist){
    if(prelist==null||prelist.size()==0)
        return null;
    return preBuild(prelist);
}
public BTNode preBuild(Queue<Integer> prelist) {
    Integer val = prelist.poll();
    if(val==null){
        return null;
    }
    BTNode root = new BTNode(val);
    root.left = preBuild(prelist);
    root.right = preBuild(prelist);
    return root;
}
public BTNode levelSerial2BTree(Queue<Integer> levellist){
    if(levellist==null||levellist.size()==0)
        return null;
    BTNode root = generateNode(levellist.poll());
    Queue<BTNode> q = new LinkedList<>();
    if(root!=null)
        q.add(root);
    BTNode node = null;
    while (!q.isEmpty()){
        node = q.poll();
        node.left = generateNode(levellist.poll());
        node.right = generateNode(levellist.poll());
        if(node.left!=null)
            q.add(node.left);
        if(node.right!=null)
            q.add(node.right);
    }
    return root;
}
public BTNode generateNode(Integer val){
    if(val==null)
        return null;
    return new BTNode(val);
}

打印树

public void printInOrder(BTNode root, int height, String to, int len) {
    if(root==null)
        return;
    printInOrder(root.right, height+1, "v", len);
    String val = to+root.val+to;
    int lenM = val.length();
    int lenL = (len-lenM)/2;
    int lenR = len-lenM-lenL;
    val = getSpace(lenL)+val+getSpace(lenR);
    System.out.println(getSpace(height*len)+val);
    printInOrder(root.left, height+1, "^", len);
}

public String getSpace(int len) {
    String space=" ";
    StringBuffer sb = new StringBuffer("");
    for(int i=0;i<len;++i){
        sb.append(space);
    }
    return sb.toString();
}

含父指针的二叉树

public class TNode {
    int val;
    TNode left;
    TNode right;
    TNode parent;   
}

给定树中某个结点,返回该节点的后继结点

中序遍历序列的后继:

若结点有右子树,则后继结点为右子树的最左结点;

若结点没有右子树,则往上找直至该结点为其父节点的左孩子

public TNode getNextNode(TNode root){
    if(root==null)
        return null;
    if(root.right!=null){
        return getMostLeft(root.right);
    }else{
        TNode parent = root.parent;
        while(parent!=null&&root!=parent.left){
            root=parent;
            parent=root.parent;
        }
        return parent;
    }
}
public TNode getMostLeft(TNode root) {
    if(root==null)
        return null;
    while(root.left!=null)
        root=root.left;
    return root;
}

折纸凹痕问题

一张长条纸上下对折后出现凹折痕,再对折后在原来折痕上下分别出现凹折痕和凸折痕,以此类推,打印所有折痕朝向(即为以此构建出的二叉树的中序遍历)

public void printFolds(int n){
    print(1, n, true);
}
public void print(int i, int n, boolean down) {
    if(i>n)
        return;
    print(i+1, n, true);
    System.out.print(down?"凹":"凸");
    print(i+1, n, false);
}

递归套路

  1. 判断二叉树是否为平衡二叉树
public class Info{
    boolean isbalance;
    int height;
    public Info(boolean b, int h){
        isbalance=b;
        height=h;
    }
}
public Info isBalance(BTNode root){
    if(root==null)
        return new Info(true, 0);
    Info left = isBalance(root.left);
    Info right = isBalance(root.right);
    int height = Math.max(left.height, right.height)+1;
    if(!left.isbalance||!right.isbalance||Math.abs(left.height-right.height)>1)
        return new Info(false, height);
    else
        return new Info(true, height);
}
  1. 返回整棵树的最大距离

max{左子树的最大距离,右子树的最大距离,左子树高度+右子树高度+1}

public int[] maxDistance(BTNode root){
    if(root==null)
        return new int[]{0, 0};
    int[] left = maxDistance(root.left);
    int[] right = maxDistance(root.right);
    int height = Math.max(left[1],right[1])+1;
    int distance = Math.max(Math.max(left[0],right[0]),left[1]+right[1]+1);
    return new int[]{distance, height};
}
  1. 返回二叉树中最大的二叉搜索子树的大小
public class searchInfo{
    boolean isBST;
    int maxSubBSTSize;
    int max;
    int min;
    public searchInfo(boolean isBST, int maxSubBSTSize, int max, int min) {
        this.isBST = isBST;
        this.maxSubBSTSize = maxSubBSTSize;
        this.max = max;
        this.min = min;
    }
}
public searchInfo maxBST(BTNode root){
    if(root==null)
        return null;
    searchInfo left = maxBST(root.left);
    searchInfo right = maxBST(root.right);
    int min=root.val, max=root.val, maxSubBSTSize=0;
    boolean isBST=false;
    if(left!=null){
        min = Math.min(min, left.min);
        max = Math.max(max, left.max);            
    }
    if(right!=null){
        min = Math.min(min, right.min);
        max = Math.max(max, right.max);            
    }
    if((left==null?true:left.isBST)&&(right==null?true:right.isBST)&&
       (left==null?true:left.max<root.val)&&(right==null?true:right.min>root.val)){
        maxSubBSTSize = (left==null?0: left.maxSubBSTSize)+
            (right==null?0:right.maxSubBSTSize)+1;
        isBST=true;
    }
    return new searchInfo(isBST, maxSubBSTSize, max, min);
}

打表技巧

小虎去买苹果,商店只提供两种类型的塑料袋,每种类型都有任意数量:能装下6个苹果的袋子和能装下8个苹果的袋子。小虎可以自由使用两种袋子来装苹果,但是小虎有强迫症,他要求自己使用的袋子数量必须最少,且使用的每个袋子必须装满。给定一个正整数N,返回至少使用多少袋子。如果N无法让使用的每个袋子必须装满,返回-1

  1. 若N为奇数,则返回-1
  2. 否则,若N<18,则return apple==0?0:(apple==6||apple==8)?1:(apple==12||apple==14||apple==16)?2:-1;
  3. 否则,return (apple-18)/8+3

给定一个正整数N,表示有N份青草统一堆放在仓库里有一只牛和一只羊,牛先吃,羊后吃,它俩轮流吃草不管是牛还是羊,每一轮能吃的草量必须是:1,4,16,64…(4的某次方),谁最先把草吃完,谁获胜。假设牛和羊都绝顶聪明都想赢,都会做出理性的决定根据唯一的参数N,返回谁会赢

public String winner(int n){
    // 0 1 2  3  4
    //后 先 后 先 先,谁先面临0份草谁输,谁先吃完谁赢
    if(n<5)
        return (n==0||n==2)?"后手":"先手";
    int base=1;
    while(base<=n){
        if(winner(n-base).equals("后手"))//在下一局中先手变成了后手
            return "先手";
        if(base>n/4)//防止base*4溢出
            break;
        base*=4;
    }
    return "后手";
}

打印结果发现规律为“后先后先先”,代码可以转化为O(1)的代码

return (n%5==0||n%5==2)?"后手":"先手";

定义一种数,可以表示成若干(数量>1)连续正数和的数。比如:5 =2+3,5就是这样的数;12 = 3+4+5,12就是这样的数;1不是这样的数,因为要求数量大于1个;2 =1 + 1,2也不是,因为等号右边不是连续正数。给定一个参数N,返回是不是可以表示成若干连续正数和的数

暴力两个for循环能判断结果,打印结果后发现规律:1和2的时候是false,其余时候若n为2的次幂,返回false,否则返回true

if(num<3)
    return false;
return (num&(num-1))!=0;//num&(num-1)==0,则num为2的次幂

矩阵处理技巧

zigzag打印矩阵

[1 2 3 4 5 6]

[7 8 9 10 11 12]

[13 14 15 16 17 18]打印为[1 2 7 13 8 3 4 9 14 15 10 5 6 11 16 17 12 18]

public static void printZigZag(int[][] matrix){
    int Ax=0,Ay=0,Bx=0,By=0;
    int endrow=matrix.length-1,endcol=matrix[0].length-1;
    boolean up2down=false;
    while(Ax!=endrow+1){
        printlevel(matrix, Ax, Ay, Bx, By, up2down);
        Ax = Ay==endcol?Ax+1:Ax;
        Ay = Ay==endcol?Ay:Ay+1;
        By = Bx==endrow?By+1:By;
        Bx = Bx==endrow?Bx:Bx+1;
        up2down=!up2down;
    }
    System.out.println();
}

private static void printlevel(int[][] matrix, int Ax, int Ay, int Bx, int By, boolean up2down) {
    if(up2down){
        while(Ax!=Bx+1){
            System.out.print(matrix[Ax++][Ay--]+" ");
        }
    }else{
        while(Bx!=Ax-1){
            System.out.print(matrix[Bx--][By++]+" ");
        }
    }
}

转圈打印矩阵

[1 2 3 4 5 6]

[7 8 9 10 11 12]

[13 14 15 16 17 18]打印为[1 2 3 4 5 6 12 18 17 16 15 14 13 7 8 9 10 11]

public static void printEdge(int[][] matrix, int Ax, int Ay, int Bx, int By){
    if(Ax==Bx){
        for(int i=Ay;i<=By;i++){
            System.out.print(matrix[Ax][i]+" ");
        }
    }else if(Ay==By){
        for(int i=Ax;i<=Bx;i++){
            System.out.print(matrix[i][Ay]+" ");
        }
    }else{
        int curX=Ax, curY=Ay;
        while(curY!=By){
            System.out.print(matrix[curX][curY]+" ");
            curY++;
        }
        while (curX!=Bx){
            System.out.print(matrix[curX][curY]+" ");
            curX++;
        }
        while(curY!=Ay){
            System.out.print(matrix[curX][curY]+" ");
            curY--;
        }
        while (curX!=Ax){
            System.out.print(matrix[curX][curY]+" ");
            curX--;
        }
    }
}
public static void printRound(int[][] matrix){
    int Ax=0,Ay=0,Bx= matrix.length-1,By=matrix[0].length-1;
    while(Ax<=Bx&&Ay<=By){
        printEdge(matrix, Ax++, Ay++, Bx--, By--);
    }
}

原地旋转正方形矩阵

1 2 3 7 4 1

4 5 6 8 5 2

7 8 9 旋转为 9 6 3

public static void rotate(int[][] matrix){
    int a=0,b=0,c= matrix.length-1,d=matrix[0].length-1;
    while(a<c){
        rotateEdge(matrix, a++, b++, c--, d--);
    }
}

public static void rotateEdge(int[][] matrix, int a, int b, int c, int d) {
    int tmp=0;
    for(int i=0;i<d-b;++i){
        tmp = matrix[a][b+i];
        matrix[a][b+i] = matrix[c-i][b];
        matrix[c-i][b] = matrix[c][d-i];
        matrix[c][d-i] = matrix[a+i][d];
        matrix[a+i][d] = tmp;
    }
}

数据结构

动态数组ArrayList

虽然需要扩容,但整体对时间复杂度影响较小,每次扩容都是常数时间的复杂度

哈希表

哈希表的增删改查都是常数时间;
java中,基础类型按值传递,Integer类在-128-127之间是按值,其他范围是引用传递
但在哈希表中,基础类型和包装类都是按值传递,自定义类是按引用传递

Map<String, String> map = new HashMap<>();
String a="123", b="123";
map.put(a, "333");
System.out.println(map.containsKey(b));//true

有序表

有序表的操作时间复杂度都是O(logN)

设计数据结构记录数组中的累加和

经常求数组arr[L,R]之间的累加和,设计数据结构方便存储和访问:

  1. 矩阵:左下角L>R,为非法查询;对角线为数组对应位置元素值;右上角记录L~R的累加和;取累加和或数组元素均可从矩阵中取值
  2. **前缀和数组:**遍历一边生成newarr[i]=newarr[i-1]+arr[i],则newarr[i]表示arr[0,i]位置的累加和;arr[L,R]的累加和可以通过newarr[R]-newarr[L-1]计算

链表

单向链表

节点的结构定义
public class Node{
	public int value;
	public Node next;
	public Node(int data){
		value = data;
	}
}
//或泛型
public static class Node<T>{
  public T value;
    public Node<T> next;
    public Node(T data){
        value = data;
    }
}

双向链表

节点的结构定义
public class DoubleNode{
	public int value;
	public DoubleNode last;
	public DoubleNode next;
	public DoubleNode(int data){
		value = data;
	}
}
//或泛型
public static class DoubleNode<T>{
    public T value;
    public DoubleNode<T> next;
    public DoubleNode<T> last;
    public DoubleNode(T data){
        value = data;
    }
}

反转

  1. 两个空节点负责记录前后节点
  2. next赋值记录
  3. 修改指针指向
  4. 节点后移
public class reverseList {
    public static class Node{
        public int value;
        public Node next;
        public Node(int data){
            value = data;
        }
    }
    public static class DoubleNode{
        public int value;
        public DoubleNode next;
        public DoubleNode last;
        public DoubleNode(int data){
            value = data;
        }
    }
    public Node reverseLinkedList(Node head){//类似于头插法
        Node pre = null;
        Node next = null;
        while(head!=null){
            next = head.next;
            head.next = pre;//修改指针指向
            pre = head;
            head = next;
        }
        return pre;
    }
    public DoubleNode reverseLinkedList(DoubleNode head){
        DoubleNode pre = null;
        DoubleNode next = null;
        while(head!=null){
            next = head.next;
            head.next = pre;//修改指针指向
            head.last = next;//反转
            pre = head;
            head = next;
        }
        return pre;
    }
}

删除给定值

  1. 找到删除给定值后的头节点(链表开头有多个都是给定值的情况)
  2. 两个节点记录pre和当前节点cur,head不改动,作为最后的头节点返回
  3. 遍历,若遇到给定值,修改指针指向,否则,pre=cur
public Node removeValue(Node head, int value){//可能删除头节点,所以应该有返回的节点值
    while(head!=null){
        if(head.value!=value)
            break;
        head = head.next;
    }
    Node pre = head;
    Node cur = head;//head是需要返回的头节点,因此利用cur来遍历
    while(cur != null){
        if(cur.value == value){
            pre.next = cur.next;
        }else{
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}
public DoubleNode removeValue(DoubleNode head, int value){
    while(head!=null){
        if(head.value!=value){
            break;
        }
        head = head.next;
    }
    DoubleNode pre = head;
    DoubleNode cur = head;
    while(cur!=null){
        if(cur.value==value){
            pre.next = cur.next;
            cur.next.last = pre;
        }else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}

K个节点的组内逆序

例如{1,4,2,6,3,7,1,5},以3个为一组,调整为{2,4,1,7,3,6,1,5}

public Node reverseK(Node head, int k){
    Node start = head;
    Node end = getKEnd(start, k);
    if(end==null)
        return head;
    head = end;//最终返回的head保持不变,是第一组的最后一个节点
    reverse(start, end);
    Node lastEnd = start;
    while (lastEnd!=null){
        start=lastEnd.next;
        end = getKEnd(start,k);
        if(end==null)
            return head;
        reverse(start, end);
        lastEnd.next = end;
        lastEnd = start;
    }
    return head;
}
public static Node getKEnd(Node start, int k){
    while (--k!=0&&start!=null)
        start = start.next;//不够k个则返回null
    return start;
}
public static void reverse(Node start, Node end){
    end=end.next;
    Node pre=null,cur=start,next=null;
    while (cur!=end){
        next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    start.next = end;
}

链表相加

例,{4,3,6}+{2,5,3}={6,8,9},表示634+352=986

public Node add(Node head1, Node head2){
    int len1=listLength(head1), len2=listLength(head2);
    Node l = len1>=len2?head1:head2;
    Node s = l==head1?head2:head1;
    Node curl=l, curs=s,last=curl;
    int curnum=0,carry=0;
    while(curs!=null){//长短都有
        curnum = curl.val + curs.val + carry;
        carry = curnum/10;
        curl.val = curnum%10;
        last=curl;
        curl=curl.next;
        curs=curs.next;
    }
    while (curl!=null){//
        curnum = curl.val + carry;
        carry = curnum/10;
        curl.val = curnum%10;
        last = curl;
        curl = curl.next;
    }
    if(carry!=0)
        last.next = new Node(1);
    return l;
}
public static int listLength(Node head){
    int len=0;
    while(head!=null){
        len++;
        head=head.next;
    }
    return len;
}

合并有序链表

public static Node merge(Node head1, Node head2){
    if(head1==null||head2==null)
        return head1==null?head2:head1;
    Node head = head1.val <= head2.val ? head1:head2;
    Node cur1 = head.next;
    Node cur2 = head==head1?head2:head1;
    Node pre = head;
    while (cur1!=null&&cur2!=null){
        if(cur1.val <= cur2.val){
            pre.next = cur1;
            cur1 = cur1.next;
        }else {
            pre.next = cur2;
            cur2 = cur2.next;
        }
        pre = pre.next;
    }
    pre.next = cur1==null?cur2:cur1;
    return head;
}

双向链表实现

双向链表实现双向队列,再通过单方向操作实现栈

public class DoubleListStack<T> {
    public DoubleList_DoubleQueue<T> myStak;
    public DoubleListStack(){
        myStak = new DoubleList_DoubleQueue<T>();
    }
    public void push(T value){
        myStak.enQueueFront(value);
    }
    public T pop(){
        return myStak.deQueueFront();
    }
}

数组实现

注意判断栈满和栈空

实现getMin(),时间复杂度为O(1)

维护一个最小栈:
入栈元素<=最小栈栈顶元素时将其压入最小栈;
出栈元素==最小栈栈顶元素时将其弹出最小栈

队列

单链表实现队列和栈

public static class Queue{
    private Node head;
    private Node tail;
    private int size;
    public Queue(){
        head=null;
        tail=null;
        size = 0;
    }
    public boolean isEmpty(){
        return size==0;
    }
    public int size(){
        return size;
    }
    public void offer(int value){
        Node n = new Node(value);
        if(tail==null){
            head=n;
            tail=n;
        }else{
            tail.next=n;
            tail=n;
        }
        size++;
    }
    public int poll(){
        int ans = -1;
        if(head!=null) {
            ans = head.value;
            head = head.next;
            size--;
        }
        if(head==null)
            tail=null;//否则tail指向的数据不会被释放,tail和head不一致
        return ans;
    }
}
public static class Stack{
    private Node top;
    int size;
    public Stack(){
        top=null;
        size=0;
    }
    public boolean isEmpty(){
        return size==0;
    }
    public int size(){
        return size;
    }
    public void push(int value){
        Node n =new Node(value);
        if(top==null){
            top = n;
        }else{
            n.next=top;
            top=n;
        }
        size++;
    }
    public int pop(){
        int ans=-1;
        if(top!=null){
            ans = top.value;
            top=top.next;
            size--;
        }
        return ans;
    }
}

所有操作时间复杂度均为O(1)

双向链表实现双端队列

package com.lqr.Stack;

import com.lqr.ListCode.removeValue;

public class DoubleList_DoubleQueue<T> {
    public removeValue.DoubleNode<T> head;
    public removeValue.DoubleNode<T> tail;
    public void enQueueFront(T value){
        removeValue.DoubleNode<T> cur = new removeValue.DoubleNode<T>(value);
        if(head==null){
            head = cur;
            tail = cur;
        }else {
            cur.next = head;
            head.last = cur;
            head = cur;
        }
    }
    public void enQueueTail(T value){
        removeValue.DoubleNode<T> cur = new removeValue.DoubleNode<T>(value);
        if(head == null){
            head = cur;
            tail = cur;
        }else{
            tail.next = cur;
            cur.last = tail;
            tail = cur;
        }
    }
    public T deQueueFront(){
        if(head == null){
            return null;
        }
        removeValue.DoubleNode<T> cur = head;
        if(head == tail){
            head = null;
            tail = null;
        }else {
            head = head.next;
            head.last = null;
            cur.next = null;
        }
        return cur.value;
    }
    public T deQueueTail(){
        if(head == null){
            return null;
        }
        removeValue.DoubleNode<T> cur = tail;
        if(head == tail){
            head = null;
            tail = null;
        }else {
            tail = tail.last;
            tail.next = null;
            cur.last = null;
        }
        return cur.value;
    }
}

双向链表实现队列

双向链表先构造双向队列,再调用类和方法实现队列

public class DoubleListQueue<T> {
    public DoubleList_DoubleQueue<T> myQueue;
    public DoubleListQueue(){
        myQueue = new DoubleList_DoubleQueue<T>();
    }
    public void enQueue(T value){
        myQueue.enQueueTail(value);
    }
    public T deQueue(){
        return myQueue.deQueueFront();
    }
}

数组实现循环队列

增加size和nextIndex()函数,不用再考虑front和tail之间的关系和越界问题

package com.lqr.Stack;
public class ArrayQueue {
    private int[] queue;
    private int front;
    private int tail;
    private int size;
    private int max_size;
    public ArrayQueue(int l){
        queue = new int[l];
        max_size = l;
        front = 0;
        tail = 0;
        size = 0;
    }
    public boolean enqueue(int value){
        if(size==max_size)
            throw new RuntimeException("队列已满");
        size++;
        queue[tail]=value;
        tail = netIndex(tail);
        return true;
    }
    public int dequeue(){
        if(size == 0){
            throw new RuntimeException("队列为空");
        }
        size--;
        int a = queue[front];
        front = netIndex(front);
        return a;
    }
    public int netIndex(int i){
        return i>max_size?0:i+1;
    }
}

栈实现队列

两个栈分别为push和pop:

  1. 入队:从push栈入栈,若pop栈为空,则将push栈元素全部出栈入栈到pop栈
  2. 出队:pop栈顶元素出栈

队列实现栈

两个队列分别为data和help:

  1. 入队:从data入队
  2. 出队:data所有元素出队,进入help,留下最后一个元素作为栈顶元素返回,交换data和help

你可能感兴趣的:(java开发,leetcode,算法,数据结构,java)