面试算法笔记

算法笔记

将积累许久的算法笔记发出来,供大家学习借鉴。马上就春招了,得有一些算法题的积累。
ps: 后面标着题号的都是leetcode的题号还有一些事lintcode的代码我也有标注,其中代码解题思路都在我的注释中,希望对大家有借鉴的作用,后面会会坚持每日一道,会持续补充其中的代码。

链表

反转链表

方法1:添加一个虚拟的头节点做空指针头,然后后续使用头插法进行插入

class Solution {
    public ListNode reverseList(ListNode head) {
        //思路:就是直接头插法,建立一个虚拟的头结点来
        ListNode dummy = new ListNode(0);
        dummy.next = null;
        ListNode next = null;
        ListNode cur = head;
        while(cur != null){
             next = cur.next;
             cur.next = dummy.next;
             dummy.next = cur;
             cur = next;
        }
        
        return dummy.next;
    }
}

方法2:直接就是一个一个直接插入到前面

1-》2-》3-》4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NF6OhBMs-1640224025978)(G:\有道云\qq2D1D5CB92B2C0FF061B3D3F82DA32CD1\b8819188964243779e9846057a933f40\clipboard.png)]

环形链表141

leetcode141.

判断链表中是否有环,如果链表有环就返回true,没有环就返回false

两种方法:1、用HashSet 2、用快慢指针

代码实现

HashSet标记

public class Solution {
    public boolean hasCycle(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();

        while(head!=null){
            if(set.contains(head)){
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;
    }
}

快慢指针

相当于一个环,刚开始快慢指针的距离为n-1,每走一步就是缩小距离1,因此走n-1步就会相遇

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }
        ListNode slow = head, fast = head.next;
        while(fast != null){
            if(fast == slow){
                return true;
            }
            fast = fast.next == null ? null : fast.next.next;
            slow = slow.next;
        }
        return false;
    }
}
环形链表2 142

题目:如果链表有环,返回环的起始节点

使用HashSet

public class Solution {
    public ListNode detectCycle(ListNode head) {
        //检测环,返回环的头。就用HashSet使用,返回第一个重名包含就行啦
        HashSet<ListNode> set = new HashSet<ListNode>();
        int pos = 0;
        while(head != null){
            if( !set.contains(head)){
                set.add(head); //添加节点
            }else{ //包含重复节点
                return head;
            }
            head = head.next;
            pos++;
        }
        return null;
    }
}

快慢指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FapwKsfa-1640224025980)(C:\Users\Think\AppData\Roaming\Typora\typora-user-images\image-20210606111751272.png)]

删除链表的倒数N个节点 19

方法一:遍历一遍链表,得到链表长度L。然后删除第L-n+1个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int size=0;
        ListNode pre=head;
        while(pre!=null){
            pre = pre.next;
            size++;
        }
        //删除的是头节点
        if(size == n){
            head = head.next;
            return head;
        }
        pre = head;
        int idx = 0;
        while(pre!=null && idx < size-n-1){
            pre = pre.next;
            idx++;
        }
        
        ListNode temp = pre.next;
        pre.next = pre.next.next;
        temp = null;
        return head;
    }
}

方法二:栈的方法

依次将节点进栈,然后第N个出栈的节点就是要删除的节点

方法三:双指针法

就是同时用first和second指针去遍历列表,当second遍历到链表尾时,first此时正好在倒数第N个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //快慢指针,让快指针先跑N个
        ListNode dummy = new ListNode(0, head);
        ListNode first = head, second = dummy;
        if(head.next == null){
            return null;
        }
        for(int i=0;i<n;i++){
            first = first.next;
        }

        while(first != null){
            first = first.next;
            second = second.next;
        }
        System.out.println(second.val);
       
        if(second == dummy){
            return head.next;
        }
        second.next = second.next.next;
        return head;
    }
}
287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。

示例

输入:nums = [1,3,4,2,2]
输出:2
class Solution {
    public int findDuplicate(int[] nums) {
        HashSet<Integer> set = new HashSet<Integer>();
        int len = nums.length;
        for(int i=0;i<len;i++){
            if(!set.contains(nums[i])){
                set.add(nums[i]);
            }
            else
                return nums[i];
        }
        return 0;
    }
}
82. 删除排序链表中的重复元素 II

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gbyGpYi-1640224025981)(G:\技术积累\算法笔记.assets\image-20211220195757316.png)]

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        //删除链表中重复元素        
        ListNode dummy = new ListNode(-101); //创建虚拟头
        ListNode prefirst=dummy, cur = dummy;
        int temp = dummy.val;
        dummy.next = head; 
        //加个头判断
        while(cur!= null){
            if(cur.next!=null && cur.next.val != temp){ //判断下一个值是否相等
                cur = cur.next;
                temp = cur.val;
                if(cur.next == null || cur.next.val != temp){
                    prefirst.next = cur; //这块在头处理
                } 
                //prefirst = cur; //暂时不换
                if(cur.next==null || cur.val != cur.next.val ){
                    prefirst = cur;
                }
               
            }else if(cur.next!=null && cur.next.val == temp){ //相等
                cur = cur.next;
            }else if(cur.next == null ){
                cur = cur.next;
                prefirst.next = null;
            }
        }
        return dummy.next;
    }
}

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

24. 两两交换链表中的节点
示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        if(head == null){
            return null;
        }
        ListNode cur=head.next, pre=dummy;
        while(cur != null){
            //交换
            pre.next.next = cur.next;
            cur.next = pre.next;
            pre.next = cur;

            pre = pre.next.next; //跳两个
            if(pre.next != null && pre.next.next != null){
                cur = pre.next.next; //再跳两个
            }else {
                cur = null; 
            }
        }
        return dummy.next;
    }
}
160. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KPOd6z12-1640224025982)(G:\技术积累\算法笔记.assets\image-20211221185510155.png)]

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //思路:让多的那个先走一下就可以啦
        int len1 =0, len2 =0;
        ListNode la = headA, lb = headB;
        while(la!=null){
            len1++;
            la=la.next;
        }
        while(lb!=null){
            len2++;
            lb=lb.next;
        }
        while(len1 > len2){
            headA = headA.next;
            len1--;
        }
        while(len2 > len1){
            headB = headB.next;
            len2--;
        }
        while(headB!=null && headA!=null && headB != headA){
            headB = headB.next;
            headA = headA.next;
        }
        return headA;
    }
}
23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

输入案例:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //思路:采用分治法来合并队列,分治理法+递归
        int len = lists.length;
        if(len == 0){
            return null;
        }
        if(len == 1){
            return lists[0];
        }
        return divideMerge(lists, 0, len-1);
        
    }
    //分治法
    public ListNode divideMerge(ListNode[] lists, int left, int right){
        if(left == right) //返回一个
            return lists[left];
        if(left > right){
            return null;
        }
        int mid = left + (right - left) / 2;
        
        ListNode l =  divideMerge(lists, left, mid);
        ListNode r =  divideMerge(lists, mid+1, right);
        return mergeTwoLists(l, r);
    }
    //递归合并两个队列
    public ListNode mergeTwoLists(ListNode l1,  ListNode l2){
        if(l1 == null)
            return l2;
        else if(l2 == null)
            return l1;
        else if(l1.val > l2.val){
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }else {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }
    }
    
}
25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ug95whpO-1640224025983)(G:\技术积累\算法笔记.assets\image-20211222214103480.png)]

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        //逆转k个序列
        if(k == 1){
            return head;
        }
        ListNode dummy = new ListNode();
        ListNode pre=head, tail, node = dummy;
        dummy.next = head;
        int count = 1;
             while(pre != null){
            count++;
            pre = pre.next;
            if(pre == null){
                break;
            }
            if(count % k == 0){
                tail = pre.next; //遍历到k序列的尾巴
                pre.next = null;
                pre = reverseList(node.next); //逆转返回的是头结点
                node.next = pre;
                int temp = 1;
                while(pre!=null && temp < k){
                    temp++;
                    pre = pre.next;
                }
                node = pre; //
                node.next = tail;
                pre = tail;
                count++;
            }
        }
        return dummy.next;
    }

    //翻转列表
    public ListNode reverseList(ListNode head) {
        //思路:就是直接头插法,建立一个虚拟的头结点来
        ListNode dummy = new ListNode(0);
        dummy.next = null;
        ListNode next = null;
        ListNode cur = head;
        while(cur != null){
             next = cur.next;
             cur.next = dummy.next;
             dummy.next = cur;
             cur = next;
        }
        
        return dummy.next;
    }
}
LRU 146

题目:运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组

「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

并查集

题目1202. 交换字符串中的元素

给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。

你可以 任意多次交换 在 pairs 中任意一对索引处的字符。

返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。

思想:就是采用并查集 ,

  • 第一步合并
  • 第二步构建 Map集合 key是代表节点 value是 带有优先队列的排序
  • 第三步是重构其字符串
class Solution {
    public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
        //并查集 回归一下
        //初始化一下
        int len = s.length();
        UnionFind uf = new UnionFind(len);
        for(List<Integer> pair:pairs){
            int index1 = pair.get(0);
            int index2 = pair.get(1);
            uf.union(index1, index2);
        }
        //构建映射关系
        char[] charArray =s.toCharArray();
        Map<Integer, PriorityQueue<Character>> hashMap = new HashMap<>(len);
        for(int i=0;i<len;i++){
            int root = uf.find(i);
            if(hashMap.containsKey(root)){
                hashMap.get(root).offer(charArray[i]); //存入这个优先队列中
            }else{
                PriorityQueue<Character> minHeap = new PriorityQueue<>();
                minHeap.offer(charArray[i]);
                hashMap.put(root, minHeap);
            }
        }
        //重构字符串
        StringBuffer str = new StringBuffer();
        for(int i=0;i<len;i++){
            int root = uf.find(i);
            str.append(hashMap.get(root).poll());
        }
        return str.toString();

    }
    
    private class UnionFind {

        private int[] parent;
        /**
         * 以 i 为根结点的子树的高度(引入了路径压缩以后该定义并不准确)
         */
        private int[] rank;

        public UnionFind(int n) {
            this.parent = new int[n];
            this.rank = new int[n];
            for (int i = 0; i < n; i++) {
                this.parent[i] = i;
                this.rank[i] = 1;
            }
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) {
                return;
            }

            if (rank[rootX] == rank[rootY]) {
                parent[rootX] = rootY;
                // 此时以 rootY 为根结点的树的高度仅加了 1
                rank[rootY]++;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
                // 此时以 rootY 为根结点的树的高度不变
            } else {
                // 同理,此时以 rootX 为根结点的树的高度不变
                parent[rootY] = rootX;
            }
        }

        public int find(int x) {
            if (x != parent[x]) {
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }
    }


}

200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

class Solution {
    public int numIslands(char[][] grid) {
        //方法1、深度搜索,然后将vist置为1
        //方法2、并查集就是最后看有几个集合
        /**
        思路:创建一个Map<index, int[]>,更具index组成一个并查集,再遍历一下,看有个root根数就行
        Map<root, index> hashMap,最后判断几个
         */
         int len = grid.length * grid[0].length;
         int row = grid.length;
         int col = grid[0].length;
         System.out.println("len row col" + len +" "+row +" "+col);
         
         //初始化树
         UninoFind uf = new UninoFind(len+5);
         //unino合并
         for(int i=0;i<row;i++){
             for(int j=0;j<col;j++){
                 //向下、向后Unino
                if(j != col-1){ //可以后Unino
                    if(grid[i][j] == '1' && grid[i][j+1] == '1'){
                        int a = i*col+j;
                        int b = i*col+j+1;
                        //System.out.println("index1 index2 "+a+" "+ b);
                        uf.unino(a, b);
                    }
                }
                if(i != row -1 ){//可以向下unino
                    if(grid[i][j]=='1' && grid[i+1][j]=='1'){
                        int a = i*col+j;
                        int b = (i+1)*col+j;
                        //System.out.println("index index "+a+" "+ b);
                        uf.unino(a, b);
                    }
                }
             }
         }
         HashSet<Integer> set = new HashSet<Integer>();
         for(int i=0;i<row;i++){
             for(int j=0;j<col;j++){
                 int root = uf.find(i*col +j);
                 if(!set.contains(root)){
                     if(grid[i][j] == '1'){
                         set.add(root);
                     }
                     
                 }
             }
         }
         return set.size();
    }

    private class UninoFind{
        private int[] parent;

        private int[] rank;

        public UninoFind(int n){
            this.parent = new int[n];
            this.rank = new int[n];
            for(int i=0;i<n;i++){
                this.parent[i]=i; 
                this.rank[i] = 1;
            }
        }

        public void unino(int x, int y){
            int rootX = find(x);
            int rootY = find(y);
            if(rootX == rootY)
                return ;
            if(rank[rootX] == rank[rootY]){
                parent[rootX] = rootY;
                rank[rootY]++;
            }else if(rank[rootX] > rank[rootY]){
                parent[rootY] = rootX;
            }else{
                parent[rootX] = rootY;
            }
        }
        public int find(int x){
            if(x != parent[x]){
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }
    }
}

二叉树

二叉树的前序、后序、中序

前序 1、访问根节点 2、前序遍历左子树 3、前序遍历右子树

public static void preOrderRe(TreeNode biTree)
{//递归实现
    System.out.println(biTree.value);
    TreeNode leftTree = biTree.left;
    if(leftTree != null)
    {
        preOrderRe(leftTree);
    }
    TreeNode rightTree = biTree.right;
    if(rightTree != null)
    {
        preOrderRe(rightTree);
    }
}
public static void preOrder(TreeNode biTree)
{//非递归实现
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(biTree != null || !stack.isEmpty())
    {
        while(biTree != null)//一直将左子树压入栈内
        {
            System.out.println(biTree.value);
            stack.push(biTree);
            biTree = biTree.left;
        }
        if(!stack.isEmpty()) //已经将左子树压入栈中压,开始弹出栈,获取右子树
        {
            biTree = stack.pop();
            biTree = biTree.right;
        }
    }
}


中序: 1、前序遍历左子树 2、访问根节点 3、前序遍历右子树

public static void midOrderRe(TreeNode biTree)
{//中序遍历递归实现
    if(biTree == null)
        return;
    else
    {
        midOrderRe(biTree.left);
        System.out.println(biTree.value);
        midOrderRe(biTree.right);
    }
}


112. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

叶子节点 是指没有子节点的节点。

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        //遍历树,二叉树的前序、后续、中序遍历,用栈去遍历
        //思路:就是用栈去遍历,遍历到叶子节点后,判断其值是否相等
        //广度优先算法,先建立一个队列
        if(root == null){
            return false;
        }
        Queue<TreeNode> node = new LinkedList<TreeNode>(); //广度优先遍历的队列
        Queue<Integer> pathval = new LinkedList<Integer>(); //存的是这条路线的数值
        pathval.offer(root.val);
        node.offer(root); //压入点,上面的数值队列一致
        while(!node.isEmpty()){
            TreeNode n = node.poll(); //弹出尾巴
            int temp = pathval.poll();
            if(n.left == null && n.right == null){ 
                if(temp == targetSum){
                    return true;
                }
                continue;
            }
            if(n.left != null){
                node.offer(n.left);
                pathval.offer(n.left.val + temp);
            }
            if(n.right != null){
                node.offer(n.right);
                pathval.offer(n.right.val + temp);
            }
        }
        return false; 
        
    }
}

113. 路径总和 II

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和的路径

叶子节点是指没有子节点的节点。返回的是路径

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> ans = new ArrayList<>();
     

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        //返回的是路径,用深度优先搜索
        //终止条件是是判断为叶子节点
        List<Integer> path = new ArrayList<>();
        dfs(root, targetSum, path);
        return this.ans;
        
    }

    public void dfs(TreeNode root, int targetSum, List<Integer> path){
        if(root == null)
            return ;
        if(root.left == null && root.right == null){ //终止条件,叶子节点
            int temp = 0;
            //List<Integer> pathcopy = new ArrayList<>();
            path.add(root.val);
            for(Integer a:path){
                temp += a;
                //pathcopy.add(a);
                //System.out.print("" + a+" ");
            }
            System.out.println("");
            if(temp == targetSum){
                //System.out.println("==");
                //ans.add(pathcopy);
                ans.add(new ArrayList<>(path));
                //System.out.println(ans.toString());
            }

            path.remove(path.size()-1); //移除最后一个
        }else{
                path.add(root.val);
                dfs(root.left, targetSum, path);//递归
                dfs(root.right, targetSum, path);
                path.remove(path.size()-1);
            
        }

    }
}

437. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int count= 0;
    //public List<List<Integer>> ans = new ArrayList<>();
    public int pathSum(TreeNode root, int targetSum) {
        //想法就是用深度优先算法 + 根节点遍历,判断减枝情况
        //结束条件变了
       if(root == null){
            return 0;
        }
        //List<Integer> path = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while(root != null || !stack.isEmpty()){ //直接遍历,非递归的中序遍历
            while(root!=null){
                int sum = 0;
                dfs(root, targetSum,sum);
                stack.push(root);
                root = root.left;
            }
            if(!stack.isEmpty()){ 
                root = stack.pop();
                root = root.right;
            }
        }

        return this.count;
    }


    
    //深度搜索
    public void dfs(TreeNode root, int targetSum,Integer sum){
        //中止条件==targetSun
        if(root == null){
            return;
        }
        sum += root.val;

        if(sum == targetSum){
            count++;
        }
        dfs(root.left, targetSum, sum);//递归
        dfs(root.right, targetSum, sum); //递归
       
        sum -= root.val;
        

    }
}

236. 二叉树的最近公共祖先

最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9V5Q4ztp-1640224025984)(G:\技术积累\算法笔记.assets\image-20210820093053316.png)]

其中 flson 和 frson 分别代表 xx 节点的左孩子和右孩子。初看可能会感觉条件判断有点复杂,我们来一条条看,说明左子树和右子树均包含 p 节点或 q 节点,如果左子树包含的是 p 节点,那么右子树只能包含 q节点,反之亦然,因为 p节点和 q 节点都是不同且唯一的节点,因此如果满足这个判断条件即可说明 xx 就是我们要找的最近公共祖先。再来看第二条判断条件,这个判断条件即是考虑了 xx 恰好是 p节点或 q 节点且它的左子树或右子树有一个包含了另一个节点的情况,因此如果满足这个判断条件亦可说明 xx 就是我们要找的最近公共祖先。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode ans = null;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //递归,终止条件有了
         midOrderRe(root, p, q);
         
         return this.ans;
    }

    public boolean midOrderRe(TreeNode root, TreeNode p, TreeNode q)
    {//中序遍历递归实现
        if(root == null ){ //子节点
            return false;
        }

       boolean left = midOrderRe(root.left,p, q); 
       boolean right = midOrderRe(root.right,p,q);
       
       if((left&& right) || (root.val == p.val || root.val == q.val)&& (left || right) ){
           System.out.println(root.val);
           ans = root; //答案
           //return true;
       }
       return (root.val == p.val || root.val == q.val) || left || right; //返回条件
    }
}

958. 二叉树的完全性检验

给定一个二叉树,确定它是否是一个完全二叉树

class Solution {
    public boolean isCompleteTree(TreeNode root) {
        //广度遍历,然后判断每个节点的完整度
    
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        Queue<TreeNode> tempQueue = new LinkedList<TreeNode>(); //暂时存每一层的节点
        queue.offer(root);
        int height = 1;
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            if(node.left==null && node.right!=null){ //一个判断
                return false;
            }
            if(queue.size()!=0){
                if(queue.peek().left != null){
                    if( node.left==null || node.right==null){ //一个判断
                    return false;
                    }   
                }
            }
            //每一个节点进行判断
            if(node.left != null){
                tempQueue.offer(node.left);
            }
            if(node.right != null){
                tempQueue.offer(node.right);
            }
            
            if(queue.isEmpty()){//复制这一层的节点进去
                
                if(tempQueue.size() != 0){
                    if(tempQueue.peek().left!=null  ){
                        if(tempQueue.size() != (1<<height))
                            return false;
                    }
                    queue.addAll(tempQueue);
                    //判断这一层的节点是否是完整的
                    
                    height++;
                    tempQueue.clear();
                }
                
            }
        }
        return true;
    }
}

124. 二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

/**
* Definition for a binary tree node.
* public class TreeNode {
*     int val;
*     TreeNode left;
*     TreeNode right;
*     TreeNode() {}
*     TreeNode(int val) { this.val = val; }
*     TreeNode(int val, TreeNode left, TreeNode right) {
*         this.val = val;
*         this.left = left;
*         this.right = right;
*     }
* }
*/
class Solution {
   public int maxValue = Integer.MIN_VALUE;
   public int maxPathSum(TreeNode root) {

       //先最笨的方法:根节点遍历+深度优先算法,判断减枝情况
       //思想是对的,动态规划,遍历节点
       if(root == null){
           return 0;
       }
       dfs(root);
       return this.maxValue;

   }

   //返回当前定点最大路径和, 只有一条分支
   public Integer dfs(TreeNode root){
       //中止条件==targetSun
       if(root == null){
           return 0;
       }
       int left = Math.max( dfs(root.left), 0);
       int right = Math.max( dfs(root.right), 0);
       maxValue = Math.max((left + right + root.val), maxValue);

       return Math.max(left, right) + root.val;
   }

}

平衡二叉树

BST : Binary Search Tree 二叉查找树

AVL: 自平衡二叉树

二者比较:

平衡二叉树类型平衡度调整频率适用场景
AVL树查询多,增/删少
红黑树增/删频繁

红黑树:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwJQ0gWX-1640224025985)(G:\技术积累\算法笔记.assets\564326-20190712072113737-1032127584.png)]

1、每个节点非黑既红

2、根节点是黑

3、每个叶节点都是黑的

4、一个节点是红的,那么它的两儿子都是黑的

5、对于任意节点而言,其他叶子点树NULL指针的每条路径都包含相同数目的黑节点

6、每条路径都包含相同的黑节点

HashMap,TreeMap使用到红黑树

222. 完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

判断k节点存在

如何判断第 k 个节点是否存在呢?如果第 k 个节点位于第 h 层,则 k 的二进制表示包含 h+1 位,其中最高位是 11,其余各位从高到低表示从根节点到第 k个节点的路径,0 表示移动到左子节点,1 表示移动到右子节点。通过位运算得到第 k个节点对应的路径,判断该路径对应的节点是否存在,即可判断第 k 个节点是否存在。

通过k来判断其节点存在不

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    
    public int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int level = 0;
        TreeNode node = root;
        while (node.left != null) {
            level++;
            node = node.left;
        }
        int low = 1 << level, high = (1 << (level + 1)) - 1;
        while (low < high) {
            int mid = (high - low + 1) / 2 + low;
            if (exists(root, level, mid)) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return low;
    }

    public boolean exists(TreeNode root, int level, int k) {
        int bits = 1 << (level - 1);
        TreeNode node = root;
        while (node != null && bits > 0) {
            if ((bits & k) == 0) {
                node = node.left;
            } else {
                node = node.right;
            }
            bits >>= 1;
        }
        return node != null;
    }

}

Hash表

剑指 Offer 48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        //计算最长的重复子字符串
        //KMP算法(模式匹配算法),hash
        //动态规划
        
        int ans = 0;
        Map map = new HashMap<Character, Integer>();
        int len = s.length();
        int[] dp = new int[len + 10];
        Integer pre = 0;
        dp[0] = 1;
        if(len == 0){
            return ans;
        }
        ans++;
        map.put(s.charAt(0), 0);
        for(int i=1;i<len;i++){
            if(map.get(s.charAt(i)) != null){
                pre = (Integer)map.get(s.charAt(i));
                if(i - pre > dp[i-1]){
                    dp[i] = dp[i-1] +1;
                }else{
                    dp[i] = i-pre;
                }
            }else{
                dp[i] = dp[i-1]+1;
            }
            ans = Math.max(ans, dp[i]);
            System.out.println(dp[i]);
            map.put(s.charAt(i), i);
        }

        return ans;
    }
}
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> dic = new HashMap<>();
        int res = 0, tmp = 0;
        for(int j = 0; j < s.length(); j++) {
            int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i
            dic.put(s.charAt(j), j); // 更新哈希表
            tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
            res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
        }
        return res;
    }
}

前缀和+Hash表

560. 和为K的子数组

给定一个整数数组和一个整数 **k,**你需要找到该数组中和为 k 的连续的子数组的个数。

class Solution {
    public int subarraySum(int[] nums, int k) {
        //方法1:直接遍历
        //方法2:前缀和+哈希表
        //先来个前缀和
        int len = nums.length;
        int[] sum = new int[len + 10];
        int ans = 0;
        //sum[0] = nums[0];
        //计算前缀和
        // for(int
        //i=0;i<len;i++){
        //     sum[i] += nums[i]; 
        // }

        //前缀和思想就是去寻找 值等于pre[i] - k的前缀和出现的次数
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        int pre = 0;
        map.put(0, 1); 
        for(int i=0;i<len;i++){
            pre += nums[i];
            if(map.containsKey(pre-k)){
                ans += map.get(pre-k);
            }
            map.put(pre, map.getOrDefault(pre, 0) + 1);
            
        }
        return ans;
    
    }
}

map.getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。

getOrDefault() 方法的语法为:

hashmap.get(Object key, V defaultValue)

523.连续的子数组和

给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

子数组大小 至少为 2 ,且
子数组元素总和为 k 的倍数。

思路1:Hash表存的是前缀和,和出现的次数

思路2:Hash表存 每个下标对应的前缀和除以k的余数,值存的是每个余数第一次出现的下标

( pre(j) - pre (i) ) % k == 0 则 pre(j) % k == pre(i) % k

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        //前缀和+HashMap。主要难点是大小为2,思路就是(pre[i]-pre[j])%k == 0 
        //难点>=2就反向思考,就去掉一个数字等于k的n倍 nums[i]==n*k的情况
        int len = nums.length;
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        int pre = 0;
        if(len == 1){
            return false;
        }
        if(k==1){
            return true;
        }
        map.put(0, 1); 
        for(int i=0;i<len;i++){
            pre += nums[i];
            //n*k
            for(int j=1;j*k<=pre;j++){
                int temp = j*k;
                if(map.containsKey(pre-temp)){
                    // !=1
                    if(nums[i] != temp){
                        return true;
                    } 
                }
            }
            map.put(pre, map.getOrDefault(pre, 0) + 1);
            if(map.get(pre) >= 3){
                return true;
            }
        }
        return false; 
        
    }
    
}

官方题解

【同余算法】 【哈希表】 【简化前缀和】

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        int m = nums.length;
        if (m < 2) {
            return false;
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0, -1);
        int remainder = 0;
        for (int i = 0; i < m; i++) {
            remainder = (remainder + nums[i]) % k;
            if (map.containsKey(remainder)) {
                int prevIndex = map.get(remainder);
                if (i - prevIndex >= 2) {
                    return true;
                }
            } else {
                map.put(remainder, i);
            }
        }
        return false;
    }
}

LintCode 138

给定一个整数数组,找到和为 0的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置

public class Solution {
    /**
     * @param nums: A list of integers
     * @return: A list of integers includes the index of the first number and the index of the last number
     */
    public List<Integer> subarraySum(int[] nums) {
        
        //前缀和,返回的区间,和为0的子数据和区间
        List<Integer> ans = new ArrayList<Integer>();
        int len = nums.length;
        if(len ==1){
            if(nums[0] == 0){
                ans.add(0);
                ans.add(0);
            }

            return ans;
        }
        for(int i=0;i<nums.length;i++){
            if(nums[i] == 0){
                ans.add(i);
                ans.add(i);
                return ans;
            }

        }
        for(int i=0;i<nums.length-1;i++){
            for(int j=i+1;j<nums.length;j++){
                int temp = 0;
                for(int k=i;k<=j;k++){
                    temp += nums[k]; 
                }
                if(temp == 0){
                    ans.add(i);
                    ans.add(j);
                    break;
                }
            }
        }
        return ans;

    }
}

前缀和 + hasnMap的算法

public class Solution {
    /**
     * @param nums: A list of integers
     * @return: A list of integers includes the index of the first number and the index of the last number
     */
    public List<Integer> subarraySum(int[] nums) {
        
        //前缀和,返回的区间,和为0的子数据和区间
        int pre = 0;
        Map<Integer, Integer> map = new HashMap<>();
        List<Integer> ans = new ArrayList<>();
        //map.put(0 , 0);
        for(int i=0;i<nums.length;i++){
            if(nums[i] == 0){
                ans.add(i);
                ans.add(i);
                return ans;
            }
            pre += nums[i]; //计算前缀和
            if(pre == 0){
                ans.add(0);
                ans.add(i);
                return ans;
            }
            if(map.containsKey(pre)){
                Integer t = map.get(pre);
                ans.add(t+1);
                ans.add(i);
                map.put(pre, i);
                return ans;
            }
            map.put(pre, i);
        }
        return ans;
    }
}

LintCode 139最接近零的子数组和

解题思路

我的思路就是采用前缀和+Hash表的方法。
Hash<前缀和 , 这个前缀和第一次出现的下标>
1、首先先遍历一遍找寻最接近0的数字存入ans 并记录下标
2、然后接接着遍历区间[ -ans+1, ans-1],寻找是否有 前缀和pre-k 在Hash表中存在(k再区间中取),如果hash表中存在就替换ans和下标,依次遍历

时间复杂度:O(n+n*logn)

//题解代码
public class Solution {
    /*
     * @param nums: A list of integers
     * @return: A list of integers includes the index of the first number and the index of the last number
     */
    public int[] subarraySumClosest(int[] nums) {
        // write your code here
        int[] res = new int[2];
        int ans = Integer.MAX_VALUE;
        //计算最近的前缀和
        int len = nums.length;
        int pre = 0;
        Map<Integer, Integer> map = new HashMap<>();
        ans = nums[0];
        if(len == 1){
            res[0] = 0;
            res[1] = 0;
            return res;
        }
        // 先遍历一遍得最小值
        for(int i=0;i<len;i++){
            if(Math.abs(nums[i]) < Math.abs(ans)){
                ans = nums[i];
                res[0] = i;
                res[1] = i;
            }
        }
        
        //现在得到目前第一次遍历的最小值
        for(int i=0;i<len;i++){
            pre += nums[i];
            if(pre == 0){
                res[0] = 0;
                res[1] = i;
                ans = 0;
                return res;
            }
            for(int k=Math.abs(ans)*-1 + 1;k<Math.abs(ans);k++){
                if(map.containsKey(pre-k)){
                    Integer t = map.get(pre-k);
                    res[0] = t+1;
                    res[1] = i;
                    ans = k;
                    //map.put(pre, i);
                    break;
                }
            }
            map.put(pre, i);
        }
        return res;
    }
}

137. 只出现一次的数字 II

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

class Solution {
    public int singleNumber(int[] nums) {
        int[] bits = new int[32];
        //思路:就是设定一个值,然后所有数都和它取与运算,然后得到一个所有位都是1的数字
 
        int ans = 0;
        for(int i=0;i<32;i++){ //从第0位开始
            int total = 0; //每一位计数
            for(int num:nums){
                total += (num>>i)&1;
            }
            if(total % 3 != 0){
                ans += 1 <<i;
            }
        }
        return ans;
        
    }
}

260. 只出现一次的数字 III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

输入实例

输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
class Solution {
    public int[] singleNumber(int[] nums) {
        List<Integer> list = new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            if(list.isEmpty()){
                list.add(nums[i]);
            }else{
                int flag = 0;
                //遍历这个list
                for(int j=list.size()-1; j>=0; j--){ //倒叙删除
                    //最异或操作,然后判断是否为0
                    int g = list.get(j).intValue()^nums[i];
                    if(g == 0 ){
                        list.remove(j); //移除后得会有问题
                        flag = 1;
                    }        
                }
                if(flag == 0){
                    list.add(nums[i]);
                }
            }
        }
        int[] ans = new int[2];
        ans[0] = list.get(0);
        ans[1] = list.get(1);
        return ans;
    }
}

295.Leetcode数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

输入案例

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3) 
findMedian() -> 2
class MedianFinder {
    PriorityQueue<Integer> queueMax = new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return b-a;
        }
    });
    PriorityQueue<Integer> queueMin = new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return a-b;
        }
    });
    public int totalLen =0;
    public MedianFinder() { //构造函数
        totalLen = 0;
    }
    
    public void addNum(int num) {   //通过维护大顶堆和小顶推,来维护最小值和最大值
        //思路:就是在中间维护最大和最小顶堆,一直判断新来的元素加左边加右边
        //如果比右边的最小堆大,看右边的数量+1 > (len/2+1),然后移除一个元素,加入左边
        //同理右边
        totalLen++;
        if(totalLen == 1){
            queueMax.add(num); //左区间
            
            return ;
        }
        if(totalLen == 2){
            if(num > queueMax.peek()){ //加右区间
                queueMin.add(num);
            }else{ //加左区间
                queueMax.add(num);
                queueMin.add(queueMax.poll());    
            }
            return ;
        }
        
        if(num > queueMax.peek()){ //加右区间
            queueMin.add(num);
            if(queueMin.size() > (totalLen/2)){ //右边区间多了,挑一个出来,再分配
                queueMax.add(queueMin.poll());
            }
        }else{ //加左区间
            queueMax.add(num);
            if(totalLen %2 != 0){
                if(queueMax.size() > (totalLen/2)+1 ){ //再分配
                    queueMin.add(queueMax.poll());
                }
            }else{
                if(queueMax.size() > totalLen/2 ){ //再分配
                    queueMin.add(queueMax.poll());
                }
            }
        }
    }
    
    public double findMedian() { //找中位数
       
        double ans = 0;
        
        if(totalLen%2==0){ //偶数
            ans = (queueMax.peek() + queueMin.peek())/2.0;
        }else{ //奇数
            ans = (double)queueMax.peek();
        }
       
        
        return ans;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

480滑动窗口中位数

class Solution {
    public double[] medianSlidingWindow(int[] nums, int k) {
        double[] rst = new double[nums.length-k+1];
        PriorityQueue<Integer> big = new PriorityQueue<>();
        PriorityQueue<Integer> small = new PriorityQueue<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer a, Integer b){
                return Integer.compare(b, a);
            }
        });
        HashMap<Integer, Integer> debt = new HashMap<>();

        //balance记录了小值堆和大值堆之间数字的差值
        int i=0, j=k-1, index=0, balance=0;
        //init
        int[] tmpArray = Arrays.copyOfRange(nums, 0, k);
        Arrays.sort(tmpArray);
        int scope = (k&1)==1?k/2:k/2-1;
        for(int m=0; m<=scope; m++)
            small.offer(tmpArray[m]);
        for(int m=scope+1; m<k; m++)
            big.offer(tmpArray[m]);
        rst[index++] = insertRst(small, big, k);
        while(++j < nums.length){
            balance += deleteElment(debt, nums, i++, small, big);
            balance += insertElment(nums, j, small, big);
            makeBalance(debt, small, big, balance);
            rst[index++] = insertRst(small, big, k);
            balance = 0;
        }
        return rst;
    }

    private int deleteElment(HashMap<Integer, Integer> debt, int[] nums, int i, PriorityQueue<Integer> small, PriorityQueue<Integer> big)
    {
        int cur = nums[i];
        debt.put(cur, debt.getOrDefault(cur, 0)+1);
        return !small.isEmpty()&&nums[i]<=small.peek()?-1:1;
    }

    private int insertElment(int[] nums, int j, PriorityQueue<Integer> small, PriorityQueue<Integer> big)
    {
        if(!small.isEmpty() && nums[j]<=small.peek()){
            small.add(nums[j]);
            return 1;
        }
        big.add(nums[j]);
        return -1;
    }

    //balance记录了此时两个堆不平等的情况,需要将其平衡到初始水平,此时如果是正的,就从小堆中删除,加到大堆里,如果是负的,就反过来,平衡完之后,只需要对欠债元素进行删除就可。欠债元素必须先从small里进行删除,因为添加的时候也是优先添加到small,优先删除big中的元素极有可能导致big为空,从而导致添加中位数时出问题
    private void makeBalance(HashMap<Integer, Integer> debt, PriorityQueue<Integer> small, PriorityQueue<Integer> big, int balance)
    {
        assert balance==2||balance==-2||balance==0;
        if(balance>0)
            big.offer(small.poll());
        else if(balance<0)
            small.offer(big.poll());
        while(!small.isEmpty()&&debt.getOrDefault(small.peek(), 0) > 0){
            debt.put(small.peek(), debt.get(small.peek())-1);
            small.poll();
        }
        while(!big.isEmpty()&&debt.getOrDefault(big.peek(), 0) > 0){
            debt.put(big.peek(), debt.get(big.peek())-1);
            big.poll();
        }
    }

    private double insertRst(PriorityQueue<Integer> small, PriorityQueue<Integer> big, int k){
        return (k&1)==1?(double)small.peek():((double)small.peek()+(double)big.peek())/2;
    }
}

[-2147483648,-2147483648,2147483647,-2147483648,-2147483648,-2147483648,2147483647,2147483647,2147483647,2147483647,-2147483648,2147483647,-2147483648] 3

[-2147483648.0,-2147483648.0,-2147483648.0,-2147483648.0,2147483647.0,2147483647.0,2147483647.0,2147483647.0,-2147483648.0,-2147483648.0,-2147483648.0]

[-2147483648.0,-2147483648.0,-2147483648.0,-2147483648.0,-2147483648.0,2147483647.0,2147483647.0,2147483647.0,2147483647.0,2147483647.0,-2147483648.0]

自己的算法:

class Solution {
        PriorityQueue<Integer> queueMax = new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return Integer.compare(b, a); //要用compare,不然大数比不了
        }
    });
    PriorityQueue<Integer> queueMin = new PriorityQueue<Integer>();
    public int totalLen =0;
    public Map<Integer, Integer> debt = new HashMap<Integer,Integer>();; //因为堆算法不能直接删除元素,就记录下来
    public double[] medianSlidingWindow(int[] nums, int k) {
        //有一堆数据,进行排序和重组                                  
        double[] ans = new double[nums.length-k+1];

        //step1:
        for(int j=0;j<k;j++){  //初始化
            addNum(nums[j]); //添加
        }
        int balance = 0; //因本次窗口滑动导致small堆元素数目与big堆元素个数差值的增量。
        ans[0] = findMedian(k); //找第一个中位数
        
        //OP 操作
        for(int i=k;i<nums.length;i++){ //开始变化
           
          
           //先判断删除的
           debt.put(nums[i-k], debt.getOrDefault(nums[i-k], 0)+1); //debt
           if(!queueMax.isEmpty() && nums[i-k] <= queueMax.peek()){ //要删除的元素在左边
               balance--;
           }else{ //删除元素在右边
               balance++;
           }

           //再判断添加的
            if(!queueMax.isEmpty() && nums[i] <= queueMax.peek()){
                balance++;
                queueMax.add(nums[i]);
            }else{
                balance--;
                queueMin.add(nums[i]);
            }

            if(balance == 2){ //左边大于右边
                queueMin.offer(queueMax.poll());
            }
            if(balance == -2){
                queueMax.offer(queueMin.poll());
            }
            //System.out.println(balance);
            //删除堆顶元素
            while(!queueMax.isEmpty() && debt.getOrDefault(queueMax.peek(), 0) > 0){
                debt.put(queueMax.peek(), debt.get(queueMax.peek())-1 );
                queueMax.poll();
            }

            while(!queueMin.isEmpty() && debt.getOrDefault(queueMin.peek(),0) >0){
                debt.put(queueMin.peek(), debt.get(queueMin.peek()) -1);
                queueMin.poll();
            }

           ans[i-k+1] = findMedian(k); 
           balance = 0;
        }
        return ans;
    }



    public void addNum(int num) {   //通过维护大顶堆和小顶推,来维护最小值和最大值
        //思路:就是在中间维护最大和最小顶堆,一直判断新来的元素加左边加右边
        //如果比右边的最小堆大,看右边的数量+1 > (len/2+1),然后移除一个元素,加入左边
        //同理右边。更牛逼的就是只维护中间的10个数字
        totalLen = queueMin.size() + queueMax.size();
        totalLen++;
        if(totalLen == 1){
            queueMax.add(num); //左区间
            return ;
        }
        if(totalLen == 2){
            if(num > queueMax.peek()){ //加右区间
                queueMin.add(num);
            }else{ //加左区间
                queueMax.add(num);
                queueMin.add(queueMax.poll());    
            }
            return ;
        }
        if(num > queueMax.peek()){ //加右区间
            queueMin.add(num);
            if(queueMin.size() > (totalLen/2)){ //右边区间多了,挑一个出来,再分配
                queueMax.add(queueMin.poll());
            }
        }else{ //加左区间
            queueMax.add(num);
            if(totalLen %2 != 0){
                if(queueMax.size() > (totalLen/2)+1 ){ //再分配
                    queueMin.add(queueMax.poll());
                }
            }else{
                if(queueMax.size() > totalLen/2 ){ //再分配
                    queueMin.add(queueMax.poll());
                }
            }
        }
    }
    
    public double findMedian(int k) { //找中位数
        double ans = 0;
        totalLen = k;
        if(totalLen%2==0){ //偶数
            ans = (double)queueMax.peek()/2 + (double)queueMin.peek()/2;
        }else{ //奇数
            ans = (double)queueMax.peek();
        }        
        return ans;
    }
}

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
class Solution {    
    public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<Integer> queueMax = new PriorityQueue<Integer>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return Integer.compare(b, a); //要用compare,不然大数比不了
            }
        });
        Map<Integer, Integer> debt = new HashMap<Integer,Integer>();; //因为堆算法不能直接删除元素,就记录下来
        int[] res = new int[nums.length-k+1];
        //step1:初始化
        for(int j=0;j<k;j++){  //初始化
            queueMax.add(nums[j]);
        }
        res[0] = queueMax.peek();        
        //OP操作
        for(int i=k;i<nums.length;i++){ //开始变化
            //先判断删除的
           debt.put(nums[i-k], debt.getOrDefault(nums[i-k], 0)+1); //debt
           //添加
           queueMax.offer(nums[i]);
           //删除元素
           while(!queueMax.isEmpty() && debt.getOrDefault(queueMax.peek(), 0) > 0){
             debt.put(queueMax.peek(), debt.get(queueMax.peek())-1);
             queueMax.poll();
           }
           res[i-k+1] = queueMax.peek();
        }
        return res;
    }

}

采用双向队列法的解法

class Solution {
    
    public int[] maxSlidingWindow(int[] nums, int k) {
       //双端队列,存的是。L、R存的是下标,最大值是队首元素,每次判断队首元素下标是否在L、R中
       //如果当前遍历的数比队尾的值大,则需要依次弹出队尾值直到比队尾值>当前遍历的值,判断队首的值的数组下标是否在 [L,R] 中,如果不在则需要弹出队首的值

       int L=0, R=0;
       LinkedList<Integer> list = new LinkedList<Integer>(); //存的是下标
       int len = nums.length;
       int[] res = new int[len-k+1];
       for(int i=0;i<len;i++){
           if(list.size() == 0){
               list.add(i);
           }
           else if(nums[i] > nums[list.getLast()] ){//大于队尾元素,
               //依次遍历队尾后加入
               list.pollLast(); //弹出队尾元素
               while(!list.isEmpty() && nums[i] > nums[list.getLast()] ){
                list.pollLast(); //弹出队尾元素
               }
               list.add(i);
           }else{ //小于就直接加入
               list.add(i);
           }

           if(i>=k){
               L++;
           }
           R++;
           //判断队首元素是否在下标中
            if(list.getFirst() < L){
                list.pollFirst(); //弹出队首
            }
            if(i>=(k-1)){
                res[i-k+1] = nums[list.getFirst()];
            }
       }
       if(len <= k){
           res[0] = nums[list.getFirst()];
       }
       return res;

    }

}

128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

输入案例

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

主要思想:减枝+HashSet

class Solution {

    public int longestConsecutive(int[] nums) {
        //判断一遍是否存在 x-1的值,如果有就直接跳过,去重
        HashSet<Integer> set = new HashSet<Integer>();
        for(int i=0;i<nums.length;i++){
            set.add(nums[i]);
        }
        int ans = 0;
        for(int i=0;i<nums.length;i++){
            //减枝
            if(set.contains(nums[i]-1)){
                continue;
            }else{
                int temp = 0;
                for(int j=nums[i];set.contains(j);j++ ){
                    temp++;
                }
                ans = Math.max(temp, ans);
            }   
        }
        return ans;
    }
}

169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

class Solution {
    public int majorityElement(int[] nums) {
        int len = nums.length;
        int ans = 0;
        Map<Integer, Integer> map= new HashMap<Integer, Integer>();
        for(int i=0;i<len;i++){
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
            if(map.getOrDefault(nums[i], 0) > len /2){
                ans = nums[i];
            }
        }
        return ans;
    }
}

高级算法——计数

class Solution {
    public int majorityElement(int[] nums) {
        int temp = 0;
        int num = 0;
        for(int i = 0;i < nums.length; i++){
            if(num == 0 ){
                temp = nums[i];
                num++;
            }else if(temp == nums[i]){
                num++;
            }else{
                num--;
            }
        }
        return temp;
    }
}

双指针

使用双指针一般需要数组有序

392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

class Solution {
    public boolean isSubsequence(String s, String t) {
        char[] s1=s.toCharArray();
        int index=-1;
        for(char c:s1){
            index=t.indexOf(c,index+1);
            if (index==-1){
                return false;
            }
        }
        return true;
    }
}

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入案例:

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
class Solution {
    public int lengthOfLongestSubstring(String s) {
        //寻找最长不含重复字符的最长子串——双指针
        //思想:hashmap+双指针+滑动窗口
        int res = 0;
        Map map = new HashMap<Character, Integer>();
        int l = 0, r = 0;
        int len = s.length();
        if(len == 0 ||len == 1){
            return len;
        }
        //窗口在变化
        while(l<=r && r<len && l<= len){
            if(map.containsKey(s.charAt(r))){ //包含
                map.clear();
                map.put(s.charAt(++l), 1);
                r = l;
            }
            //r++;
            map.put(s.charAt(r), 1);    
            res = Math.max(r-l+1, res);
            r++;
        }
        return res;
    }
}
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128];
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i); //记录之前重复的字符的位置
            ans = Math.max(ans, j - i + 1); //记录答案
            index[s.charAt(j)] = j + 1; //存的是这个字符串的最新位置
        }
        return ans;
    }
}

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NrvRbuQ7-1640224025988)(G:\技术积累\算法笔记.assets\image-20211219145252856.png)]

class Solution {
    public int trap(int[] height) {
        //寻找最大左上沿+ 最大右上沿。双指针---法
        int ans = 0;
        int len = height.length;
        if(len <=2 ){
            return 0;
        }
       int lmax = 0, rmax=0;
       int l=0, r = len-1;
       while(l<len && l<r){
            lmax = Math.max(lmax, height[l]);
            rmax = Math.max(rmax, height[r]);
           if(height[l] < height[r]){
               ans += lmax - height[l];
               l++;
               
           }else{
               ans += rmax - height[r];
               r--;             
           }            
       }
       return ans;
    }
  
}
class Solution {
    public int trap(int[] height) {
      //采用最小栈 ——法,Deque是接口
      Deque<Integer> que = new LinkedList<Integer>();
      int ans = 0;
      
      for(int i=0;i<height.length;i++){
          while(!que.isEmpty() &&  height[i] > height[que.peek()] ){
              int top = que.pop();
              if(que.isEmpty()){
                  break; 
              }
              int left = que.peek();
              int w = i - left -1;
              int h = Math.min(height[left], height[i]) - height[top];
              ans += w*h; 
          }
          que.push(i);
      }
      return ans;
    }
  
}

查找

二分查找

  • bug-free写法:左闭右开,先写排除中位数的逻辑

189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数

class Solution {
    public void rotate(int[] nums, int k) {
        //公式(i+k)%n
        //step1:对k进行模运算
        int len = nums.length;
        k = k%len;
        //step2:根据k的大小将后面的数字整块前移
        int[] ans = new int[200005];
        //先将前面的后移
        for(int i=k;i<len;i++){
            ans[i] = nums[i-k];
        }
        //后面的前移
        for(int i=len-k;i<len;i++){
            ans[i+k-len] = nums[i];
        }

        for(int i=0;i<len;i++){
            nums[i] = ans[i];
        }
    }
}

153. 寻找旋转排序数组中的最小值

class Solution {
    public int findMin(int[] nums) {
        //二分查找
        int low = 0, high = nums.length-1;
        //判断条件
        while(low<high){
            int mid = (low + high)/2;
            //两种情况
            if(nums[mid] < nums[high]){
                high = mid;
            }
            if(nums[mid] > nums[high]){
                low = mid+1;
                
            }
        }
        return nums[low];
    }
}
class Solution {
    public int findMin(int[] nums) {
        //两种情况,1、一直想上升   2、有向下的情况
        
        int len = nums.length;
        for(int i=0;i<len-1;i++){
            if(nums[i] > nums[i+1]){       
                return nums[i+1];
            }
        }
        return nums[0];
    }
}

154. 寻找旋转排序数组中的最小值 II

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xCD2wTI-1640224025989)(G:\技术积累\算法笔记.assets\image-20210901101123817.png)]

class Solution {
    public int findMin(int[] nums) {
         //二分查找
        int low = 0, high = nums.length-1;
        //判断条件
        while(low<high){
            int mid = (low + high)/2;
            //两种情况
            if(nums[mid] < nums[high]){
                high = mid;
            }
            else if(nums[mid] > nums[high]){
                low = mid+1;
            }
            else{
                high-=1;
            }           
        }
        return nums[low];
    }
}

162. 寻找峰值

在简单的二分查找中,我们处理的是一个有序数列,并通过在每一步减少搜索空间来找到所需要的数字。在本例中,我们对二分查找进行一点修改。首先从数组 nums 中找到中间的元素 mid。若该元素恰好位于降序序列或者一个局部下降坡度中(通过将 nums[i] 与右侧比较判断),则说明峰值会在本元素的左边。于是,我们将搜索空间缩小为 mid 的左边(包括其本身),并在左侧子数组上重复上述过程。

若该元素恰好位于升序序列或者一个局部上升坡度中(通过将 nums[i] 与右侧比较判断),则说明峰值会在本元素的右边。于是,我们将搜索空间缩小为 mid 的右边,并在右侧子数组上重复上述过程。

public class Solution {
    public int findPeakElement(int[] nums) {
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int mid = (l + r) / 2;
            if (nums[mid] > nums[mid + 1])
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
}

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

方法一:哈希表

class Solution {
    public int findDuplicate(int[] nums) {
        HashSet<Integer> set = new HashSet<Integer>();
        int len = nums.length;
        for(int i=0;i<len;i++){
            if(!set.contains(nums[i])){
                set.add(nums[i]);
            }
            else
                return nums[i];
        }
        return 0;
    }
}

方法二:二分查找

4. 寻找两个正序数组的中位数

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
nums1 中小于等于 pivot1 的元素有 nums1[0 … k/2-2] 共计 k/2-1 个

nums2 中小于等于 pivot2 的元素有 nums2[0 … k/2-2] 共计 k/2-1 个
pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个

这样 pivot 本身最大也只能是第 k-1 小的元素
pivot = pivot1,那么 nums1[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组

如果 pivot = pivot2,那么 nums2[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组
们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        /*寻找中位数:两种情况
        1、总数是偶数 寻找下标(m+n)/2-1,(m+n)/2  
        2、总数是奇数 下标(m+n)/2 
        时间复杂度O(log(m+n))
        */
        int lenA = nums1.length, lenB=nums2.length;
        int totallen = lenA+lenB;
        if(totallen%2==1){
            double m = findIndexValue(nums1, nums2, (lenA+lenB)/2+1);
            
            return m;
        }else{
            int ta = findIndexValue(nums1, nums2, (lenA+lenB)/2);
            System.out.println(ta);
            int tb = findIndexValue(nums1, nums2, (lenA+lenB)/2 + 1);
            System.out.println(tb);
            double m = (findIndexValue(nums1, nums2, (lenA+lenB)/2) + findIndexValue(nums1, nums2, (lenA+lenB)/2+1))/2.0;
            return m;
        }
    }
    //两个有序数组寻找第k个元素
    public int findIndexValue(int[] nums1, int[] nums2, int k){
        int lenA = nums1.length, lenB=nums2.length;
        //int totallen = lenA+lenB;
        int indexA=0, indexB=0;
        while(true){
            //边界情况
            if(indexA == lenA){
                return nums2[indexB + k-1];
            }
            if(indexB == lenB){
                return nums1[indexA+k-1];
            }
            if(k==1){
                //只有一个
                return Math.min(nums1[indexA], nums2[indexB]);
            }
            //正常情况
            int half = k/2;
            int newIndexA=Math.min(indexA+half, lenA) - 1;
            int newIndexB=Math.min(indexB+half, lenB) - 1;
            int p1=nums1[newIndexA], p2=nums2[newIndexB];
            if(p1 <= p2){
                k-=(newIndexA-indexA+1);
                indexA=newIndexA+1;
            }else{
                k-=(newIndexB-indexB+1);
                indexB=newIndexB+1;
            }
        }

    }
}
668. 乘法表中第k小的数

在乘法表中寻找第k个值

输入: m = 3, n = 3, k = 5
输出: 3
解释:
乘法表:
1 2 3
2 4 6
3 6 9

第5小的数字是 3 (1, 2, 2, 3, 3).

class Solution {
    public int findKthNumber(int m, int n, int k) {
        //思路:二分查找
        //通过判断enought(x), 当x的值>最大第k值的时候为true
        int l = 1, r = m*n;
        
        while(l<r){
            int mid = l+(r-l)/2;
            if(enough(mid,m,n,k)){
                r = mid;
            }else{
                l = mid+1;
            }
        }
        return l;
        
    }
    public boolean enough(int x, int m, int n, int k){
        int count = 0;
        for(int i=1;i<=m;i++){
            count += Math.min(x/i,n);
        }
        return count >= k;
    }
}
719. 找出第 k 小的距离对

输入:
nums = [1,3,1]
k = 1
输出:0
解释:
所有数对如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
因此第 1 个最小距离的数对是 (1,1),它们之间的距离为 0。

前缀和+二分查找

class Solution {
    public int smallestDistancePair(int[] nums, int k) {
        //先排序
        Arrays.sort(nums);
        int len = nums.length;
        //数组对的最小距离是0,最大距离为max - min
        int left = 0, right = nums[len - 1] - nums[0];
        while(left < right){
            //中间的值
            int mid = left+(right - left)/2;

            int count = 0;
            int pl=0;
            for(int pr=0;pr<len;pr++){
                while(nums[pr] - nums[pl] > mid){
                    pl++;
                }
                //以pr为结尾结果相加
                count += pr - pl;
                
            }
            if(count >= k){
                right = mid;
            }else{
                left = mid+1;
            }
        }
        return left;
    }
}

数组

53. 最大子序和 leetcode

DP动态规划思路

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

class Solution {
    public int maxSubArray(int[] nums) {
        //dp 动态规划算法 推导公式  dp[i] = MAX(dp[i-1]+nums[i], dp[i])。考虑是否要添加最新的元素
        int len = nums.length;
        int[] dp = new int[len];
        int max = Integer.MIN_VALUE;
        if(len == 1){
            return nums[0];
        }
        dp[0] = nums[0];
        max = dp[0];
        /*动态规划 */
        for(int i=1;i<len;i++){
            dp[i] = Math.max(dp[i-1]+nums[i], nums[i]);
            max = Math.max(dp[i], max);
        }
        return max;
        
    }
}
152.乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

class Solution {
    public int maxProduct(int[] nums) {
        //最大乘积 状态转移公式 dp[i] = Math.max();
        //计算最大的正数+最小的负数
        int len = nums.length;
        int max = 0;
        int[] dpmax = new int[len+1];
        int[] dpmin = new int[len+1];

        if(len == 1){
            return nums[0];
        }
        dpmax[0] = nums[0];
        dpmin[0] = nums[0];
        max = nums[0];
        for(int i=1;i<len;i++){
            //状态转移公式
            dpmax[i] = Math.max(Math.max(dpmax[i-1]*nums[i], dpmin[i-1]*nums[i]), nums[i]);
            dpmin[i] = Math.min(Math.min(dpmax[i-1]*nums[i], dpmin[i-1]*nums[i]), nums[i]);
            max = Math.max(dpmax[i], max);
        }
        return max;
        
    }
}

广度优先算法

/*
 *访问所有数组里的值
 */
import java.util.LinkedList;
import java.util.Queue;

public class 广度优先搜索算法 {
    private int r=4;//行
    private int c=4;//列
    //数组
    private int[][] graph=new int[][]{{1,2,3,4},
                                        {5,6,7,8},
                                        {9,10,11,12},
                                        {13,14,15,16}};
    private int[][] gr=new int[r][c];//标记
    int[][] rc=new int[][]{{0,-1},{-1,0},{0,1},{1,0}};//左上右下,四个方向
    public static void main(String[] args) {
        new 广度优先搜索算法().BFS();
    }
    //方法内部类,定义数据结构
    class Node{
        int r;//行
        int c;//列
        int k;//第几波被访问的
        Node(int r,int c,int k){
            this.r=r;
            this.c=c;
            this.k=k;
        }
    }
    private void BFS() {
        // TODO Auto-generated method stub
        Node node=new Node(0,0,0);//初始化,从0,0开始,
        gr[0][0]=1;//0,0默认已访问过
        Queue<广度优先搜索算法.Node> que=new LinkedList<广度优先搜索算法.Node>();//初始化队列
        que.offer(node);//把初始化过的node传入队列
        while (!que.isEmpty()) {
            Node tem=que.poll();//获取并移除队列头
            for(int i=0;i<4;i++){//循环四次,四个方向
                int newr=tem.r+rc[i][0];//新的行
                int newc=tem.c+rc[i][1];//新的列
                if(newr<0||newc<0||newr>=r||newc>=c)continue;//如果新的行和列超出范围就跳过这次循环
                if(gr[newr][newc]!=0)continue;//如果新的节点已被访问也跳过此次循环
                gr[newr][newc]=1;//标记当前节点已被访问
                que.offer(new Node(newr,newc,tem.k+1));//加入队列
                //输出遍历到数组的值
                System.out.println(graph[newr][newc]+" "+(tem.k+1));
            }
        }
    }
}

#include<stdio.h>

int n;
int a[10],book[10];

//递归核心代码
void dfs(int step)
{
    if(step == n+1){    //递归结束条件
        for(int i=1;i<=n;i++){
            printf("%d",a[i]);
        }
        printf("\n");
        
        return; //返回上一步
    }else{
        for(int i=1;i<=n;i++){//当下该怎么做
            if(book[i]==0){
                book[i]=1;    //进行放入
                a[step]=i;    //放入第step步
                dfs(step+1);  //深度下一节
                book[i]=0;    //进行回收(非常重要的一步)
            }
        }
        return;
    }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    getchar();
    return 0;
}

深度优先搜索的模型。理解深度优先搜索的关键在于解决“当下该如何做”。至于“下一步如何做”,则与“当下该如何做”事一样的。

void dfs(int step)
{
  //判断边界
  //尝试每一种可能 
  for(int i = 1; i <= n; i++){
      //继续下一步
      dfs(step+1);
  }
}

排序

  • 归并排序
  • 快速排序
  • 堆排序

稳定和不稳定排序——就是相等元素的相对位置可能会发生改变就是不稳定排序,如果

归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而**治(conquer)**的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。 其递归算法复杂度O(logn)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyeMb8IC-1640224025990)(G:\技术积累\算法笔记.assets\1024555-20161218163120151-452283750.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yGMmPp1A-1640224025991)(G:\技术积累\算法笔记.assets\1024555-20161218194508761-468169540.png)]

由上图可知分的时间复杂度为O(logn),治的复杂度为O(n)。因此这个总复杂度是O(nlogn)。

归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

public class Solution {
    /**
     * @param A: an integer array
     * @return: nothing
     */
    public void sortIntegers2(int[] A) {
        // 分治算法实现
         //预先创建好数组,避免递归太深产生内存消耗
        int len = A.length;
        int[] temp = new int[len+10];
        sort(0, len-1, A, temp); 
    }

    public void sort(int left,int right, int[] array, int[] temp){
        //返回条件
        if(left < right){
            int mid = left + (right - left)/2;
            //divide
            sort(left, mid, array, temp);
            sort(mid+1, right, array, temp);
            //merge
            merge(left, right, array, temp);
        }
    }
    /*治的过程*/
    public void merge(int left, int right, int[] array, int[] temp){
        int mid = left + (right - left)/2;
        int i=left;
        int j=mid+1;
        int index = 0;
        while(i<=mid && j<=right){
            temp[index++] = array[i] > array[j] ? array[j++]:array[i++]; 
        }
        //残留数组
        while(i<=mid){
            temp[index++] = array[i++];
        }
        //残留数组
        while(j<=right){
            temp[index++] = array[j++];
        }
        // for( i=0;i<index;i++){
        //     System.out.print(temp[i]);
        // }
        System.out.println();
        index = 0;
        //复制数组
        for(i=left;i<=right;i++){
            array[i] = temp[index++];
        }
    }
}

快速排序

快排的思想:就是取第一个数字作为基准temp,然后利用plow、phigh两个指针进行遍历,将array[phigh] < temp,然后交换 。

public class Solution {
    /**
     * @param A: an integer array
     * @return: nothing
     */
    public void sortIntegers2(int[] A) {
        // 分治算法实现
         //预先创建好数组
        int len = A.length;
        //int[] temp = new int[len+10];
        quickSort(0, len-1, A); 

    }
    /*递归实现*/
    public void quickSort(int low, int high,int[] array){
        if(low < high){
            int baseStandardNum = findBaseNum(low, high, array);
            quickSort(low, baseStandardNum-1, array);
            quickSort(baseStandardNum+1, high, array);
        }
    }
    /*找到基准数字的位置*/
    public int findBaseNum(int low, int high, int[] array){
        int baseNum = array[low];
        int plow = low, phigh = high;
        while(plow < phigh){
            while(plow < phigh && array[phigh] >= baseNum){
                phigh--;
            }
            array[plow] = array[phigh];
            //plow++;
            while(plow < phigh && array[plow] <= baseNum){
                plow++;
            }
            array[phigh] = array[plow];
            //phigh--;
        }
        array[plow] = baseNum;
        // System.out.println("low:"+low+" high:"+high);
        // for(int i=low;i<=high;i++){
        //     System.out.print(array[i]);
        // }
        // System.out.println();
        return plow;
    }

}

堆排序

public class Solution {
    /**
     * @param A: an integer array
     * @return: nothing
     */
    public void sortIntegers2(int[] A) {
        // 堆排序实现
        /*Step1:建立初始堆 Step2:数组尾和头进行交换*/
        int len = A.length;

        for(int i=len/2-1;i>=0;i--){
            adjust(i, A, len);
            /*for(int j=0;j<len;j++){
                System.out.print(A[j]);
            }
            System.out.println();*/
        }

        /*Step2:数组尾和头进行交换*/
        for(int i=len-1;i>0;i--){
            //swap
            int temp = A[i];
            A[i] = A[0];
            A[0] = temp;

            adjust(0, A, i);

            /*for(int j=0;j<len;j++){
                System.out.print(A[j]);
            }
            System.out.println();*/
        }

    }
    //调成大顶堆
    public void adjust(int index, int[] array, int len){
        int temp = array[index];
        for(int k=index*2+1;k<len;k=k*2+1){
            if( k+1 < len && array[k] < array[k+1] ){
                k++;
            }
            if(array[k] > temp){ //交换
                array[index] = array[k];
                index = k;
            }else{
                break;
            }    
        }

        array[index] = temp;
    }
}

347、前K个高频元素

利用优先队列来实现大顶堆的实现,直接使用优先对垒代替堆排序

PriorityQueue 优先队列学习

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int num: nums){
            map.put(num, map.getOrDefault(num,0)+1);
        }
        PriorityQueue<int[]> queue = new PriorityQueue<int[]> (new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] - b[1];
            }
        });

        for(Map.Entry<Integer, Integer> entry:map.entrySet()){
            int num = entry.getKey(), count = entry.getValue();
            if(queue.size() == k){//预先处理了,让最小的元素的新加进来的比较一下。如果新来的比较大,就替换到之前队列中最后的元素
                if(queue.peek()[1] < count) {
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            }
            else{
                queue.offer(new int[]{num, count});
            }
        }
        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = queue.poll()[0];
        }
        return res;
    }
}

532 LinCode逆序对

采用归并发来算

public class Solution {
    /**
     * @param A: an array
     * @return: total of reverse pairs
     */
    public int ans = 0;
    public long reversePairs(int[] A) {
        //采用分治的思想
        int len = A.length;
        int[] temp = new int[len+10];
        sort(0, len-1, A,temp);
        return ans;
    }
    public void sort(int left,int right,int[] array,int[] temp){
        //返回条件
        if(left < right){
            int mid = left + (right - left)/2;
            sort(left, mid,array, temp);
            sort(mid+1, right,array, temp);
            merge(left, right, array, temp);
        }
    }
    public void merge(int left, int right,int[] array, int[] temp){
        int mid = left + (right - left)/2;
        int i = left;
        int j=mid+1;
        int index = 0;
        int temp_ans = 0;
        //先算答案
        for(int left_i=left;left_i<=mid;left_i++){
            for(int right_j=mid+1;right_j<=right;right_j++){
                if(array[left_i] > array[right_j]){
                    temp_ans++;
                }
                else
                    continue;
            }
        }
       // System.out.println("temp:" + temp_ans + "right:"+right+" left:"+left);
        ans += temp_ans;
         while(i<=mid && j<=right){
            temp[index++] = array[i] > array[j] ? array[j++]:array[i++]; 
        }
        //残留数组
        while(i<=mid){
            temp[index++] = array[i++];
        }
        //残留数组
        while(j<=right){
            temp[index++] = array[j++];
        }

        index = 0;
        //复制数组
        for(i=left;i<=right;i++){
            array[i] = temp[index++];
        }

    }
}

144、交错正负数

给出一个含有正整数和负整数的数组,重新排列成一个正负数交错的数组。

public class Solution {
    /*
     * @param A: An integer array.
     * @return: nothing
     */
    public void rerange(int[] A) {
        //思路:就是先判断正负数哪个最多,然后将多的那组放在前面,少的在后面。然后一个个交换
        //一次快排
        int plow = 0, phigh = A.length-1;
        int countPos =0, countNeg=0;
        int flag = 0;
        for(int i=0;i<A.length;i++){
            if(A[i] >0)
                countPos++;
            else
                countNeg++;
        }
        flag = countNeg > countPos ? -1 : 1;
        int temp = 0;
        while(plow <= phigh){    
            while(plow <= phigh && A[plow]*flag > 0 ){ //多加一次关键
                plow++;    
            }
            //temp = A[phigh];
            //A[phigh] = A[plow];
            while(plow <= phigh && A[phigh]*flag < 0){
                phigh--;
            }
            //A[]
            //swap
            if(plow <= phigh){
                temp = A[plow];
                A[plow] = A[phigh];
                A[phigh] = temp;
                plow++;
                phigh--;
            }
        }
        for(int i=0;i<A.length;i++){
            System.out.print(A[i] + " ");
        }
        System.out.println(plow);

        //step2 交换
        int left = 1, right = plow;
        while(left < right && right <A.length){
            temp = A[left];
            A[left] = A[right];
            A[right] = temp;
            left+=2;
            right++;            
        }
        
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值