LeetCode算法笔记

1、认识复杂度、对数器、二分法与异或运算

(1)复杂度

常见常数时间操作:

  • 常见的算术运算(+、-、*、/、%等)
  • 常见的位运算(>>>、>>、<<、|、&、^等)
  • 赋值、比较、自增、自减操作等
  • 数组寻址操作

选择排序

public class Code01_SelectionSort {
    public static void selectionSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        // 第一个for是控制那个范围做什么事
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;//找到最小值在那个位置i ~ (n-1),找到就放i位置
            for (int j = i + 1; j < arr.length; j++) {
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr,i,minIndex);
        }
    }
    private static void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

冒泡排序

public class Code02_BubbleSort {
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = arr.length - 1; i > 0; i--) {
            for (int j = 0; j < i; i++) {
                if (arr[j] < arr[j + 1]) {
                    swap(arr,i,j);
                }
            }
        }
    }
    private static void swap(int[] arr,int i,int j){
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }
}

插入排序

public class Code01_InsertionSort {
    public static void insertSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 1; i < arr.length;i++) {
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr,j,j+1);
            }
        }
    }
    static void swap(int[] arr,int i,int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }
}

一个问题的最优解是什么?

一般情况下,认为解决一个问题的算法流程,在时间复杂度的指标上,一定尽可能地低,先满足了时间复杂度最低这个指标之后,在使用最少空间的算法流程,这样就叫问题的最优解。

(2)认识对数器
  1. 你想要测的方法a
  2. 实现复杂度不好但容易实现的方法b
  3. 实现 一个随机样本产生器
  4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样
  5. 如果有一个随机样本使得比对结果不一致,打印样本进行人工干涉,对方法进行
  6. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确
(3)二分法

有序二分查找

public class Code01_Binary {
    public static boolean binary(int[] arr,int num) {
        if (arr == null || arr.length == 0) {
            return false;
        }
        int l = 0;
        int r = arr.length - 1;
        while (l < r) {
            int mid = l + ((r - l) >> 2);
            if (arr[mid] == num) {
                return true;
            }
            else if (arr[mid] > num) {
                r = mid - 1;
            }
            else {
                l = mid + 1;
            }
        }
        return arr[l] == num;
    }
}

无序的二分

// 局部二分
    public static int getLessIndex(int[] arr) {
        if (arr.length == 0 || arr == null) {
            return -1;
        }
        if (arr.length == 1 || arr[0] < arr[1]) {
            return 0;
        }
        if (arr[arr.length - 1] < arr[arr.length - 2]) {
            return arr.length - 1;
        }
        int left = 1,right = arr.length - 2,mid = 0;
        while (left < right) {
            mid = (left+right) / 2;
            if (arr[mid] > arr[mid - 1]) {
                right = mid - 1;
            }
            else if (arr[mid] > arr[mid + 1]) {
                left = mid + 1;
            }
            else {
                return mid;
            }
        }
        return left;
    }

(4)认识异或运算

记住一句话:异或运算就记成无进位相加

异或运算的作用:

  • 参与运算的两个值,如果两个相应bit位相同,则结果为0,否则为1。
  • 特点:
    • 00=0,01=1 0异或任何数=任何数
    • 10=1,11=0 1异或任何数-任何数取反
    • 任何数异或自己=把自己置0

题目1:如果不用额外变量交换两个数

题目2:一个数组中有一种数出现了奇数次,其他数都出现偶数次,怎么找到并打印这种数。

2 链表结构、栈、队列、递归行为、哈希表和有序表

(1)单向链表和双向链表

单链表和双链表如何反转

public class Code01_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 static 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 head;
    }
    // 反转单链表
    public static Node reverseDoubleList(Node head) {
        Node pre = null;
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return head;
    }
}

把给定值都删除

public class Code01_DeleteGivenValue {
    public static class Node{
        public int value;
        public Node next;
        public Node(int data) {
            value = data;
        }
    }
    public static Node removeValue(Node head,int num) {
        while (head != null) {
            if (head.value != num) {
                break;
            }
            head = head.next;
        }
        Node pre = head;
        Node cur = head;
        while (cur != null) {
            if (cur.value == num) {
                pre.next = cur.next;
            }
            else {
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }
}
(2)栈和队列

逻辑概念:

  • 栈:数据先进后出
  • 队列:数据先进先出

栈和队列的实际实现:

  • 双向链表实现
  • 数组实现

题目1:pop、push、getMin操作的时间复杂度都是O(1),设计的栈类型可以使用现有的栈结构

public class Code1_GetMinStack {
    public static class MyStack1 {
        private Stack<Integer> stackData;
        private Stack<Integer> stackMin;
        public MyStack1(){
            this.stackData = new Stack<Integer>();
            this.stackMin = new Stack<Integer>();
        }
        public void push(int newNum) {
            if (this.stackMin.isEmpty()) {
                this.stackMin.push(newNum);
            }
            else if (newNum <= this.getmin()) {
                this.stackData.push(newNum);
            }
        }
        public int pop(){
            if (this.stackData.isEmpty()) {
                throw new RuntimeException("You stack is empty");
            }
            int value = this.stackData.pop();
            if (value == this.getmin()) {
                this.stackMin.pop();
            }
            return value;
        }
        public int getmin() {
            if (this.stackMin.isEmpty()) {
                throw new RuntimeException("You stack is empty");
            }
            return this.stackMin.peek();
        }
    }
    public static class MyStack2{
        private Stack<Integer> stackData;
        private Stack<Integer> stackMin;
        public MyStack2(){
            this.stackData = new Stack<Integer>();
            this.stackMin = new Stack<Integer>();
        }
        public void push(int newNum) {
            if (this.stackMin.isEmpty()) {
                this.stackMin.push(newNum);
            }
            else if (newNum < this.getmin()) {
                this.stackData.push(newNum);
            }
            else {
                int newMin = this.stackMin.peek();
                this.stackMin.push(newMin);
            }
            this.stackData.push(newNum);
        }
        public int pop(){
            if (this.stackData.isEmpty()) {
                throw new RuntimeException("You stack is empty");
            }
            int value = this.stackData.pop();
            if (value == this.getmin()) {
                this.stackMin.pop();
            }
            return value;
        }
        public int getmin() {
            if (this.stackMin.isEmpty()) {
                throw new RuntimeException("You stack is empty");
            }
            return this.stackMin.peek();
        }
    }
}

题目2:

  • 如何用栈结构实现队列结构
  • 如何用队列结构实现栈结构
public class Code01_TwoStackImplementQueue {
    public static class TwoStacksQueue{
        public Stack<Integer> stackPush;
        public Stack<Integer> stackPop;

        public TwoStacksQueue() {
            stackPush = new Stack<Integer>();
            stackPop = new Stack<Integer>();
        }
        // push栈向pop栈导入数据
        private void pushToPop(){
            if (stackPop.empty()) {
                while (!stackPush.empty()) {
                    stackPop.push(stackPush.pop());
                }
            }
        }
        public void add(int pushInt) {
            stackPush.push(pushInt);
            pushToPop();
        }
        public int poll() {
            if (stackPop.empty() && stackPush.empty()) {
                throw new RuntimeException("Queue is empty");
            }
            pushToPop();
            return stackPop.pop();
        }
        public int peek() {
            if (stackPop.empty() && stackPush.empty()) {
                throw new RuntimeException("Queue is empty");
            }
            pushToPop();
            return stackPush.peek();
        }
    }
}

(3)递归

栗子:

求数组arr[L…R]中的最大值,怎么用递归实现

  1. 将[L…R]范围分成左右两半,左:[L…Mid],右:[Mid+1…R]
  2. 左部分求最大值,右部分求最大值
  3. [L…R]范围上的最大值,是Max{左部分最大值,右部分最大值}
  4. 注意2是一个递归过程,当范围上只有一个数,就可以不用再递归啦
public class Code01_GetMax {
    //求最大值
    public static int getMax(int[] arr) {
        return process(arr,0,arr.length-1);
    }
    private static int process(int[] arr,int L,int R){
        if (L == R) {
            return arr[L];
        }
        int mid = L + ((R - L) >> 2);
        int leftMax = process(arr,L,mid);
        int rightMax = process(arr,mid+1,R);
        return Math.max(leftMax,rightMax);
    }

    public static void main(String[] args) {
        int[] arr = {3,1,6,2,7,9,8,2};
        System.out.println(getMax(arr));
    }
}
(4)哈希表

哈希表在,增、删、改、查都是使用O(1)

Int 这种的类型是按值传递的;而大类型Integer是引用传递的,但是如果integer(-128~127)仍然是按值传递,非基础类型是引用传递。

总结:

  • 哈希表增删改查可以认为是O(1)
  • 放入哈希表的数据,如果是基础类型,内部按值传递,内存占用是这个数据的大小
  • 放入哈希表的数据,如果不是基础类型,内部按引用传递,内存占用是8个字节
(5)有序表

7、二叉树的基本算法

(1)二叉树的先序、中序、后序遍历

  • 先序:任何子树的处理顺序都是,先头节点、再左子树、然后右子树
  • 中序:任何子树的处理顺序都是,先左子树、再头节点、然后右子树
  • 后序:任何子树的处理顺序都是,先左子树、再右子树、然后头节点

递归方式实现二叉树的先序、中序、后序遍历

  • 理解递归序
  • 先序、中序、后序都可以在递归序基础上加工出来
  • 第一次到达一个节点就打印就是先序、第二次打印即中序、第三次即后序

非递归方式实现二叉树的先序、中序、后序遍历

  • 任何递归函数都可以改变成非递归
  • 自己设计压栈的来实现

先序打印:1弹打印;2如有右,压入栈;3如有左,压入左

public static void pre(Node head) {
        System.out.println("pre-order");
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.add(head);
            while (!stack.isEmpty()) {
                head = stack.pop();
                System.out.println(head.value + " ");
                if (head.right != null) {
                    stack.push(head.right);
                }
                if (head.left != null) {
                    stack.push(head.left);
                }
            }
        }
        System.out.println();
    }

中序打印

public static void in(Node head) {
        System.out.println("in-order");
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null) {
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                }
                else {
                    head = stack.pop();
                    System.out.println(head.value + " ");
                    head = head.right;
                }
            }
        }
        System.out.println();
    }

实现二叉树的层次遍历

  • 其实就是宽度优先遍历,用队列
  • 可以通过设置flag变量的方式,来发现某一层的结束
public static void pos2(Node h) {
        System.out.println("pos-order");
        if (h != null) {
            Stack<Node> stack = new Stack<>();
            stack.push(h);
            Node c = null;
            while (!stack.isEmpty()) {
                c = stack.pop();
                if (c.left != null && h != c.right) {
                    stack.push(c.right);
                }
                else if (c.right != null && h != c.right) {
                    stack.push(c.right);
                }
                else {
                    System.out.println(stack.pop().value + " ");
                    h = c;
                }
            }
        }
        System.out.println();
    }

题目:如何设计一个打印整棵树的打印函数

//中序
public class Code08_PaperFolding {
    public static void printAllFolds(int N) {
        printProcess(1,N,true);
    }
    private static void printProcess(int i,int N,boolean down) {
        if (i > N) {
            return;
        }
        printProcess(i+1,N,true);
        System.out.println(down ? "凹": "凸");
        printProcess(i+1,N,false);
    }
    public static void main(String[] args) {
        int N = 3;
        printAllFolds(N);
    }
}

(2)二叉树套路

可以解决面试中绝大多数的二叉树问题尤其是树型dp问题,本质是利用递归遍历二叉树的便利性。

递归套路:

  1. 假设以X节点为头,假设可以向X左树和X右树要任何信息
  2. 在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要)
  3. 列出所有可能性后,确定到底需要向左树和右树要什么样的信息
  4. 把左树信息和右树信息求全集,就是任何一颗子树都需要返回的信息S
  5. 递归函数都返回S,每一颗子树都这么要求
  6. 写代码,在代码中考虑如何把左树的in 信息和右树信息整合出整颗树的信息

题目:给定一颗二叉树的头节点head,返回这颗二叉树是不是平衡二叉树

8、打表技巧和矩阵处理技巧

题目1

小虎去买苹果,商店只提供两种类型的塑料袋,每种类型都有任意数量。

  • 能装下6个苹果的袋子
  • 能装下8个苹果的袋子

小虎可以自由使用两种袋子装苹果,但是小虎有强迫证,他要求自己使用的袋子数量必须最少,且使用的每个袋子必须装满。

给定一个正整数N,返回至少使用多少袋子。如果N无法让使用每个袋子必须装满,返回-1;

题目2

给定一个正整数N,表示有N份青草统一推放在仓库里有一只牛和一只羊,牛先吃、羊后吃,它俩轮流吃草不管是牛还是羊,每一轮能吃的草量必须是:1、4、16、64 。。。(4的某次放)

谁最先把草吃完,谁获胜,假设牛和羊都绝顶聪明,都想赢,都会做出理性的决定,根据唯一的参数N,返回谁会赢

public class Code02_EatGrass {
    public static String winner1(int n) {
        if (n < 5) {
            return (n == 0 || n == 2) ? "后手" : "先手";
        }
        int base = 1;
        while (base <= n) {
            if (winner1(n - base).equals("厚实")){
                return "先手";
            }
            if (base > n / 4) {
                break;
            }
            base *= 4;
        }
        return "后手";
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println(i+":"+winner1(i));
        }
    }
}

题目3

定义一种数:可以表示成若干(数量>1)连续正数,比如:

  • 5 = 2 + 3,5就是这样的数
  • 12 = 3 + 4 +5
  • 1 不是这样的数,因为要求数量大于1、连续正数和
  • 2 = 1 + 1,2也不是,因为等号右边不是连续正数
  • 给定一个参数N,返回是不是可以表示成若干连续正数的数
public class Code03_MSumToN {
    public static boolean isMSum1(int num) {
        for (int i = 1; i <= num; i++) { // 开头的数i...
            int sum = i;
            for (int j = i + 1; j <= num; j++) {
                if (sum + j > num) {
                    break;
                }
                if (sum + j == num) {
                    return true;
                }
                sum += j;
            }
        }
        return false;
    }
    public static boolean isMSum2(int num) {
        if (num < 3) {
            return false;
        }
        return (num & (num - 1)) != 0;
    }

    public static void main(String[] args) {
        for (int num = 1; num <= 5; num++) {
            System.out.println(num + ":" + isMSum1(num));
        }
        for (int num = 1;num < 100;num++) {
            System.out.println(num +":"+isMSum2(num));
        }
    }
}

题目4:zigzag打印矩阵

题目5:转圈打印矩阵

public class Code05_printMatrix {
    public static void sprralOrderPrint(int[][] matri) {
        int tR = 0;
        int tC = 0;
        int dR = matri.length - 1;
        int dC = matri[0].length -  1;
        while (tR <= dR && tC <= dC) {
            printEdge(matri,tR++,tC++,dR--,dC--);
        }
    }
    public static void printEdge(int[][] m,int a,int b,int c,int d) {
        if (a == c) {
            for (int i = b; i <= d; i++) {
                System.out.print(m[a][i] + " ");
            }
        }
        else if (b == d) {
            for (int i = a; i <= c; i++) {
                System.out.print(m[i][b]+" ");
            }
        }
        else {
            int curC = b;
            int curR = a;
            while (curC != d) {
                System.out.print(m[a][curC] + "");
                curC++;
            }
            while (curR != c) {
                System.out.print(m[curR][d] + "");
                curR++;
            }
            while (curC != b) {
                System.out.print(m[c][curC] + "");
                curC--;
            }
            while (curR != a) {
                System.out.print(m[curR][b] + "");
                curR--;
            }
        }
    }
}

题目6:原地转正方法形矩阵

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

    public static void main(String[] args) {
        int[][] arr = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15},{16,17,18,19,20},{21,22,23,24,25}};
        for (int[] x : arr) {
            for (int y : x) {
                System.out.print(y+" ");
            }
            System.out.println();
        }
        System.out.println("=======================");
        rotate(arr);
        for (int[] x : arr) {
            for (int y : x) {
                System.out.print(y+" ");
            }
            System.out.println();
        }
    }
}

9、并查集结构和图相关算法

贪心算法求解的标准过程

  • 分析业务
  • 根据业务逻辑找到不同的贪心策略
  • 对于能举出反例的策略直接跳过,不能举出反例的策略要证明有效性,这往往是特别困难,要求数学能力很高且不具有统一的技巧性

贪心算法的解题套路:

  1. 实现一个不依靠贪心策略的解法X,可以用暴力的尝试
  2. 脑补出贪心策略A、贪心策略B、贪心策略C
  3. 用解法X和对数器,用实验的方式得知哪个贪心策略正确
  4. 不纠结贪心策略的证明

题目1:

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。

给你每一个项目开始时间和结束时间,让你来安排宣讲的日程,要求会议室进行的宣讲的场次最多,返回最多的宣讲场次。

(1)并查集

  1. 有若干个样本ABCD。。。类型假设是V
  2. 在并查集中一开始认为每个样本都在单独的集合里
  3. 用户可以在任何时候调用如下两个方法
    1. boolean isSameSet(V x,V y):查询样本X和样本Y是否属于一个集合
    2. void union(V x,V y):把X和Y各自所在集合的所有样本合并成一个集合
  4. isSameSet 和 union方法的代价越低越好

题目2:

如果两个user,a字段一样、或者b字段一样、或者c字段一样,就认为是一个人。请结合users,返回合并之后的用户数量。

(2)

更多文章:https://github.com/niutongg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值