手撕算法

数组

    • 索引为数字,存放出现次数。
    • 数组往右递增,往下递增。可从右上角遍历,过大往左,过小往下,溢出报错,找到返回。
    • 迭代优于递归。
    • 首先判断是否为没有旋转的数组,最左<最右
    • 循环中依次判断找到的情况,无法应用二分查找的情况,排除递增序列的情况。
    • 注意n很大的情况。

    • 利用递归全排列的方式,输出时考虑前面的0不显示。

    • 维护两个指针,前偶后奇则调换。
    • 外圈打印的条件:start*2<min(row,col)
    • 从左往右不约束
    • 从上往下需要2行:start<end_y
    • 从右往左需要满足2列:start<end_y&&start<end_x
    • 从下往上需要3行:start<end_y-1&&start<end_x
  • partition思想找出中位数,或记录出现次数。

  • topK问题。

    public class TopK {
        public static void main(String[] args) {
            Integer[] arr = {3,2,5,8,71,3,69,5,4,9,82,5,55,96,485,357};
            System.out.println(top(Arrays.asList(arr),5));
        }
        public static <T extends Comparable<T>> List<T> top(List<T> list, int top){
            if (list == null){
                throw new RuntimeException("list is null!");
            }
            PriorityQueue<T> queue = new PriorityQueue<>(top);
            list.forEach(t -> {
                if (queue.size() < top){
                    queue.add(t);
                }else if (queue.peek().compareTo(t) < 0){
                    queue.poll();
                    queue.add(t);
                }
            });
            LinkedList<T> arr = new LinkedList<>();
            while (!queue.isEmpty()){
                arr.addFirst(queue.poll());
            }
            return arr;
        }
    }
    
  • 维护一个最小堆和最大堆,数目之差不能超过1,最小堆的所有数据要大于最大堆。

    class MedianFinder {
        //最小堆
        Queue<Integer> min_queue;
        //最大堆
        Queue<Integer> max_queue;
        /** initialize your data structure here. */
        public MedianFinder() {
            this.min_queue=new PriorityQueue<>((o1,o2)->o1-o2);
            this.max_queue=new PriorityQueue<>((o1,o2)->o2-o1);
        }
        
        public void addNum(int num) {
            if(min_queue.size()==max_queue.size()){
                //插入小顶堆
                if(min_queue.isEmpty()||num>=max_queue.peek()){
                    min_queue.offer(num);
                }else{
                    min_queue.offer(max_queue.poll());
                    max_queue.offer(num);
                }
            }else{
                //插入大顶堆
                if(num<=min_queue.peek()){
                    max_queue.offer(num);
                }else{
                    max_queue.offer(min_queue.poll());
                    min_queue.offer(num);
                }
            }
        }
        
        public double findMedian() {
            if(min_queue.size()==max_queue.size()){
                return ((double)min_queue.peek()+(double)max_queue.peek())/2;
            }else{
                return (double)min_queue.peek();
            }
        }
    }
    
    /**
     * Your MedianFinder object will be instantiated and called as such:
     * MedianFinder obj = new MedianFinder();
     * obj.addNum(num);
     * double param_2 = obj.findMedian();
     */
    
  • 分析数字1的出现规律。

  • 0123456789101112131415...y

  • 数组类型必须为封装类型,重写Comparator中的compare方法。

    class Solution {
        public String minNumber(int[] nums) {
            String[] strs = new String[nums.length];
            for (int i = 0; i < nums.length; i++) {
                strs[i] = String.valueOf(nums[i]);
            }
            Arrays.sort(strs, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    String o12 = o1 + o2;
                    String o21 = o2 + o1;
                    for (int i = 0; i < o12.length(); i++) {
                        if (o12.charAt(i) != o21.charAt(i)) {
                            return o12.charAt(i) - o21.charAt(i);
                        }
                    }
                    return 0;
                }
            });
            StringBuilder sb = new StringBuilder();
            for (String s : strs) {
                sb.append(s);
            }
            return sb.toString();
        }
    }
    
  • 滑动窗口,Set维护不重复的字符

    class Solution {
        public int lengthOfLongestSubstring(String s) {
            if(s.length()<2){
                return s.length();
            }
            int i=0;
            int j=0;
            Set<Character> set=new HashSet<Character>();
            int max=0;
            while(j<s.length()){
                if(!set.contains(s.charAt(j))){
                    max=Math.max(max,j-i+1);
                    set.add(s.charAt(j));
                    j++;
                }else{
                    set.remove(s.charAt(i));
                    i++;
                }
            }
            return max;
        }
    }
    
  • 创建数组保存已有的丑树,空间换时间

  • 归并排序(较难)

  • 二分查找的判定条件

  • 一个数只出现1次,其他数出现2次

    数组异或的结果。

    两个数各出现1次,其他数出现2次

    数组异或的某位为1,根据该位拆分数组2份(两个出现一次的数分组)

        public int[] singleNumbers(int[] nums) {
            int res = 0;
            for (int num : nums) {
                res ^= num;
            }
            //得到res的最后一个1所在的位置,根据该位置区分
            res = res - (res & (res - 1));
            List<Integer> list1 = new ArrayList<>();
            List<Integer> list2 = new ArrayList<>();
            for (int num : nums) {
                if ((num & res) == 0) {
                    list1.add(num);
                } else {
                    list2.add(num);
                }
            }
            int[] ans = new int[2];
            for (int num : list1) {
                ans[0] ^= num;
            }
            for (int num : list2) {
                ans[1] ^= num;
            }
            return ans;
        }
    

    一个数出现1次,其他数出现3次

    将所有数的二进制的每一位相加,能被3整除,该位就是0,否则为1,状态机

    class Solution {
        //(另非)与(自异)
        public int singleNumber(int[] nums) {
            int i=0;
            int j=0;
            //另一位是1,该位不能为1
            //该位不考虑进位的加法(异或)
            for(int n:nums){
                i=(~j)&(i^n);
                j=(~i)&(j^n);
            }
            return i;
        }
    }
    
  • 双指针,一个从前往后,一个从后往前

  • 翻转前n个+翻转后面部分+翻转整个字符串=左旋n位

  • public class QuickSort{
        public void quickSort(int[] nums,int head,int tail){
            int i=head;
            int j=tail;
            int index=nums[i];
            while(i<j){
                while(i<j&&nums[j]>=index){
                    j--;
                }
                if(i<j){
                    nums[i++]=nums[j];
                }
                while(i<j&&nums[i]<index){
                    i++;
                }
                if(i<j){
                    nums[j--]=nums[i];
                }
                nums[i]=index;
                quickSort(nums,head,i-1);
                quickSort(nums,i+1,tail);
            }
        }
    }
    
  • public class MergeSort {
        private static void mergeSort(int[] arrays, int head, int tail) {
            if (head < tail) {
                int mid = (head + tail) / 2;
                mergeSort(arrays, head, mid);
                mergeSort(arrays, mid + 1, tail);
                merge(arrays, head, mid, tail);
            }
        }
        private static void merge(int[] arrays, int head, int mid, int tail) {
            int[] new_arrays = new int[tail - head + 1];
            int i = head;
            int j = mid + 1;
            int index = 0;
            while (i <= mid && j <= tail) {
                if (arrays[i] <= arrays[j]) {
                    new_arrays[index] = arrays[i];
                    i++;
                } else {
                    new_arrays[index] = arrays[j];
                    j++;
                }
                index++;
            }
            while (i <= mid) {
                new_arrays[index++] = arrays[i++];
    
            }
            while (j <= tail) {
                new_arrays[index++] = arrays[j++];
            }
            for (int k = head; k <= tail; k++) {
                arrays[k] = new_arrays[k - head];
            }
        }
    }
    

字符串

  • 空格替换为较长的字符串,从尾到头复制。

  • 字符的出现次数HashMap

链表

  • 利用栈(也可反转)。

  • 下一个节点的值拷贝,该节点指向下下个。

  • 1、快慢指针判断是否有环。2、得到环中节点数目。3、指针2移动节点数目,同时移动直至相遇则是入口节点。

  • class Solution {
        public ListNode reverseList(ListNode head) {
            if(head==null||head.next==null)
                return head;
            ListNode cur= reverseList(head.next);
            head.next.next=head;
            head.next=null;
            return cur;
        }
    }
    
  • public class Solution {
        public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
            if(headA==null||headB==null){
                return null;
            }
            //用栈的思想,从后往前比较
            Stack<ListNode> stack1=new Stack<>();
            Stack<ListNode> stack2=new Stack<>();
            while(headA!=null){
                stack1.push(headA);
                headA=headA.next;
            }
            while(headB!=null){
                stack2.push(headB);
                headB=headB.next;
            }
            ListNode cur=null;
            while((!stack1.isEmpty())&&(!stack2.isEmpty())&&(stack1.peek()==stack2.peek())){
                cur=stack1.pop();
                stack2.pop();
            }
            return cur;
        }
    }
    

二叉树

  • public class PreorderTraversal {   
    	public List<Integer> preorderTraversal(TreeNode root) {
            List<Integer> list = new ArrayList<>();
            Stack<TreeNode> stack = new Stack<>();
            TreeNode cur = root;
            while (!stack.isEmpty() || cur != null) {
                while (cur != null) {
                    //先序cur的值
                    list.add(cur.val);
                    stack.push(cur);
                    cur = cur.left;
                }
                cur = stack.pop();
                //中序
                cur = cur.right;
            }
            return list;
        }
    }
    
  • public class PostorderTraversal {
        public List<Integer> postorderTraversal(TreeNode root) {
            LinkedList<TreeNode> stack = new LinkedList<>();
            LinkedList<Integer> output = new LinkedList<>();
            if (root == null) {
                return output;
            }
            stack.add(root);
            while (!stack.isEmpty()) {
                TreeNode node = stack.pollLast();
                output.addFirst(node.val);
                if (node.left != null) {
                    stack.add(node.left);
                }
                if (node.right != null) {
                    stack.add(node.right);
                }
            }
            return output;
        }
    }
    
  • 根据前序遍历和中序遍历构造二叉树。

  • 递归遍历二叉树,判断树A中以R为根节点的树是否与树B具有相同的数据结构。

    class Solution {
        public boolean isSubStructure(TreeNode A, TreeNode B) {
            if(B==null||A==null){
                return false;
            }
            //A为根节点与B相等
    		if(isEqual(A,B)){
                return true;
            }
            //A的左右子树是否包含B
            return isSub(A.left, B)||isSub(A.right,B);
        }
       	private boolean isSub(TreeNode A, TreeNode B) {
            if(B==null){
                return true;
            }
            if(A==null){
                return false;
            }
            //A为根节点与B相等
    		if(isEqual(A,B)){
                return true;
            }
            //A的左右子树是否包含B
            return isSub(A.left, B)||isSub(A.right,B);
        }
        private boolean isEqual(TreeNode A, TreeNode B){
            if(B==null){
                return true;
            }
            if(A==null){
            	return false;
            }
            if(A.val!=B.val){
                return false;
            }
            return isEqual(A.left,B.left)&&isEqual(A.right,B.right);
        }
    }
    
  • public class MirrorTree {
        public TreeNode mirrorTree(TreeNode root) {
            //空节点直接返回
            if(root==null){
                return root;
            }
            //交换左右节点
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
            //左右节点分别镜像
            mirrorTree(root.left);
            mirrorTree(root.right);
            
        }
    }
          
    
  • public class IsSymmetric {
        public boolean isSymmetric(TreeNode root) {
     		return isSym(root,root);
        }
        
        private boolean isSym(TreeNode root1, TreeNode root2){
            //节点均为空
            if(root1==null && root2==null){
                return true;
            }
            //节点只有一个为空
            if(root1==null || root2==null){
                return false;
            }
            //对称点的取值不相等
            if(root1.val != root2.val){
                return false;
            }
            return isSym(root1.left,root2.right) && isSym(root1.right,root2.left);
        }
    }
    
    /** 非递归实现*/
    public boolean isSymmetric(TreeNode root) {
        //队列
        Queue<TreeNode> queue = new LinkedList<>();
        if (root == null)
            return true;
        //左子节点和右子节点同时入队
        queue.add(root.left);
        queue.add(root.right);
        //如果队列不为空就继续循环
        while (!queue.isEmpty()) {
            //每两个出队
            TreeNode left = queue.poll(), right = queue.poll();
            //如果都为空继续循环
            if (left == null && right == null)
                continue;
            //如果一个为空一个不为空,说明不是对称的,直接返回false
            if (left == null ^ right == null)
                return false;
            //如果这两个值不相同,也不是对称的,直接返回false
            if (left.val != right.val)
                return false;
            //这里要记住入队的顺序,他会每两个两个的出队。
            //左子节点的左子节点和右子节点的右子节点同时
            //入队,因为他俩会同时比较。
            //左子节点的右子节点和右子节点的左子节点同时入队,
            //因为他俩会同时比较
            queue.add(left.left);
            queue.add(right.right);
            queue.add(left.right);
            queue.add(right.left);
        }
        return true;
    }
    
    • 队列
    • 从根节点到叶节点形成一条路径。
  • root节点的左边为左子树的最右节点,右边为右子树的最左节点。

    public class TreeToDoublyList {
        public Node treeToDoublyList(Node root) {
            if (root == null) {
                return null;
            }
            solve(root);
            Node l = root;
            Node r = root;
            while (l.left != null) {
                l = l.left;
            }
            while (r.right != null) {
                r = r.right;
            }
            l.left = r;
            r.right = l;
            return l;
        }
    
        private void solve(Node root) {
            if (root.left != null) {
                solve(root.left);
                Node left = findLeftMax(root.left);
                root.left = left;
                left.right = root;
            }
            if (root.right != null) {
                solve(root.right);
                Node right = findeRightMin(root.right);
                root.right = right;
                right.left = root;
            }
    
        }
    
        private Node findeRightMin(Node right) {
            if (right.left == null) {
                return right;
            }
            return findeRightMin(right.left);
        }
    
        private Node findLeftMax(Node left) {
            if (left.right == null) {
                return left;
            }
            return findLeftMax(left.right);
        }
    }
    
  • 用'$'表示空节点。

  • 二叉搜索树,先遍历右子树,再遍历根节点,最后遍历左节点

    class Solution {
        private static int n;
        private static int res;
        public int kthLargest(TreeNode root, int k) {
            n=k;
            find(root);
            return res;
        }
        public void find(TreeNode root){
            if(root==null){
                return;
            }
            find(root.right);
            if(n<=0){
                return;
            }
            if(--n==0){
                res=root.val;
                return;
            }
            find(root.left);
        }
    }
    
  • 1+max(左子树的深度,右子树的深度)

    public class MaxDepth {
        public int maxDepth(TreeNode root) {
            return (root != null)? (1+Math.max(maxDepth(root.left),maxDepth(root.right))) : 0;
        }
    }
    
  • public class IsBalanced {
        public boolean isBalanced(TreeNode root) {
            return recur(root) != -1;
        }
    
        //返回root的高度
        public int recur(TreeNode root) {
            if (root == null) {
                return 0;
            }
            int left = recur(root.left);
            int right = recur(root.right);
            if (left == -1 || right == -1) {
                return -1;
            }
            if (Math.abs(left - right) < 2) {
                return 1 + Math.max(left, right);
            }
            return -1;
        }
    }
    
  • class Solution {
        TreeNode node=null;
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            dfs(root,p,q);
            return node;
        }
        public boolean dfs(TreeNode root, TreeNode p, TreeNode q){
            if(root==null){
                return false;
            }
            boolean lson=dfs(root.left,p,q);
            boolean rson=dfs(root.right,p,q);
            //左子树和右子树都有节点
            if(lson&&rson){
                node=root;
            }
            //本身为节点且左子树或右子树有节点
            if((root.val==p.val||root.val==q.val)&&(lson||rson)){
                node=root;
            }
            return lson||rson||root.val==p.val||root.val==q.val;
        }
    }
    

堆栈

  • 栈2不为空直接弹出,否则栈1压入栈2再弹出。

  • 队列头维护最大值的索引,有更大值或者超过滑动窗口宽度后弹出后面的数。

    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            if(nums == null || nums.length == 0) {
                return new int[0];
            }
            int[] res = new int[nums.length - k + 1];
            //queue存放索引
            Deque<Integer> queue = new ArrayDeque<>();
            for(int i = 0, j = 0; i < nums.length; i++) {
                //不为空,且超过长度则队列poll
                if(!queue.isEmpty() && i - queue.peek() >= k) {
                    queue.poll();
                }
                //加入新的数,poll出比它小的数
                while(!queue.isEmpty() && nums[i] > nums[queue.peekLast()]) {
                    queue.pollLast();
                }
                //offer相当于add,不会抛出异常而是返回错误
                queue.offer(i);
                //赋滑动窗口的最大值
                if(i >= k - 1) {
                    res[j++] = nums[queue.peek()];
                }
            }
            return res;
        }
    }
    

动态规划

  • 长度不小于5时,尽可能多剪长度为3的绳子。

    • 绳子长度>1,至少剪成两段,答案需要取模 1e9+7(1000000007
    class Solution {
        public int cuttingRope(int n) {
            //n<4,切出长度为1
            return n<4?n-1:(int)cut(n);
        }
        public long cut(long n) {
            //n=4为本身,否则n-3
            return n<=4?n:(cut(n-3)*3)%1000000007;
        }
    }
    
  • class Solution {
        public double myPow(double x, int n) {
            return n<0?1/p(x,-n):p(x,n);
        }
        public double p(double x, int n){
            if(n==0){
                return 1;
            }
            if(n==2){
                return x*x;
            }
            return n%2==0?p(p(x,n/2),2):p(p(x,n/2),2)*x;
        }
    }
    
  • class Solution {
        public int maxSubArray(int[] nums) {
            if(nums.length==0){
                return 0;
            }
            int[] res=new int[nums.length];
            res[0]=nums[0];
            int max=res[0];
            for(int i=1;i<nums.length;i++){
                res[i]=Math.max(nums[i],nums[i]+res[i-1]);
                max=Math.max(max,res[i]);
            }
            return max;
        }
    }
    
  • f(i)=f(i+1)+g(i,i+1)*f(i+2)
    
  • 棋盘的左上角到达右下角

    得到礼物的价值最多。

  • class Solution {
        public int trap(int[] height) {
            int len=height.length;
            if(len<=2){
                return 0;
            }
            int[] left=new int[len];
            int[] right=new int[len];
            //根据左边界,该位置能存储的最大水量
            for(int i=1;i<len-1;i++){
                left[i]=Math.max(left[i-1],height[i-1]);
            }
            //根据右边界,该位置能存储的最大水量
            for(int i=len-2;i>0;i--){
                right[i]=Math.max(right[i+1],height[i+1]);
            }
            int sum=0;
            for(int i=1;i<len;i++){
                sum+=Math.max(Math.min(left[i],right[i])-height[i],0);
            }
            return sum;
    
        }
    }
    

位运算

  • n&(n-1):去除最右边的1。

抽象建模能力

  • //构造二维数组,用n-1的骰子数量求n的骰子数量
    class Solution {
        //骰子的面
        private static final int count = 6;
    
        public double[] twoSum(int n) {
            double[][] res = new double[n][n * count];
            for (int i = 0; i < count; i++) {
                res[0][i] = 1;
            }
            for (int i = 1; i < n; i++) {
                for (int j = i - 1; j < (i+1) * count; j++) {
                    for (int k = Math.max(0, j - 6); k < j; k++) {
                        res[i][j] += res[i - 1][k];
                    }
                }
            }
            double[] t_res = new double[n * count - n + 1];
            for (int i = 0; i < n * count - n + 1; i++) {
                t_res[i] = res[n - 1][i + n - 1];
            }
            double sum = Arrays.stream(res[n - 1]).sum();
            for (int i = 0; i < n * count - n + 1; i++) {
                t_res[i] /= sum;
            }
            return t_res;
        }
    }
    
  • class Solution {
        public boolean isStraight(int[] nums) {
            Arrays.sort(nums);
            //能补的个数
            int res=0;
            //需要补的个数
            int len=0;
            //不为0且重复的情况
            for(int i=1;i<nums.length;i++){
                if(nums[i-1]!=0&&nums[i]==nums[i-1]){
                    return false;
               }
            }
    
            for(int i=0;i<nums.length;i++){
                if(nums[i]==0){
                    res++;
                }
                //不为0的个数之间需要添加的数
                else if(i>0&&nums[i-1]!=0){
                    len+=nums[i]-nums[i-1]-1;
                }
            }
            return res>=len;
        }
    }
    
  • 0,1,2,...n-1的n个数从0开始数m个就删除一个,求最后剩下的数字

    public int lastRemaining(int n,int m){
        if(n<1||m<1){
            return -1;
        }
        int last=0;
        for(int i=2;i<=n;i++){
            last=(last+m)%i;
        }
        retrun last;
    }
    
  • 状态机

  • class Solution {
        public int add(int a, int b) {
            int num1;//保存相加位
            int num2;//保存进位
            do{
                num1=a^b;
                num2=(a&b)<<1;
                a=num1;
                b=num2;
            }
            while(b!=0);//没有进位
            return a;
        }
    }
    
  • class Solution {
        public int[] constructArr(int[] a) {
            int len=a.length;
            if(len==0){
                return a;
            }
            int[] left=new int[len];
            int[] right=new int[len];
            left[0]=1;
            right[0]=1;
            for(int i=1;i<len;i++){
                left[i]=left[i-1]*a[i-1]; 
                right[i]=right[i-1]*a[len-i];
            }
            int[] res=new int[len];
            for(int i=0;i<len;i++){
                res[i]=left[i]*right[len-1-i];
            }
            return res;
        }
    }
    
  • public class LRUCache {
        LRU cache;
    
        public LRUCache(int capacity) {
            this.cache = new LRU(capacity);
        }
    
        class LRU extends LinkedHashMap<Integer, Integer> {
            int capacity;
    
            public LRU(int capacity) {
                super(capacity, 0.75f, true);
                this.capacity = capacity;
            }
    
            public boolean removeEldestEntry(HashMap.Entry entry) {
                return this.size() > capacity;
            }
        }
    
        public int get(int key) {
            if (cache.containsKey(key)) {
                return cache.get(key);
            }
            return -1;
        }
    
        public void put(int key, int value) {
            cache.put(key, value);
        }
    }
    
posted @ 2020-08-08 22:30  樱空废宅  阅读(173)  评论(0)    收藏  举报