-
单例
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
饿汉模式
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到懒加载的效果。
-
懒汉模式(线程不安全)
public class Singleton { private static Singleton instance; private Singleton (){ } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懒汉模式申明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反映稍慢一些,而且在多线程不能正常工作。
多线程不能正常工作?
instance = new Singleton();//这一步骤出现问题,可以分解为3个步骤 1. memory = allocate(); //1.分配对象的内存空间 2. ctorInstance(memory);//2.初始化对象 3. instance = memory;//3.设置instance指向刚才分配的内存地址 //注意,上面2和3是可能发生指令重排序的,指向地址的时候可能还没初始化,要加synchronized,但是这是错误的优化,还要加上volatile
-
懒汉模式(线程安全)
public class Singleton { private static Singleton instance; private Singleton (){ } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。
-
双重检查模式 (DCL)
public class Singleton { private volatile static Singleton instance; private Singleton (){ } public static Singleton getInstance() { if (instance== null) { synchronized (Singleton.class) { if (instance== null) { instance= new Singleton(); } } } return singleton; } }
这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。在这里用到了volatile关键字,防止指令重排序。在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。 DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《java并发编程实践》一书建议用静态内部类单例模式来替代DCL。
-
静态内部类单例模式
public class Singleton { private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.sInstance; } private static class SingletonHolder { private static final Singleton sInstance = new Singleton(); } }
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式。
-
枚举单例
public enum Singleton { INSTANCE; public void doSomeThing() { } }
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException{ return singleton;
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
-
使用容器实现单例模式
public class SingleTon {//系统服务就用的这种 getSystemService() private static Map
mSingleMap = new HashMap<>() ; private SingleTon(){ } /** * 用静态的方式把 SingleTon对象初始化好了 */ static { mSingleMap.put("activity_manager" , new SingleTon()) ; } public static Object getService(String serviceName){ return mSingleMap.get(serviceName) ; } } 用SingletonManager 将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
-
-
反转链表
package leetcode; public class ReverseLinkedList { /** * 206. Reverse Linked List time : O(n); space : O(1); * @param head * @return * 思路是在原链表之前建立一个空的newHead(pre),因为首节点会变,然后从head开始,将之后的一个节点移到 * newHead(pre)之后,重复此操作直到head成为末节点为止 */ //看这个 public static ListNode reverseList(ListNode head) { //if(head==null) return head;这样写也行,下面一行注释掉也行 if (head == null || head.next == null) return head; ListNode pre = null; while (head != null) { //临时节点暂存当前节点的下一个节点 ListNode temp = head.next; //当前节点指向他前面的节点 head.next = pre; //然后变换节点 pre = head; head = temp; } return pre; } //递归 class Solution { public ListNode reverseList(ListNode head) { if(head==null||head.next==null) return head; ListNode last=reverseList(head.next);//最后last是5 head是1 last指向前一个last head.next.next=head; head.next=null; return last; } }
-
字符串转整数
package leetcode; public class StringtoInteger { /** * time : O(n) * space : O(1) * @param str * @return */ public int myAtoi(String str) { if (str == null || str.length() == 0) return 0; //去掉前尾的空白符 str = str.trim(); if(str.length()==0) return 0; char firstChar = str.charAt(0); int sign = 1;//注意是1不是0 不然若没有‘+’‘-’,那么就res*sign=0 int start = 0; long res = 0; if (firstChar == '+') { //判断+ —性 sign = 1; //从下一位符号开始判断 start++; } else if (firstChar == '-') { sign = -1; start++; } for (int i = start; i < str.length(); i++) { //Character.isDigit()判断是不是一个数字 if (!Character.isDigit(str.charAt(i))) { //不是数字 return (int) res * sign; } //-143 //0*10+1=1 //1*10+4=14 //14*10+3=143 //charAt(i)是字符串中的第i个字符,s.charAt(i)就是S中的第i个字符,因为字符都是用ASCII码存储的,存储 //的事ASCII码值,用s.charAt(i)减去字符‘0’,就是用s.charAt(i)的码值减去‘0’的码值,得到的值干好就是s //中第i个字符的十进制值。 res = res * 10 + str.charAt(i) - '0'; //越界 if (sign == 1 && res > Integer.MAX_VALUE) return Integer.MAX_VALUE; if (sign == -1 && res > Integer.MAX_VALUE) return Integer.MIN_VALUE; } return (int) (res*sign);//注意要加括号,不加括号的话,res要放在前面 } }
-
整数翻转
package leetcode; public class ReverseInteger { /** * 7. Reverse Integer * Reverse digits of an integer. Example1: x = 123, return 321 Example2: x = -123, return -321 int : -2147483648 ~ 2147483647 corner case : 越界 time : O(n); space : O(1); * @param x * @return */ // 1.取模运算多见于计算机领域,取余运算一般用于数学领域。 // 2.取模运算(取余运算)计算步骤 // a、求整数商 // c=a/b // b、求模(余数) // r=a-c*b //两者不同点:取模运算 c 向负无穷远处取整,取余运算 c 向 0 方向取整。 //结论:a 为正整数时,取模运算和取余运算结果相同;a 为负整数时,两者结果不同。 public static int reverse(int x) { long res = 0; while (x != 0) { res = res * 10 + x % 10; x /= 10; if (res > Integer.MAX_VALUE || res < Integer.MIN_VALUE) return 0; } return (int)res; } }
-
两个链表相加
package leetcode; public class AddTwoNumbers { /** Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 0 -> 8 time : O(n) space : O(n) * @param l1 * @param l2 * @return */ public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummyNode = new ListNode(0); ListNode cur = dummyNode; int sum = 0; while (l1 != null || l2 != null) { if(l1 != null) { sum += l1.val; l1 = l1.next; } if(l2 != null) { sum += l2.val; l2 = l2.next; } cur.next = new ListNode(sum % 10);//真正存入的结果值 sum /= 10;//结果0、1,相当于进位,结果用于下次的链表值相加 cur = cur.next; } //用于判断最后一次的结果,是否要进位+1 if(sum == 1) { cur.next = new ListNode(1); } return dummyNode.next;//因为dummyNode是不动的,一直==0,cur是往后遍历的,结果是从cur.next开始放的 } }
-
二叉树前序,中序,后序 ,层序,锯齿遍历
-
前序迭代
package leetcode; import java.util.ArrayList; import java.util.List; import java.util.Stack; public class BinaryTreePreorderTraversal { //根左右 //方法一:递归 public static List
preorderTraversal(TreeNode root) { List res = new ArrayList<>(); if (root == null) return res; helper(res, root); return res; } public static void helper(List res, TreeNode root) { if (root == null) return; res.add(root.val); helper(res, root.left); helper(res, root.right); } //方法二:迭代 public static List preorderTraversal2(TreeNode root) { List res = new ArrayList<>(); if (root == null) return res; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode cur = stack.pop(); //记得先让右子树入栈,栈是先进后出的 if (cur.right != null) stack.push(cur.right); if (cur.left != null) stack.push(cur.left); res.add(cur.val); } return res; } } -
中序
package leetcode; import java.util.ArrayList; import java.util.List; import java.util.Stack; public class BinaryTreeInorderTraversal { /** * 94. Binary Tree Inorder Traversal * Given a binary tree, return the inorder traversal of its nodes' values. * time : O(n) * space : O(n) * @param root * @return */ //方法一:递归 //左中右 public static List
inorderTraversal(TreeNode root) { List res = new ArrayList<>(); if (root == null) return res; helper(res, root); return res; } public static void helper(List res, TreeNode root) { if (root == null) return; helper(res, root.left); res.add(root.val); helper(res, root.right); } //方法二:迭代 public static List inorderTraversal2(TreeNode root) { List res = new ArrayList<>(); if (root == null) return res; Stack stack = new Stack<>(); TreeNode cur = root;//注意中序遍历进入循环前是没有入栈操作的,它需要不断的顺着根节点找下面的节点,所以首先根节点不能为空 while (cur != null || !stack.isEmpty()) { while (cur != null) { stack.push(cur); cur = cur.left;//找到最左节点 } cur = stack.pop();//1 res.add(cur.val);//左根右:自身->右子节点 cur = cur.right;//当某个节点的右子节点为null后,那么需要从栈中弹出新的节点继续遍历(1处),所以||stack!=null,当cur==null且栈空,说明中序遍历结束 } return res; } } -
后序
package leetcode; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Stack; public class BinaryTreePostorderTraversal { /** * 145. Binary Tree Postorder Traversal * * time : O(n); * space : O(n); * @param root * @return */ //方法一:递归 //左右根 public static List
postorderTraversal(TreeNode root) { List res = new ArrayList<>(); if (root == null) return res; helper(res, root); return res; } public static void helper(List res, TreeNode root) { if (root == null) return; helper(res, root.left); helper(res, root.right); res.add(root.val); } //方法二:迭代 public static List postorderTraversal2(TreeNode root) { LinkedList res = new LinkedList<>(); if (root == null) return res; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode cur = stack.pop(); res.addFirst(cur.val);//加在前面addFirst是LinkedList的方法(在列表首部添加元素),ArrayList的方法是add(0,cur.val); if (cur.left != null) stack.push(cur.left);//左 if (cur.right != null) stack.push(cur.right);//右 //res.addFirst(cur.val);放在这里也一样,其实它就是模拟了栈的功能//根 } return res; } //双栈法,如果要求直接输出,不是返回List ,可以用上 public List postorderTraversal(TreeNode root) { List res = new ArrayList<>(); if(root==null) return res; Stack stack = new Stack(); Stack stack2 = new Stack(); stack.push(root); while(!stack.isEmpty()){ TreeNode cur = stack.pop(); if(cur.left!=null) stack.push(cur.left); if(cur.right!=null) stack.push(cur.right); stack2.push(cur); } while(!stack2.isEmpty()){ res.add(stack2.pop().val); } return res; } } -
层序
package leetcode; import org.omg.PortableServer.LIFESPAN_POLICY_ID; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; public class BinaryTreeLevelOrderTraversal { /** * 102. Binary Tree Level Order Traversal 3 / \ 9 20 / \ 15 7 [ [3], [9,20], [15,7] ] time : O(n); space : O(n); * @param root * @return */ //https://www.bilibili.com/video/BV1xa411A76q?p=18手画图解版+代码p18 public static List
- > levelOrder(TreeNode root) {
List
- > res = new ArrayList<>();
if (root == null) return res;
Queue
queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { int size = queue.size(); List list = new ArrayList<>(); for (int i = 0; i < size; i++) { TreeNode cur = queue.poll(); if (cur.left != null) queue.offer(cur.left); if (cur.right != null) queue.offer(cur.right); list.add(cur.val); } res.add(list); } return res; } //********递归版********* public static List - > levelOrder2(TreeNode root) {
List
- > res = new ArrayList<>();
if (root == null) return res;
helper(res, root, 0);
return res;
}
public static void helper(List
- > res, TreeNode root, int level) {
if (root == null) return;
if (level >= res.size()) {
res.add(new ArrayList<>());
}
res.get(level).add(root.val);
helper(res, root.left, level + 1);
helper(res, root.right, level + 1);
}
-
锯齿遍历
package leetcode; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; public class BinaryTreeZigzagLevelOrderTraversal { /** * 103. Binary Tree Zigzag Level Order Traversal 3 / \ 9 20 / \ 15 7 [ [3], [20,9], [15,7] ] time : O(n) space : O(n); * @param root * @return */ public static List
- > zigzagLevelOrder(TreeNode root) {
List
- > res = new ArrayList<>();
if (root == null) return res;
Queue
queue = new LinkedList<>(); queue.offer(root); boolean x = true;//奇数层还是偶数层 while (!queue.isEmpty()) { int size = queue.size(); //把他当成栈/数组使用,比我的方法更好,他少使用了一个栈 //每次都new一个,就不需要clear了 List list = new ArrayList<>(); for (int i = 0; i < size; i++) { TreeNode cur = queue.poll(); if (x) { list.add(cur.val); } else { list.add(0, cur.val); } if (cur.left != null) { queue.offer(cur.left); } if (cur.right != null) { queue.offer(cur.right); } } res.add(list); x = x ? false : true;//取反 } return res; }
-
-
两数之和
package leetcode; import java.util.Arrays; import java.util.HashMap; public class TwoSum { public int[] twoSum(int[] nums, int target) { HashMap
map = new HashMap (); for (int i = 0; i < nums.length; i++) { if (map.containsKey(target - nums[i])) { return new int[] { i, map.get(target - nums[i]) }; } map.put(nums[i], i); } return new int[] { -1, -1 }; } } -
Pow(x, n)
package leetcode; public class Pow { public double myPow1(double x, int n) {//递归版 if (n > 0) { return pow(x, n); } else { return 1.0 / pow(x, n); } } public double pow (double x, int n) { if (n == 0) { return 1; } double y = pow(x, n / 2); if (n % 2 == 0) { return y * y; } else { return y * y * x; } } //time:O(logn),space:O(1) public static double myPow2(double x, int n) { if (n == 0) return 1; double res = 1; // int : -6.. ~ +6.. -2^32 ~ 2 ^32-1 Integer.MIN_VALUE long abs = Math.abs((long)n); while (abs > 0) { if (abs % 2 != 0) { res *= x;//有几次为奇数,就执行几次 7/2一次 3/2 一次 1/2一次 } x *= x;//记录上一轮的结果 abs /= 2; } if (n < 0) { return 1.0 / res; } return res; }
-
二叉树的最小深度(LC111)
package leetcode; public class MinimumDepthofBinaryTree { /** * 111. Minimum Depth of Binary Tree * Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. time : O(n); space : O(H);H树的高度 * @param root * @return */ //还是用深度优先搜索 DFS 来完成,万能的递归啊。首先判空,若当前结点不存在,直接返回0。然后看若左子结点不存在,那么对右子结点调用递归函数,并加1返回。反之,若右子结点不存在,那么对左子结点调用递归函数,并加1返回。若左右子结点都存在,则分别对左右子结点调用递归函数,将二者中的较小值加1返回即可 public static int minDepth(TreeNode root) { //这道题递归条件里分为2种情况 if (root == null) return 0; //1.如果左孩子和由孩子其中一个为空,那么需要返回比较大的那个孩子的深度 if (root.left == null || root.right == null) { //必有一个为null,相当于为0,另一个返回深度+1 return Math.max(minDepth(root.left), minDepth(root.right)) + 1; } //2.一种情况,也就是左右孩子都不为空,返回最小深度+1即可 return Math.min(minDepth(root.left), minDepth(root.right)) + 1; } }
-
平衡二叉树
package leetcode; public class BalancedBinaryTree { /** * 110. Balanced Binary Tree */ 算法流程: helper(root): 递归返回值: 当节点root 左 / 右子树的高度差 <2 :则返回以节点root为根节点的子树的最大高度,即节点 root 的左右子树中最大高度加 1 ( max(left, right) + 1 ); 当节点root 左 / 右子树的高度差 ≥2:则返回 −1 ,代表 此子树不是平衡树 。 递归终止条件: 当越过叶子节点时,返回高度 0 ; 当左(右)子树高度 left== -1 时,代表此子树的 左(右)子树 不是平衡树,因此直接返回 −1; isBalanced(root) : 返回值: 若 recur(root) != 1 ,则说明此树平衡,返回 true ;否则返回 false。 public boolean isBalanced(TreeNode root) { if (root == null) return true; return helper(root) != -1; } public int helper(TreeNode root) { if (root == null) return 0; int l = helper(root.left); int r = helper(root.right); if (l == -1 || r == -1 || Math.abs(l - r) > 1) { return -1; } return Math.max(l, r) + 1; } }
-
回文数(LC9)
package leetcode; public class PalindromeNumber { /** * 9. Palindrome Number time : O(n) space : O(1) * @param x * @return */ public boolean isPalindrome(int x) { if (x < 0 || x != 0 && x % 10 == 0) return false;//x%10==0能被10整除的都不是 int palind = x; int rev = 0; while (x > 0) { rev = rev * 10 + x % 10; x /= 10; } return palind == rev; } }
-
二分查找(LC704)
class Solution { public int search(int[] nums, int target) { int start = 0; int end = nums.length-1; //数组:length 容器:size() String:length() while(start<=end){//要考虑只有一个数的情况 int mid = start + (end-start)/2; if(nums[mid]==target) return mid; else if(nums[mid]
-
LRU(LC146)
//看这个 public class LRUCache { /** * * Node类用于抽象链表的节点 * key、value存储键、值, * before、after分别指向当前节点的前后Node节点; * */ class Node { int key; int value; Node before; Node after; } /** * 使用HashMap缓存Node节点 */ private HashMap
cache = new HashMap (); /** * 最大容量,超过capacity时继续插入会触发删除最老未被使用的节点 */ private int capacity; /** * 头节点、尾节点(注意这两个节点不存储实际的数据) */ private Node head, tail; public LRUCache(int capacity) { this.capacity = capacity; head = new Node(); head.before = null; tail = new Node(); tail.after = null; head.after = tail; tail.before = head; } /** * 将节点插入队列头部 * * @param node */ private void addToHead(Node node) { node.before = head; node.after = head.after; head.after.before = node; head.after = node; } /** * 删除队列中的一个节点 * * @param node */ private void removeNode(Node node) { //把目标节点的前后节点保存,然后链接 Node before = node.before; Node after = node.after; before.after = after; after.before = before; } /** * 将节点移动到有效数据头部 * * @param node */ private void moveToHead(Node node) { //先删除掉 removeNode(node); //在插入 addToHead(node); } /** * 删除有效数据尾节点 * * @return 尾节点 */ private Node popTail() { Node res = tail.before; this.removeNode(res); return res; } public int get(int key) { Node node = cache.get(key); if (node == null) { return -1; // should raise exception here. } // 如果获取到数据,则将获取到的节点移动到队列头部; moveToHead(node); return node.value; } public void put(int key, int value) { Node node = cache.get(key); if (node == null) { //新写进去的,会增加节点 Node newNode = new Node(); newNode.key = key; newNode.value = value; cache.put(key, newNode); addToHead(newNode); if (cache.size() > capacity) { // 删除队尾有效数据节点 Node tail = this.popTail(); this.cache.remove(tail.key); } } else { //已经在缓存里 node.value = value; // 在使用get方法获取值之后,需要将当前获取的节点移动到队列头部 moveToHead(node); } } } 两数相加(LC2)
-
计数质数(LC204)
package leetcode; public class CountPrimes { /** * 厄拉多塞筛法,求一组质数,时间复杂度仅有O(nloglogn)https://www.cnblogs.com/jiaxin359/p/6081629.html * 如果从1到n-1分别判断质数,时间复杂度为O(nsqrt(n))) * @param n * @return */ public int countPrimes(int n) { boolean[] notPrime = new boolean[n]; int res = 0; for (int i = 2; i < n; i++) {//如果是2,没有进入循环 if (notPrime[i] == false) {//就是质数 res++; for (int j = 2; i * j < n; j++) { notPrime[i * j] = true;//i的j倍数都是非质数,将i*j排除掉 } } } return res; } }
-
生产者消费者模式
//店员类 class Clerk { private int product = 0; // 进货 public synchronized void get() { while (product >= 1) { System.out.println("产品已满!"); // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + ++product); this.notifyAll(); // 唤醒 } // 售货 public synchronized void sale() { while (product <= 0) { System.out.println("缺货!"); try { this.wait();// 等待 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + --product); this.notifyAll();// 唤醒 } } // 生产者类 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } clerk.get(); } } } // 消费者类 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 10; i++) { clerk.sale(); } } } public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "Productor A").start(); new Thread(consumer, "Consumer B").start(); new Thread(productor, "Productor C").start(); new Thread(consumer, "Consumer D").start(); } }
-
删除链表的倒数第 N 个结点(LC19)
package leetcode; public class RemoveNthNodeFromEndofList { //https://www.bilibili.com/video/BV1Pq4y1j78o?p=10 b站讲解 class Solution { public ListNode getKthFromEnd(ListNode head, int k) { ListNode dummy = new ListNode(0); ListNode slow = dummy; ListNode fast = dummy; dummy.next = head; for (int i = 0; i <= n; i++) { //注意是<=n fast = fast.next; } while (fast != null) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next;//把中间要移除的断掉 return dummy.next; } }
-
环形链表(LC141 判断链表是否有环)
package leetcode; import java.util.List; public class LinkedListCycle { //time : O(n); space : O(1); //这道题是快慢指针的经典应用。只需要设两个指针,一个每次走一步的慢指针和一个每次走两步的快指针,如果链表里有环的话,两个指针最终肯定会相遇 public static boolean hasCycle(ListNode head) { if (head == null || head.next == null) return false; ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { return true;//遇到 } } return false; } }
-
环形链表 II(LC142 找到环形链表的入口结点)
package leetcode; public class LinkedListCycleII { /** * Given a linked list, return the node where the cycle begins. If there is no cycle, return null. * time : O(n) * space : O(1) */ //题解:https://www.cnblogs.com/hiddenfox/p/3408931.html //因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(这个结论很重要!)。 //我们已经得到了结论a=c,那么让两个指针分别从X和Z开始走,每次走一步,那么正好会在Y相遇!也就是环的第一个节点。 public ListNode detectCycle(ListNode head) { if (head == null || head.next == null) return null; ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (fast == slow) {//fast比slow多走一个环的距离 ListNode slow2 = head; while (slow != slow2) {//不管fast绕了多少圈和slow相遇,依然满足这个情况 slow = slow.next; slow2 = slow2.next; } return slow; } } return null; } }
-
手写一个观察者/建造者...设计模式
观察者设计模式
被观察者类
/** * 被观察者接口 */ public Interface Observable { // 添加观察者 void register(Observer observer); // 移除观察者 void remove(Observer observer); // 通知观察者 void notify(Message msg); } /** * 被观察者类 */ public class ObservableClass implement Observable { private List
observers = new ArrayList<>(); @Override public void register(Observer observer) { observers.add(observer); } @Override public void remove(Observer observer) { observers.remove(observer); } @Override public void notify(Message msg) { for(Observer observer: observers) { observer.update(msg); } } } 观察者类
public interface Observer { // 当被观察者更新时,执行处理逻辑 void update(Message message); } public class ConcreteObserverOne implements Observer { @Override public void update(Message message) { //TODO: 获取消息通知,执行自己的逻辑... System.out.println("ConcreteObserverOne is notified."); } } public class ConcreteObserverTwo implements Observer { @Override public void update(Message message) { //TODO: 获取消息通知,执行自己的逻辑... System.out.println("ConcreteObserverTwo is notified."); } }
使用示例
public class Demo { public static void main(String[] args) { ObservableClass observable = new ObservableClass(); observable.register(new ConcreteObserverOne()); observable.register(new ConcreteObserverTwo()); observable.notify(new Message()); } }
使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
建造者设计模式
创建产品类
//它有三个部件:CPU 、主板和内存。并在里面提供了三个方法分别用来设置CPU 、主板和内存: public class Computer { private String mCpu; private String mMainboard; private String mRam; public void setmCpu(String mCpu) { this.mCpu = mCpu; } public void setmMainboard(String mMainboard) { this.mMainboard = mMainboard; } public void setmRam(String mRam) { this.mRam = mRam; } }
创建Builder类规范产品的组建
public abstract class Builder { public abstract void buildCpu(String cpu); public abstract void buildMainboard(String mainboard); public abstract void buildRam(String ram); public abstract Computer create(); }
商家实现了抽象的Builder类,MoonComputerBuilder类用于组装电脑
public class MoonComputerBuilder extends Builder { private Computer mComputer = new Computer(); @Override public void buildCpu(String cpu) { mComputer.setmCpu(cpu); } @Override public void buildMainboard(String mainboard) { mComputer.setmMainboard(mainboard); } @Override public void buildRam(String ram) { mComputer.setmRam(ram); } @Override public Computer create() { return mComputer; } }
用Dirextor指挥者类来统一组装过程
public class Direcror { Builder mBuild=null; public Direcror(Builder build){ this.mBuild=build; } public Computer CreateComputer(String cpu,String mainboard,String ram){ //规范建造流程 this.mBuild.buildMainboard(mainboard); this.mBuild.buildCpu(cpu); this.mBuild.buildRam(ram); return mBuild.create(); } }
客户端调用指挥者类
public class CreatComputer { public static void main(String[]args){ Builder mBuilder=new MoonComputerBuilder(); Direcror mDirecror=new Direcror(mBuilder); //组装电脑 mDirecror.CreateComputer("i7-6700","华擎玩家至尊","三星DDR4"); } }
使用场景
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能。
- 创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
优点:
- 使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 具体的建造者类之间是相互独立的,容易扩展。
- 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
缺点:
- 产生多余的Build对象以及Dirextor类。
-
线程交替打印
//其他方法: https://segmentfault.com/a/1190000021433079?utm_source=sf-similar-article //*********3个线程交替打印0->100*************** public class Main012 { public static void main(String[] args) { int threadCount = 3; int max = 100; for (int i = 0; i < threadCount; i++) { new Thread(new PrintSequenceThread(i, threadCount, max)).start(); } } } class PrintSequenceThread implements Runnable { private static final Object mLock = new Object(); /** * 当前即将打印的数字 */ private static int current = 0; /** * 当前线程编号,从0开始 */ private int threadNo; /** * 线程数量 */ private int threadCount; /** * 打印的最大数值 */ private int max; public PrintSequenceThread(int threadNo, int threadCount, int max) { this.threadNo = threadNo; this.threadCount = threadCount; this.max = max; } @Override public void run() { while (true) { synchronized (mLock) { // 判断是否轮到当前线程执行 while (current % threadCount != threadNo) { if (current > max) { break; } try { // 如果不是,则当前线程进入wait mLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 最大值跳出循环 if (current > max) { break; } System.out.println("thread-" + threadNo + " : " + current); current++; // 唤醒其他wait线程 mLock.notifyAll(); } } } } //******************************************************************************************* //3个线程并发打印从0到n,不是交替打印,每个线程可能连续执行,有的线程可能长时间按得不到执行 public class Main12_1 { public static void main(String[] args) { int threadCount = 3; for (int i = 0; i < threadCount; i++) { new Thread(new PrintSequenceThread1(i, threadCount)).start(); } } } class PrintSequenceThread1 implements Runnable { private static final Object mLock = new Object(); /** * 当前即将打印的数字 */ private static int current = 0; /** * 当前线程编号,从0开始 */ private int threadNo; /** * 线程数量 */ private int threadCount; public PrintSequenceThread1(int threadNo, int threadCount) { this.threadNo = threadNo; this.threadCount = threadCount; } @Override public void run() {//3个线程并发打印从0到n,不是交替打印,每个线程可能连续执行,有的线程可能长时间按得不到执行 while (true) { synchronized (mLock) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread-" + threadNo + " : " + current); current++; } } } } **************************************************************************************** //***************//3个线程交替打印从0到n***************** public class Main12_2 { public static void main(String[] args) { int threadCount = 3; for (int i = 0; i < threadCount; i++) { //3个线程交替打印从0到n new Thread(new PrintSequenceThread2(i, threadCount)).start(); } } } class PrintSequenceThread2 implements Runnable { private static final Object mLock = new Object(); /** * 当前即将打印的数字 */ private static int current = 0; /** * 当前线程编号,从0开始 */ private int threadNo; /** * 线程数量 */ private int threadCount; public PrintSequenceThread2(int threadNo, int threadCount) { this.threadNo = threadNo; this.threadCount = threadCount; } @Override public void run() {//3个线程交替打印从0到n while (true) { synchronized (mLock) { // 判断是否轮到当前线程执行 while (current % threadCount != threadNo) {//一定是while,而不是if try { //是while的原因,当这个线程被notifyAll时,它会从mLock.wait()这句之后继续往下执行,即使当前没有轮到它 //写成while,如果它依然满足条件(没有轮到它),它会一直阻塞在这里 // 如果不是,则当前线程进入wait mLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(100);//停顿100ms,为了让打印不要太快 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread-" + threadNo + " : " + current); current++; // 唤醒其他wait线程 mLock.notifyAll(); } } } }
-
斐波拉切数列
/*大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。 n<=39*/ //第1题:Fibonacci数列 //判题OJ:https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking //正推法 public int Fibonacci(int n) { //first:前面2个,second:前面1个 int first = 1, second = 1; int third = 0; if(n == 1 || n == 2) return 1; for(int i = 3; i <= n; i++){ third = first + second;//将上一次保留下来的相加 例如5=4+3 first = second;//保留上一轮的结果3 second = third;//保留这一轮的结果4 } return third; }
-
青蛙跳台阶
//第1题:跳台阶 //判题OJ:https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking public int climbStairs(int n) { if (n <= 2) return n; int fisrt = 1; int second = 2; int third = 3; for (int i = 2; i < n; i++) { third = fisrt + second; fisrt = second; second = third; } return third; } //第2题:变态跳台阶 //判题OJ:https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 每个台阶可以看作一块木板,让青蛙跳上去,n个台阶就有n块木板,最后一块木板是青蛙到达的位子,必须存在,其他n-1块木板可以任意选择是否存在,则每个木板有存在和不存在两种选择,n-1块木板就有2^(n-1)种,可以直接得到结果,不用那么麻烦分析吧 public int JumpFloorII(int target) { return 1<< target-1; } //类似题: https://www.zybang.com/question/104cecdfb73ed0ca724706dccad2f9d0.html //跳台阶3:(此题好像不能用非递归) //n阶楼梯,每次能走1阶或2阶或5阶,问:到n阶总共有多少种走法 public int jump(int n) { if (n == 0) return 1; if (n <= 2) return n; if (n < 5) return jump(n - 1) + jump(n - 2); return jump(n - 1) + jump(n - 2) + jump(n - 5); }
-
1个数组(有重复元素)找相加等于0的数有多少对
//有点力扣167的意思 //给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。见题解下的双指针解法 class Solution { public int[] twoSum(int[] numbers, int target) { int i = 0; int j = numbers.length - 1; while (i < j) { int sum = numbers[i] + numbers[j]; if (sum < target) { i++; } else if (sum > target) { j--; } else { return new int[]{i+1, j+1}; } } return new int[]{-1, -1}; } } //以上不是本题的题解 //思路:先排序,然后双指针解法,跟上面一样
-
两栈实现队列
class CQueue { LinkedList
A, B; public CQueue() { A = new LinkedList (); B = new LinkedList (); } public void appendTail(int value) { A.addLast(value); } public int deleteHead() { if(!B.isEmpty()) return B.removeLast(); if(A.isEmpty()) return -1; while(!A.isEmpty()) B.addLast(A.removeLast()); return B.removeLast(); } } -
计算View树的深度
private int maxDeep(View view) { //当前的view已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了 if (!(view instanceof ViewGroup)) { return 0; } ViewGroup vp = (ViewGroup) view; //虽然是viewgroup,但是如果并没有任何子view,那么也已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了 if (vp.getChildCount() == 0) { return 0; } //用来记录最大层数 int max = 0; //广度遍历view for (int i = 0; i < vp.getChildCount(); i++) { //由于vp拥有子view,所以下面还有一层,因为可以+1,来叠加一层,然后再递归几岁算它的子view的层数 int deep = maxDeep(vp.getChildAt(i)) + 1; //比较哪个大就记录哪个 if (deep > max) { max = deep; } } return max; }
-
十大排序
-
冒泡
//冒泡排序:Time:O(n^2),Space:(1),最好Time:O(n),最坏Time:O(n^2) 稳定 public class maoPao { public static void main(String[] args) { BubbleSort(new int[]{5, 4, 9, 4, 2, 3, 1}); } private static void BubbleSort(int[] arr) { for (int i = 0; i < arr.length; i++) { for (int j = arr.length-1; j >i ; j--) { if (arr[j]
-
选择
/* * 默认第一个值最小,比较第一个值开始和后面2开始到n-1的大小,小则交换 * 然后 拿第二个值和后面3开始到n-1的大小,小则交换 * */ //选择排序:Time:O(n^2),Space:(1),最好Time:O(n^2),最坏Time:O(n^2) 稳定 public class SelectionSort { public static void selectionSort(int[] arr) { if (arr == null && arr.length < 2) { return; } for (int i = 0; i < arr.length - 1; i++) { //第一个判断结束后,下一个作为min int minIndex = i; for (int j = i + 1; j < arr.length; j++) { minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); } //set可以去重 Collection
collection = new LinkedHashSet<>(); for (int i = 0; i < arr.length; i++) { collection.add(arr[i]); System.out.print(arr[i]); } } private static void swap(int[] arr, int i, int minIndex) { int tmp = arr[minIndex]; arr[minIndex] = arr[i]; arr[i] = tmp; } public static void main(String[] args) { int[] arr = {5, 2, 1, 6, 7, 5, 6}; selectionSort(arr); } } -
插入
//插入排序:Time:O(n^2),Space:(1) ;最好(全部有序)Time:O(n);最坏(全部逆序)Time:O(n^2),稳定 public class InsertionSort { public static void insertionSort(int[] arr) { if (arr == null && arr.length < 2) { return; } //0~0 有序的 //0~i 想有序 for (int i = 1; i < arr.length; i++) { //0~i做到有序 for (int j = i - 1; j >= 0; j--) { //if部分可以 :int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j-- if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); } } } //set可以去重 Collection
collection = new LinkedHashSet<>(); for (int i = 0; i < arr.length; i++) { collection.add(arr[i]); System.out.print(arr[i]); } System.out.println(); System.out.println(collection); } private static void swap(int[] arr, int i, int j) { arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j]; } public static void main(String[] args) { int[] arr = {5, 4, 9, 4, 2, 3, 1}; insertionSort(arr); } } -
快排
//平均/最好Time:O(nlogn),Space:O(logn).最坏Time:O(n^2),不稳定 public class kuaiPai { public static void main(String[] args) { int[] ints = {5, 7, 6, 5, 3}; quickSort(ints,0,4); for (int anInt : ints) { System.out.print(anInt); } } public static void quickSort(int nums[], int left, int right) { if (left >= right) return;// left>right肯定不行,left=right时,只有一个数也没必要继续排序 int pos = partition(nums, left, right); quickSort(nums, left, pos - 1);// 非递归要用栈来模拟,那还是算了吧 quickSort(nums, pos + 1, right); } public static int partition(int nums[], int left, int right) { int pivot = nums[left]; int l = left + 1; int r = right; while (l <= r) {// 算上left至少两个数 if (nums[l] > pivot && nums[r] < pivot) { swap(nums, l++, r--); } if (nums[l] <= pivot) l++;//没有满足上述条件的也要向中间移动 if (nums[r] >= pivot) r--; }//r
-
堆排
// 堆排序:Time:O(nlogn),Space:(1) ;最好/最坏Time:O(nlogn);不稳定 import java.util.*; public class Main { public void heapSort(int[] arr) { initHeap(arr);// //构建大顶堆, 从第一个非叶子结点从下至上,从右至左调整结构 for (int i = arr.length - 1; i > 0; i--) { // 调整堆结构+交换堆顶元素与末尾元素 // 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆 //将堆顶记录和当前未经排序子序列的最后一个记录交换 swap(arr, 0, i);// 将堆顶元素与末尾元素进行交换,交换后最后一个元素为最大 // 交换之后,需要重新检查堆是否符合大顶堆,不符合则要调整 adjustHeap(arr, 0, i);// 调整根节点满足最大堆, } } // 初始化最大堆 public void initHeap(int[] arr) { // 从第一个非叶子结点从下至上,从右至左调整结构 //依次以索引arr.length / 2 - 1到0的元素为堆顶(根节点),调整大顶堆, for (int i = arr.length / 2 - 1; i >= 0; i--) { adjustHeap(arr, i, arr.length); } } //以i为根节点,调整最大堆 // 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上) public void adjustHeap(int[] arr, int i, int size) {//i是堆顶坐标,size是数组元素个数,不是坐标 int temp = arr[i];// 先取出当前元素i////根节点 int child = 2 * i + 1;// 左子节点,从i结点的左子结点开始,也就是2i+1处开始 while (child < size) {// 有左子结点 if (child + 1 < size && arr[child + 1] > arr[child]) {// 且有右子节点,如果左子结点小于右子结点,child指向右子结点 child++; } // 如果父节点小于孩子结点,则需要交换 // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换) if (arr[child] > temp) {// 这里的arr[child] 左右子节点中较大的那个子节点//子节点>根节点 arr[i] = arr[child];//子节点值=父节点值 i = child;//索引变成子节点的,向下遍历到子节点 child = 2 * i + 1;//child变成i的左子节点继续遍历//这里的child就是子节点的子节点了 } else { break;// 大顶堆结构未被破坏,不需要调整 } } arr[i] = temp;// 将temp值放到最终的位置 } public void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } //测试用 //测试OJ:https://www.nowcoder.com/questionTerminal/3385982ae71d4a1ca8bf3d03614c0325 /**public static void main(String[] args) { Scanner cin = new Scanner(System.in); int number = Integer.parseInt(cin.nextLine()); String[] data = cin.nextLine().split(" "); int[] array = new int[number]; for (int i = 0; i < number; i++) { array[i] = Integer.parseInt(data[i]); } new Main().heapSort(array); for (int i = 0; i < number; i++) { if (i != number - 1) System.out.print(array[i] + " "); else System.out.print(array[i]); } }*/ }
-
归并
//归并排序:Time:O(nlogn),Space:(1),最好Time:O(nlogn),最坏Time:O(nlogn) 不稳定 public class MergeSort { public static void mergeSort(int[] arr, int leftPtr, int rightPtr, int rightBound) { if (arr == null && arr.length < 2) { return; } int mid = rightPtr - 1; int[] temp = new int[rightBound - leftPtr + 1]; int i = leftPtr; int j = rightPtr; int k = 0; while (i <= mid && j <= rightBound) { temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++]; /* if (arr[i] < arr[j]) { temp[k] = arr[i]; i++; k++; } else { temp[k] = arr[j]; j++; k++; }*/ } //如果有剩余(只会有一边) 直接复制(已经排过序的) while (i <= mid) { temp[k++] = arr[i++]; } while (j <= rightBound) { temp[k++] = arr[j++]; } for (int m = 0; m < temp.length; m++) { arr[leftPtr + m] = temp[m]; } } private static void sort(int[] arr, int left, int right) { if (left == right) return; //分成两半 int mid = left + (right - left) / 2; //左边排序 sort(arr, left, mid); //右边排序 sort(arr, mid + 1, right ); mergeSort(arr, left, mid + 1, right); } private static void print(int[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } } public static void main(String[] args) { int[] arr = {1, 4, 7, 8, 3, 6, 9}; sort(arr, 0, arr.length - 1); print(arr); } }
-
-