Leetcode - 模板思路+易错点

-------------------------- 模板 --------------------------

动态规划

总结

 递归+额外空间保存中间状态(减少重复计算)

步骤

拿leetcode题目为例: https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/nge-tou-zi-de-dian-shu-dong-tai-gui-hua-ji-qi-yo-3/

使用动态规划解决问题一般分为三步:

  • 表示状态
  • 找出状态转移方程
  • 边界处理

下面我们一步一步分析,相信你一定会有所收获!

表示状态
分析问题的状态时,不要分析整体,只分析最后一个阶段即可因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。

对于这道题,最后一个阶段是什么呢?

通过题目我们知道一共投掷 nn 枚骰子,那最后一个阶段很显然就是:当投掷完 nn 枚骰子后,各个点数出现的次数。

注意,这里的点数指的是前 nn 枚骰子的点数和,而不是第 nn 枚骰子的点数,下文同理。

找出了最后一个阶段,那状态表示就简单了。

  • 首先用数组的第一维来表示阶段,也就是投掷完了几枚骰子。
  • 然后用第二维来表示投掷完这些骰子后,可能出现的点数。
  • 数组的值就表示,该阶段各个点数出现的次数。

所以状态表示就是这样的:dp[i][j]dp[i][j] ,表示投掷完 ii 枚骰子后,点数 jj 的出现次数。

 
 

找出状态转移方程
找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。

最后一个阶段也就是投掷完 nn 枚骰子后的这个阶段,我们用 dp[n][j]dp[n][j] 来表示最后一个阶段点数 jj 出现的次数。

单单看第 nn 枚骰子,它的点数可能为 1 , 2, 3, ... , 61,2,3,...,6 ,因此投掷完 nn 枚骰子后点数 jj 出现的次数,可以由投掷完 n-1n−1 枚骰子后,对应点数 j-1, j-2, j-3, ... , j-6j−1,j−2,j−3,...,j−6 出现的次数之和转化过来。

for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
    dp[n][j] += dp[n-1][j - i]
}

 

边界处理
这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了

我们可以直接知道的状态是啥,就是第一阶段的状态:投掷完 11 枚骰子后,它的可能点数分别为 1, 2, 3, ... , 61,2,3,...,6 ,并且每个点数出现的次数都是 11 .

for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}

 

位运算

  • 与(&)、非(~)、或(|)、异或(^)
  • 位运算的优先级小于==, 因此在该位运算语句中 if ( (nums[i] & mask) == 0),要加入括号。
  • 详细解析:位运算 - 位运算的常见用法/题目

 

把整数最右边一个1变成0 : 把该整数减去1,再和原来的整数做与(&)运算

把一个整数减去1,再和原来的整数做与运算,会把该整数最右边一个1变成0。

那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。

eg:

原整数为 1110...

1110 & (1110 - 1) = 1110 & 1101 = 1100

1100 & (1100 - 1) = 1100 & 1011 = 1000

1000 & (1000 - 1) = 1000 & 0111 = 0000

 

判断整数第i位是否是1:num & (1<

假设整数为: 0110 0000

如果我们想判断第4位是否为1, 那就把1右移4为 0000 1000, 和原数据0110 0000做与运算,结果为0.代表第4为不是1.

如果我们想判断第6位是否为1, 那就把1右移6为 0010 0000, 和原数据0110 0000做与运算,结果不为0.代表第6为是1.

 

把0的第i位变成1:0 ^ (1<
  • 0与任何数字异或还是该数字本身 

 

两个相同的数字做异或(^), 等于0

  • 0与任何数字异或还是该数字本身 
  • 两个相同的数字做异或,等于0

 

海量数据

  • 不能把所有的数据加载进内存中
  • 需要维护一个固定大小的容器,每进来一个元素进行比较和保留

 例题:

面试题40. 最小的k个数

 

题目描述的暗示

暗示你用HashMap

“假设数组中没有重复的数字”/ “保证只有一个答案”

暗示你用int[26],int[52],int[256]

当题干提示“只包含大写or小写字母”,那么使用int[26];

如果大小写均有,使用int[52];

如果是任意字符,则是int[256]

暗示你用数组解题

“在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。”

 

正序会覆盖,试试倒序

有时题目会要求在O(k) (或者比正常思路小)的空间复杂度里操作,可能需要重复利用空间。

如果从0往后使用,极有可能会overlap。这时可以倒序处理。

119. Pascal's Triangle II

class Solution {
    public List getRow(int rowIndex) {
    	List result = new ArrayList<>();
    	int[] array = new int[rowIndex+1];

    	if(rowIndex < 0){
    		return result;
    	}

    	for(int i=0; i<=rowIndex; i++){
    		for(int j = i; j >= 0; j--){
    			if(j == 0 || j == i){
    				array[j] = 1;
    				continue;
    			}
    			array[j] = array[j] + array[j-1];

    		}
    	}
    	for(int q = 0; q < array.length; q++){
    		result.add(array[q]);
    	}
    	return result;

    }
}

  

二叉树

当递归函数需要记录两种信息

例题:判断一个树是否为平衡二叉树 https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/

又要记录树的高度int,又要记录树是否是平衡的boolean,可以用-1来代替boolean(false),>0的int代替boolean(true)

 

二叉搜索树

模板

把一个二叉搜索树“中序遍历”,会是一个递增的序列。例如下图,中序遍历后是1,5,6,7,8

Leetcode - 模板思路+易错点_第1张图片

 

 

注意

不能单纯的只递归左节点<中间节点 && 右节点>中间节点,不然下面的case无法通过。(4虽然小于7, 但是因为4在6的右边,也应该大于6才行)

 

Leetcode - 模板思路+易错点_第2张图片

 

 

Sliding Window 滑动窗口

模板

整体思路:左右两个指针。右指针在for循环里面控制,左指针在for内部的while循环控制。当窗口内部的某条件不符合题目要求时,会在while循环里移动左指针。

public int slidingWindowTemplate(String[] a, ...) {
    // 输入参数有效性判断
    if (...) {
        ...
    }

    // 申请一个散列,用于记录窗口中具体元素的个数情况
    // 这里用数组的形式呈现,也可以考虑其他数据结构
    int[] hash = new int[...];

    // 预处理(可省), 一般情况是改变 hash
    ...

    // l 表示左指针
    // count 记录当前的条件,具体根据题目要求来定义
    // result 用来存放结果
    int l = 0, count = ..., result = ...;

    //*important* for循环只控制右指针,左指针通过内部的while语句控制
    for (int r = 0; r < A.length; ++r) {
        // 更新新元素在散列中的数量
        hash[A[r]]--;

        // 根据窗口的变更结果来改变条件值
        if (hash[A[r]] == ...) {
            count++;
        }

        // 如果当前条件不满足,移动左指针直至条件满足为止
        while (count > K || ...) {
            ...
            if (...) {
                count--;
            }
            hash[A[l]]++;
            l++;
        }

        // 更新结果
        results = ...
    }

    return results;
}

  

注意事项

1- 有时可以存储item本身,也可以存储index下标

2- 使用HashSet或者HashMap,让查询的时间复杂度降到O(1)

3-当题干提示只包含大写or小写字母,那么使用int[26]; 如果大小写均有,使用int[52];如果是任意字符,则是int[128]

4-当for循环的index变量, 在for循环外依旧要使用。注意该index已经超过了for循环设置的limit,例如:

当跑出for循环外,i的值变成了11,而不是10。如果想使用for的最后一个index值(10),需要将i (11)减去1后再使用。

int i = 0;
for(; i <= 10; i++){
     //do something...
}
System.out.println("i value = " + i); //i = 11,not 10  

5-for循环的执行顺序

第一次进入for循环:1--> 2

其余次进入for循环:3 --> 2

因此,该循环最后一次i会变成11,从而造成>10,跳出循环。

Leetcode - 模板思路+易错点_第3张图片

 

-------------------------- 易错点 --------------------------

快速排序

相等的情况怎么处理,特别容易出错

1-在普通情况下,在while循环当中,两个while循环都要包含==情况

Leetcode - 模板思路+易错点_第4张图片

 

 

2-在需要自定义“大小排序”函数时,不要声明boolean返回值的判断函数,一定要是int返回型得函数 (0代表相等,>0代表n1>n2, <0代表n1等于情况。

大数

1-当题目要求输出max/min时,中途会有max/min操作。此时切记不要中途做mod操作,因为原本A>B,但是如果因为A超过了规定的模值,提前mod的话,会导致A

    public int cuttingRope(int n) {
        BigInteger[] cache = new BigInteger[n+1];
        cache[0]= new BigInteger("1");
        cache[1]= new BigInteger("1");
        BigInteger e= new BigInteger("1000000007");
        for(int i =2; i <= n; i++){
            BigInteger temp_max = new BigInteger("0");
            BigInteger max = new BigInteger("0");

            for(int j = 1; j <= i-1; j++){
                //如果在这里提前mod...会在下一步max()出错
                BigInteger t1 = (BigInteger.valueOf(j).multiply(cache[i-j]));
                BigInteger t2 = (BigInteger.valueOf(j).multiply(BigInteger.valueOf(i-j)));

                //这里牵扯到max比较,因为必须全都保存原始数据
                temp_max = t1.max(t2);
                max = max.max(temp_max);
            }
            //这里也不能mod,要全都保存原始数据
            cache[i] = max;
        }

        //最最后返回时,再取mod
        return cache[n].mod(e).intValue();
    }

  

 

-------------------------- 常用类 --------------------------

Arrays

import java.util.Arrays;

 

快排排序 Arrays.sort(int[]/long[]/short[]/char[]/byte[]/float[]/double[]) 

int[] test = new int[]{0,3,4,2,1};
Arrays.sort(test); // test change to {0,1,2,3,4}

 

指定坐标区间快速排序 Arrays.sort(int[] a, int fromIndex, int toIndex) 

[fromIndex, toIndex)

 

类型转换

List <> int[][]

List list = new ArrayList<>();

int[][] result = list.toArray(new int[list.size][]); 

 

 

注意:此方法不适合一维数组, 因为如果一维数组调用“泛型”那个api,返回的应该是Integer[],而不是int[].

// from java.util.List interface

Object[] toArray();        // 非泛型
 T[] toArray(T[] a); //泛型

  

  

 

int > char

因为是高精度>低精度的去转换,可能出现丢失精度的情况,需要强制转换。

  • char c = 'a' + 10;
  • char c = (char) ('a' + 10); //correct

 

char, char[], int, long, float, double, boolean > String

 String.valueOf();

 

String <> char[]

char[] = String.toCharArray();

String = new String(char[]);

String = String.valueOf(char[]);

 

int <> BigInteger

int = BigInteger.intValue();

BigInteger = BigInteger.valueOf();

>>>THIS IS WRONG: BigInteger = new BigInteger(int);

能直接用new方法构造BigInteger的, 有 new BigInteger(String / long),  new BigInteger(int[] / byte[])

 

int[][]

初始化

只定义有多少行即可,每一行的具体细节可以之后再赋值

//只初始化有多少行即可       
int size = 5; int[][] result_arr = new int[size][]; //再单独处理每一行的细节 for(int i = 0; i < size; i++){ result_arr[i] = listToArray(result.get(i)); //具体逻辑 }

  

 

String

直接比较数字大小

可以直接调用String.compareTo()函数

int result = "123".compareTo("987") // result < 0
int result = "987".compareTo("123") // result > 0
int result = "123".compareTo("123") // result = 0

  

截取substring

注意函数名字的大小写,

  • 是String.substring(int i, int j) // [i,j)
  • 不是String.subString()

 

split()

以下的“$”代表一个空格“ ”

"hello$world".split(" ") --> {“hello”, "world"}

"hello$$world".split(" ") --> {“hello”, "", "world"}  // Not {“hello”, "$", "world"} 

"hello$$$world".split(" ") --> {“hello”, "", "", "world"} // Not {“hello”, "$", "$", "world"} 

 

trim()

以下的“$”代表一个空格“ ”

"$$$hello$$$$world$$$$$$".trim() --> "hello$$$$world"

 

LinkedList

通用!!! 可以用来替代ArrayList, Stack, Queue

public class LinkedList
    extends AbstractSequentialList
    implements List, Deque, Cloneable, java.io.Serializable

  

 

代替ArrayList

LinkedList.get(int index); // inherit AbstractList

LinkedList.set(int index, E); // inherit AbstractList

LinkedList.add(E); // inherit AbstractList

 

代替Stack

LinkedList本质上是一个Queue,只不过因其实现了Deque,所以可以双向操作。

 

Stack的push() : LinkedList.add(E); // inherit AbstractList, "function" equals addLast() -- inherit from Deque

Stack的pop() : LinkedList.pollLast(); // inherit from Deque

Stack的peek() : LinkedList.peekLast(); // inherit from Deque

 

代替Queue

Queue的add() : LinkedList.add(E); // inherit AbstractList, "function" equals addLast() -- inherit from Deque

Queue的poll() : LinkedList.poll(); // inherit from Deque

Queue的peek() : LinkedList.peekLast(); // inherit from Deque

 

深度拷贝 Deep Clone

list2 is the deep clone of list1.

 LinkedList list1 = new  LinkedList();

 LinkedList list2 = new  LinkedList (list1);

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection c) {
        this();
        addAll(c);
    }

  

 

ArrayList

不能用[]下标访问

ArrayList list = new ArrayList<>();

>>>THIS IS WRONG: Integer i = list[0];

 

>>>THIS IS RIGHT: Integer i = list.get(0);  

 

初始化后,不能直接调用set(int index, E element)

ArrayList list = new ArrayList<>();

list.set(int index, E element); // Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

因为ArrayList初始化后的size是0,紧接着做任何的set都会报错。必须先使用add()操作扩容size才可。详细请查阅ArrayList的size扩容文章。

 

HashSet

HashSet的遍历

        HashSet set = new HashSet<>();
        Iterator iter = set.iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }    

  

HashSet的add()

如果所加的元素,之前不存在,会返回true。如果已经存在,则返回false。

 

 

PriorityQueue 优先队列

https://www.cnblogs.com/yongh/p/9945539.html

最大堆 & 最小堆 初始化

PriorityQueue(优先队列),一个基于优先级堆的无界优先级队列。实际上是一个堆(不指定Comparator时默认为最小堆),通过传入自定义的Comparator函数可以实现大顶堆。

PriorityQueue minHeap = new PriorityQueue(); //小顶堆,默认容量为11
PriorityQueue maxHeap = new PriorityQueue(11,new Comparator<Integer>(){ //大顶堆,容量11
    @Override
    public int compare(Integer i1,Integer i2){
        return i2-i1;
    }
});

  

常用方法

PriorityQueue的常用方法有:poll(),offer(Object),size(),peek()等。

  • 插入方法 offer()、poll()、add() 时间复杂度为O(log(n)) ;
  • 删除方法 remove(Object) 时间复杂度为O(n)
  • 包含方法 contains(Object) 时间复杂度为O(n);
  • 检索方法(peek、element 和 size)时间复杂度为O(1)。

 

初始化capacity和size不一样

PriorityQueue queue = new PriorityQueue<>(6, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        }); //capacity = 6

int size = queue.size(); //size = 0

  

 

你可能感兴趣的:(Leetcode - 模板思路+易错点)