Bootstrap

算法模板整理

二叉树

总模板

总路线:明确一个节点要做的事情,然后剩下的时抛给框架

void traverse(TreeNode node){
   
    //node节点需要做的事情,在这做
    toDo(...);
    //其他的节点不用node操心,抛给框架
    traverse(node.left);
    traverse(node.right);
}

代码实现:

  1. 二叉树所有的节点值加1

    void plusOne(TreeNode node){
         
        //终止条件
        if(node == null) return;
        
        //node具体的操作
        root.val += 1;
        
        //其余节点
        plusOne(node.left);
        plusOne(node.right);
    }
    
  2. 判断二叉树是否相同

    boolean isSameTree(TreeNode node1, TreeNode node2){
         
    	
        //两个node节点的具体操作
        if(node1 == null && node2 == null) return true;//都为空,则显然相同
        if(node1 == null || node2 == null) retrun false;//一个空一个非空,显然不同
        if(node1.val != node2.val) return false;//两个都非空,但val不同也不同
        
        //其余节点
        return isSameTree(node1.left,node2.left) && isSameTree(node1.right,node2.right);
    }
    

二分搜索树模板

定义:一个二叉树中,任意节点的值要大于等于左子树所有节点的值,且要小于等于右边子树的所有节点的值。

BST的遍历:

void BST(TreeNode node, int target){
   
    if(node.val == target)
        //找到目标做点什么
    if(node.val < target)
        BST(node.right,target);
    if(node.val > target)
        BST(node.left,target);
}

单调栈

基础模板

以Next Greater Number为例作为模板(参考labuladong的算法小抄)

给出:[2,1,2,4,3] 返回:[4,2,4,-1,-1]

ArrayList<Integer> nextGreaterElement(int[] nums){
   
    List<Integer> ans = new ArrayList<>();
    Stack<Integet> stack = new Stack<>();
    
    for(int i=nums.length-1; i>=0; i--){
   //倒着入栈,正着出栈
        while(!stack.isEmpty() && stack.top() <= nums[i]){
   //判定个子高矮
            stack.pop(); //矮个被弹出,因为会被高的挡住
        }
        //拿到这个元素身后的第一高个
        ans.get(i) = stack.isEmpty() ? -1 : stack.peek(); 
        stack.push(nums[i]);//进队
    }
    return ans;
} 

:这个算法的时间复杂度为O(N),因为while循环会弹出元素,每个元素都会入栈一次最多也只会被出栈一次,没有任何多余操作故为O(N)的时间复杂度

循环数组模板

当上面的数组是以环形的方式存放的则可以使用双倍长度数组来进行模拟

线性数组模拟环形特效

int n = nums.length,index = 0;
while(true){
   
    print(nums[index % n]);
    index++;
}

环形Next Greater Number

ArrayList<Integer> nextGreaterElement(int[] nums){
   
    int n = nums.length;
    List<Integer> ans = new ArrayList<>();
    Stack<Integet> stack = new Stack<>();
    
    for(int i=2*n-1; i>=0; i--){
   //假装数组长度翻倍
        while(!stack.isEmpty() && stack.top() <= nums[i%n]){
   //通过取余来实现环形
            stack.pop(); //矮个被弹出,因为会被高的挡住
        }
        //拿到这个元素身后的第一高个
        ans.get(i%n) = stack.isEmpty() ? -1 : stack.peek(); 
        stack.push(nums[i]);//进队
    }
    return ans;
} 

单调队列

本质上仍是一个队列,只不过队列中的元素递增或递减,通过这样的一个数据结构可以解决滑动窗的一系列问题

以LeetCode-239题目为例,重点在于线性时间复杂度的实现

前提结论:

​ 在一堆数字中,已知最值,如果给这堆数添加一个数,那么比较一下就可以很快计算出最值;但如果减少一个数则需要遍历所有数,重新找最值

解题模板:

public int[] maxSlidingWindow(int[] nums, int k) {
   
	Window window;
    int[] res = new int[nums.length-k+1];
    
    for(int i=0; i<nums.length;i++){
   
        if(i < k-1){
   //先把窗口的前k-1填满
            window.push(nums[i]);
        }else{
   //窗口开始滑动
            window.push(nums[i]);
            res[i] = window.max(); //窗口里的最大值
            window.pop(nums[i-k+1]); //移除窗口中最左侧元素
        }    
    }
    return res;
}

window结构可以使用单调队列来找寻最值

//Java中LinkedList用来实现双向队列的
LinkedList<Integer> list = new LinkedList();

for(int i=0; i<nums.length; i++){
   
    //保证从左至右是从大到小的,如果前面的数小于当前的则弹出
    while(list.isEmpty() && nums[list.peekLast()] <= nums[i])
        list.pollLast();
    //添加对应元素的下标到队列中
    list.addLast(i);
    //当窗口长度超过k时则需要删除过期的元素
    if(list.peek()<= i-k)
        list.poll();
}

单调队列与优先级队列:

​ 单调队列在添加元素的时候靠删除元素保持队列的单调性,相当于抽取出某个函数中单调递增(递减)部分;优先级队列(二叉堆)相当于自动排序

二分查找

分析二分查找:不要出现else,而是把所有情况用else if 写清楚,这样可以清楚地展现所有细节

二分查找模板

public int binarySearch(int[] nums, int target){
   
    int left = 0;
    int right = ...;
    
    while(...){
   
        int mid = left+(right-left)/2;
        if(nums[mid] == target){
   
            ...
        }else if(nums[mid] < target){
   
            left = ...;
        }else if(nums[mid] > target){
   
            right = ...;
        }
    }
    return ...;
} 

基本二分查找

查询一个数,判断是否存在

public int binarySearch(int[] nums, int target){
   
    int left = 0;
    int right = nums.length-1; //right的起始位置会影响while循环的结束条件
    
    while(left <= right){
   
        int mid = left+(right-left)/2;
        if(nums[mid] == target){
   
            return mid;
        }else if(nums[mid] < target){
   
            left = mid+1;
        }else if(nums[mid] > target){
   
            right = mid-1;
        }
    }
    //不存在时返回-1
    return -1;
} 

这个最基本的二分搜索,存在局限性:无法处理重复元素时的情况,下面两种方法则可以处理这类情况

左侧边界二分查找

public int left_bound(int[] nums, int target){
    if(nums.length == 0) return -1;
    int left = 0;
    int right = nums.length;
    
    while(left < right){
        int mid = left+(right-left)/2;
        if(nums[mid] == target){
            right = mid;
        }else if(nums[mid] < target){
            left = mid+1;
        }else if(nums[mid] > target){
            right = mid;
        }
    }
    return left;
}

右侧边界二分查找

public int right_bound(int[] nums, int target){
   
    if(nums.length == 0) return -1;
    int left = 0;
    int right = nums.length;
    
    while(left < right){
   
        int mid = left+(right-left)/2;
        if(nums[mid] == target){
   
            left = mid+1;
        }else if(nums[mid] < target){
   
            left = mid+1;
        }elsr if(nums[mid] > target){
   
            right = mid;
        }
    }
    return left-1;
}

双指针

双指针还可以细化分为两类:快慢指针和左右指针

快慢指针

  1. 链表是否有环

    public boolean hasCysle(ListNode head){
         
    	ListNode fast,slow;
        fast = slow = head;
        
        while(fast != null && fast.next != null){
         
            fast = fast.next.next;
            slow = slow.next;
            
            if(fast == slow
;