一 回溯法、递归

79.单词搜索

842.将数组拆分成斐波那契序列

**306.累加数**
剑指Offer34:二叉树的路径和

回溯法的经典模板:

  1. private void backtrack("原始参数") {
  2. //终止条件(递归必须要有终止条件)
  3. if ("终止条件") {
  4. //一些逻辑操作(可有可无,视情况而定)
  5. return;
  6. }
  7. for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) {
  8. //一些逻辑操作(可有可无,视情况而定)
  9. //做出选择
  10. //递归
  11. backtrack("新的参数");
  12. //一些逻辑操作(可有可无,视情况而定)
  13. //撤销选择
  14. }
  15. }

二 一些重要的方法

1 compare & compareTo

注意要想自定义排序规则,就不能使用基本数据类型int,double ,char

  1. //数组排序
  2. String[] str = new String[5];
  3. Arrays.sort(str, new Comparator<String>() {
  4. @Override
  5. public int compare(String o1, String o2) {
  6. // TODO Auto-generated method stub
  7. return 0;
  8. }
  9. });
  10. //List集合排序
  11. ArrayList<String> list = new ArrayList<String>();
  12. Collections.sort(list, new Comparator<String>() {
  13. @Override
  14. public int compare(String o1, String o2) {
  15. // TODO Auto-generated method stub
  16. return 0;
  17. }
  18. });
  1. String[] str = new String[5];
  2. Arrays.sort(str, new Comparator<String>() {
  3. @Override
  4. public int compare(String o1, String o2) {
  5. // TODO Auto-generated method stub
  6. return o1.compareTo(o2);
  7. }
  8. });

2 字符串的一些操作

  1. string.substring()
  2. str.startwith()
  3. str.charAt()
  4. str.toCharArray()
  5. str.equals()
  6. ## 字符串列表转字符串
  7. List<String> arr = new ArrayList<>();
  8. String ret = String.join("", arr);

3 埃氏筛

4 位运算

与运算(&)

**n & 1 = 0:** 表示数字最末尾的二进制数为0

移位运算

  • **n >>> 1:** 表示n的二进制数 无符号右移 1位,当n为负数时,最高位表示正负的1不会扩展,右移后,用0补充
  • **n >> 1:** 表示n的二进制数带符号右移1为,当n为负数时,最高位表示正负的1会扩展,右移后,用1补充,相当于除以2

异或操作:相同为0,不同为1

  • res ^= s.charAt(i)

使用异或运算,将所有值进行异或
异或运算,相异为真,相同为假,所以 a^a = 0 ;0^a = a
因为异或运算 满足交换律 a^b^a = a^a^b = b 所以数组经过异或运算,单独的值就剩下了

5 最长递增子序列

  1. 俄罗斯套娃

6 二维数组排序

  1. Arrays.sort(people, new Comparator<int[]>() {
  2. @Override
  3. public int compare(int[] o1, int[] o2) {
  4. if(o1[0] == o2[0]) return o2[1] - o1[1];
  5. return o2[0] - o1[0];
  6. }
  7. })

7 数组列表转二维数组

  1. List<int[]> arr = new ArrayList<>();
  2. int[][] ret = arr.toArray(new int[arr.size()][2]);

三 丑数

四 链表的操作

每一个都是一个节点

基础:206(翻转链表)

试着用双指针做做:剑指offer22

147 对于链表的插入排序 :引入哑节点的作用,方便在头节点插入数据
148 排序链表
328 奇偶链表

先从最简单的合并链表开始

  1. // 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
  2. class Solution {
  3. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  4. ListNode head = new ListNode(0);
  5. ListNode node = head;
  6. while(l1 != null && l2 != null) {
  7. if(l1.val > l2.val) {
  8. node.next = l2;
  9. node = node.next;
  10. l2 = l2.next;
  11. } else {
  12. node.next = l1;
  13. node = node.next;
  14. l1 = l1.next;
  15. }
  16. }
  17. if(l1 != null) node.next = l1;
  18. if(l2 != null) node.next = l2;
  19. return head.next;
  20. }
  21. }

七 Map

拼多多:454:四数相加

map的基本操作:

  1. Map<Integer, Integer> map = new HashMap<>();
  2. map.put(key, value);
  3. map.containsKey(Key) //返回的是boolean -->
  4. map.get(Key) // return Value
  5. map.merge(Key, Value, BiFunction<? super V,? super V,? extends V> remappingFunction) // 新功能!!!

1 Map的遍历

Java中Map遍历的四种方式

  • 使用for循环加上Entry ```java /**
  • 最常见也是大多数情况下用的最多的,一般在键值对都需要使用 */ Map map = new HashMap(); map.put(“熊大”, “棕色”); map.put(“熊二”, “黄色”); for(Map.Entry entry : map.entrySet()){ String mapKey = entry.getKey(); String mapValue = entry.getValue(); System.out.println(mapKey+”:”+mapValue); } ```
  • 在for循环中遍历Key使用keySet和values集合,在性能上比使用entrySet较好

    1. Map <String,String>map = new HashMap<String,String>();
    2. map.put("熊大", "棕色");
    3. map.put("熊二", "黄色");
    4. //key
    5. for(String key : map.keySet()){
    6. System.out.println(key);
    7. }
    8. //value
    9. for(String value : map.values()){
    10. System.out.println(value);
    11. }
  • 通过使用Iterator遍历

    1. Iterator<Entry<String, String>> entries = map.entrySet().iterator();
    2. while(entries.hasNext()){
    3. Entry<String, String> entry = entries.next();
    4. String key = entry.getKey();
    5. String value = entry.getValue();
    6. System.out.println(key+":"+value);
    7. }
  • 通过键找值遍历,效率较低

    1. for(String key : map.keySet()){
    2. String value = map.get(key);
    3. System.out.println(key+":"+value);
    4. }

八 位运算(&、|、<<、>>、^(异或)、>>>)

剑指Offer65:不用加减乘除做加法

^:异或运算符,两数的对应位置不同才是1,否则是0

405:数字转换为十六进制数:StringBuffer

是无符号右移运算符,在后面补零
例: 4>>>1 = 2

LC:191 位运算法优化n中1的个数

**n&(n - 1)** 结果是把n的二进制位总最低位的1变成0之后的结果
**6&(6 - 1) = 4, 6 = (110), 4 = (100)** ,不断的进行 **n&(n - 1)** 操作,当值变为0时操作的次数,即为二进制n中1的个数。

九 栈

单调栈:739:每日温度、456:132模式
单调栈维护的都是临时状态,随时可能会被更新

十 树

110. 平衡二叉树

1530. 好叶子节点对的数量

之后再做做吧

十一 Set

特性:不可以添加重复元素

api如下:

  1. add()
  2. addAll()
  3. clear()
  4. contains(Object o)
  5. containsAll
  6. equals()
  7. hashCode()
  8. isEmpty()
  9. remove()
  10. removeAll()
  11. retainAll()
  12. toArray()
  13. size()

1 HashSet(哈希表)

普通的List集合转为HashSet

  1. Set<String> wordSet = new HashSet<>(wordList); // 直接将内容转为HashSet存储,加快查询的速度??

2 TreeSet

treeSet.ceiling可以用来维护滑动窗口

  1. public E ceiling(E e) {
  2. return m.ceilingKey(e);
  3. }
  4. /**
  5. * Returns the least key greater than or equal to the given key,
  6. * or {@code null} if there is no such key.
  7. *
  8. * @param key the key
  9. * @return the least key greater than or equal to {@code key},
  10. * or {@code null} if there is no such key
  11. * @throws ClassCastException if the specified key cannot be compared
  12. * with the keys currently in the map
  13. * @throws NullPointerException if the specified key is null
  14. * and this map does not permit null keys
  15. */
  16. K ceilingKey(K key);

treeSet.ceiling(key)用来返回窗口中大于等于给定键值的值,没有的话返回null

十二 java8的Stream流操作

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如 筛选 (filter), 排序 (sorted), 映射 (map) 聚合 (collect)等。 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

如:

  1. List<Integer> transactionsIds =
  2. widgets.stream()
  3. .filter(b -> b.getColor() == RED) // 过滤
  4. .sorted((x,y) -> x.getWeight() - y.getWeight()) // 排序
  5. .mapToInt(Widget::getWeight) // 映射(变换数据)
  6. .sum(); // 聚合
  7. // 列表转换为int数组也可以使用流操作转换
  8. List<Integer> arr = new ArrayList<>();
  9. return arr.stream().mapToInt(i -> i).toArray();
  10. // Int、Integer数组转换为List
  11. Integer [] myArray = { 1, 2, 3 };
  12. List myList = Arrays.stream(myArray).collect(Collectors.toList());
  13. //基本类型也可以实现转换(依赖boxed的装箱操作)
  14. int [] myArray2 = { 1, 2, 3 };
  15. List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
  16. // 再补充两个
  17. int max = Arrays.stream(myArray2).max().getAsInt();
  18. int sum = Arrays.stream(myArray2).sum();

1 一个旧的集合变成一个新的集合

  1. spu.stream().map(spu -> {
  2. }).collect(Collectors.toList());

十三 队列

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。 LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

  1. import java.util.LinkedList;
  2. import java.util.Queue;
  3. public class Main {
  4. public static void main(String[] args) {
  5. //add()和remove()方法在失败的时候会抛出异常(不推荐)
  6. Queue<String> queue = new LinkedList<String>();
  7. //添加元素
  8. queue.offer("a");
  9. queue.offer("b");
  10. queue.offer("c");
  11. queue.offer("d");
  12. queue.offer("e");
  13. for(String q : queue){
  14. System.out.println(q);
  15. }
  16. System.out.println("===");
  17. System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
  18. for(String q : queue){
  19. System.out.println(q);
  20. }
  21. System.out.println("===");
  22. System.out.println("element="+queue.element()); //返回第一个元素
  23. for(String q : queue){
  24. System.out.println(q);
  25. }
  26. System.out.println("===");
  27. System.out.println("peek="+queue.peek()); //返回第一个元素
  28. for(String q : queue){
  29. System.out.println(q);
  30. }
  31. }
  32. }

1 优先队列

23.合并K个升序链表

  1. // 使用优先队列求解
  2. PriorityQueue<ListNode> queue = new PriorityQueue<>((x1, y1) -> x1.val - y1.val);
  3. ListNode pre = new ListNode(0);
  4. ListNode tail = pre;
  5. for(ListNode list : lists) {
  6. if(list != null)
  7. queue.offer(list);
  8. }
  9. while(!queue.isEmpty()) {
  10. ListNode curr = queue.poll();
  11. tail.next = curr;
  12. tail = tail.next;
  13. if(curr.next != null) {
  14. queue.offer(curr.next);
  15. }
  16. }
  17. return pre.next;

十五 Sort

以下参考自排序总结

题型总结 - 图1

题型总结 - 图2

  1. Arrays.sort(arr, new Comparator<>() {
  2. @Override
  3. public int compare(int a, int b) {
  4. return a - b; // 升序排列
  5. }
  6. })

插入排序的时间复杂度是 O(n^2)O(_n_2) 时间复杂度是 O(nlog n) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2),其中最适合链表的排序算法是归并排序, 因为归并排序是稳定的

1 快速排序

时间复杂度:O(NlogN) 快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

哨兵: 选定每一个二分的起始节点作为判定点,j先从右往左找小于的值,i再从左往右找大于的值,将两者交换,依次进行下去,直到i >= j停止,将哨兵放在中间,迭代上述过程,直到数组长度等于1

2 希尔排序

希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

题型总结 - 图3

  1. /**
  2. * 希尔排序
  3. *
  4. * @param array
  5. * @return
  6. */
  7. public static int[] ShellSort(int[] array) {
  8. int len = array.length;
  9. int temp, gap = len / 2;
  10. while (gap > 0) {
  11. for (int i = gap; i < len; i++) {
  12. temp = array[i];
  13. int preIndex = i - gap;
  14. while (preIndex >= 0 && array[preIndex] > temp) {
  15. array[preIndex + gap] = array[preIndex];
  16. preIndex -= gap;
  17. }
  18. array[preIndex + gap] = temp;
  19. }
  20. gap /= 2;
  21. }
  22. return array;
  23. }

3 归并排序

时间复杂度:O(NlogN)

归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,考虑到递归调用的栈空间,自顶向下归并排序的空间复杂度是 O(logn)。如果要达到O(1) 的空间复杂度,则需要使用自底向上的实现方式

分而治之:

  • 找到数组、链表(使用快慢指针)的中间值,不断二分迭代,再进行比较

代码实现

  1. /**
  2. * 二路-归并排序
  3. *
  4. * @param array
  5. * @return
  6. */
  7. public static int[] MergeSort(int[] array) {
  8. if (array.length < 2) return array;
  9. int mid = array.length / 2;
  10. int[] left = Arrays.copyOfRange(array, 0, mid);
  11. int[] right = Arrays.copyOfRange(array, mid, array.length);
  12. return merge(MergeSort(left), MergeSort(right));
  13. }
  14. /**
  15. * 归并排序——将两段排序好的数组结合成一个排序数组
  16. *
  17. * @param left
  18. * @param right
  19. * @return
  20. */
  21. public static int[] merge(int[] left, int[] right) {
  22. int[] result = new int[left.length + right.length];
  23. for (int index = 0, i = 0, j = 0; index < result.length; index++) {
  24. if (i >= left.length)
  25. result[index] = right[j++];
  26. else if (j >= right.length)
  27. result[index] = left[i++];
  28. else if (left[i] > right[j])
  29. result[index] = right[j++];
  30. else
  31. result[index] = left[i++];
  32. }
  33. return result;
  34. }

以链表的归并排序为例:

  1. ListNode head;
  2. public ListNode sortList(ListNode head, ListNode tail) {
  3. // 定义边界情况
  4. if(head == null) return head;
  5. if(head.next == tail) {
  6. head.next = null;
  7. return head;
  8. }
  9. // 定义快慢指针
  10. ListNode fast = head, low = head;
  11. // 使用快慢指针查找链表中值
  12. while(fast.next != null && fast != null) {
  13. fast = fast.next;
  14. low = low.next;
  15. while(fast != null) {
  16. fast = fast.next;
  17. }
  18. }
  19. ListNode list1 = sortList(head, low);
  20. ListNode list2 = sortList(low, tail);
  21. ListNode res = merge(list1, list2);
  22. return res;
  23. }
  24. public ListNode merge(...) {...}

4 堆排序

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

  1. ** 大顶堆原理:**根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大顶堆。大顶堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。<br /> **小顶堆原理:**根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者,称为小顶堆。小堆堆要求根节点的关键字既小于或等于左子树的关键字值,又小于或等于右子树的关键字值。

堆排序的基本思路

  • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  • 将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。 ```java

import java.util.Arrays; /**

  • @author Administrator / public class HeapSort { public static void main(String []args){

    1. int []arr = {7,6,7,11,5,12,3,0,1};
    2. System.out.println("排序前:"+Arrays.toString(arr));
    3. sort(arr);
    4. System.out.println("排序前:"+Arrays.toString(arr));

    }

    public static void sort(int []arr){

    1. //1.构建大顶堆
    2. for(int i=arr.length/2-1;i>=0;i--){
    3. //从第一个非叶子结点从下至上,从右至左调整结构
    4. adjustHeap(arr,i,arr.length);
    5. }
    6. //2.调整堆结构+交换堆顶元素与末尾元素
    7. for(int j=arr.length-1;j>0;j--){
    8. swap(arr,0,j);//将堆顶元素与末尾元素进行交换
    9. adjustHeap(arr,0,j);//重新对堆进行调整
    10. }

    }

    /**

    • 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
    • @param arr
    • @param i
    • @param length / public static void adjustHeap(int []arr,int i,int length){ int temp = arr[i];//先取出当前元素i for(int k=i2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始

      1. if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
      2. k++;
      3. }
      4. if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
      5. arr[i] = arr[k];
      6. i = k;
      7. }else{
      8. break;
      9. }

      } arr[i] = temp;//将temp值放到最终的位置 }

      /**

    • 交换元素
    • @param arr
    • @param a
    • @param b */ public static void swap(int []arr,int a ,int b){ int temp=arr[a]; arr[a] = arr[b]; arr[b] = temp; } } ```

5 桶排序

时间复杂度:O(N), **164: 最大间距**

桶的间距:ids = Math.max((max - min) / (length - 1) )
桶的数量:num = (max - min) / ids + 1

十六 String

String、StringBuffer、StringBuilder的关系

String StringBuffer StringBuilder
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 可变类,速度更快
不可变 可变 可变
线程安全 (有synchronized) 线程不安全(无synchronized)
多线程操作字符串 单线程操作字符串

StringBuffer的赋值:通过构造函数赋值

两份代码:

  1. StringBuffer stb1 = new StringBuffer();
  2. int i = 0, n = s.length(); //s = "abc"
  3. while(i < n) {
  4. if(Character.isLetterOrDigit(s.charAt(i))) {
  5. stb1.append(Character.toLowerCase(s.charAt(i)));
  6. }
  7. i++;
  8. }
  9. // stb1 = "abc", stb2 = "cba"
  10. StringBuffer stb2 = new StringBuffer(stb1).reverse();
  11. return stb2.toString().equals(stb1.toString()); // false
  1. StringBuffer stb1 = new StringBuffer();
  2. StringBuffer stb2 = new StringBuffer();
  3. int i = 0, n = s.length();
  4. while(i < n) {
  5. if(Character.isLetterOrDigit(s.charAt(i))) {
  6. stb1.append(Character.toLowerCase(s.charAt(i)));
  7. }
  8. i++;
  9. }
  10. // stb1 = stb2 = "abc"
  11. stb2 = stb1.reverse(); // 错误写法
  12. return stb2.toString().equals(stb1.toString()); // true

StringBuffer str1= new StringBuffer();//1
StringBuffer str2 = new StringBuffer();//2
str2=str1执行后,指向同一个StringBuffer//1 的对象地址。
第2就不被引用,操作str1,str2都是操作第一个new StringBuffer() 所以改其中任一个值,另一个也会变。