Leetcode - 模板思路+易错点
-------------------------- 模板 --------------------------
数组里找超过1/2,1/3个数的数字
通解通法:摩尔投票法
一个数组,超过1/2的数字,至多有1个;超过1/3的数字,至多有2个...
超过1/2: 169. 多数元素
超过1/3:229. 求众数 II
疑问:最后留下的一定是超过1/2,或者1/3么?
1/2版本
想象着这样一个画面:会议大厅站满了投票代表,每个都有一个牌子上面写着自己所选的候选人的名字。然后选举意见不合的(所选的候选人不同)两个人,会打一架,并且会同时击倒对方。显而易见,如果一个人拥有的选票比其它所有人加起来的选票还要多的话,这个候选人将会赢得这场“战争”,当混乱结束,最后剩下的那个代表(可能会有多个)将会来自多数人所站的阵营。但是如果所有参加候选人的选票都不是大多数(选票都未超过一半),那么最后站在那的代表(一个人)并不能代表所有的选票的大多数。因此,当某人站到最后时,需要统计他所选的候选人的选票是否超过一半(包括倒下的),来判断选票结果是否有效。
1/3版本
有一个对摩尔投票法非常形象的比喻:多方混战。
首先要知道,在任何数组中,出现次数大于该数组长度1/3的值最多只有两个。
我们把这道题比作一场多方混战,战斗结果一定只有最多两个阵营幸存,其他阵营被歼灭。数组中的数字即代表某士兵所在的阵营。
我们维护两个潜在幸存阵营A和B。我们遍历数组,如果遇到了属于A或者属于B的士兵,则把士兵加入A或B队伍中,该队伍人数加一。继续遍历。
如果遇到了一个士兵既不属于A阵营,也不属于B阵营,这时有两种情况:
-
A阵营和B阵营都还有活着的士兵,那么进行一次厮杀,参与厮杀的三个士兵全部阵亡:A阵营的一个士兵阵亡,B阵营的一个士兵阵亡,这个不知道从哪个阵营来的士兵也阵亡。继续遍历。
-
A阵营或B阵营已经没有士兵了。这个阵营暂时从地球上消失了。那么把当前遍历到的新士兵算作新的潜在幸存阵营,这个新阵营只有他一个人。继续遍历。
大战结束,最后A和B阵营就是初始人数最多的阵营。判断一下A,B的人数是否超过所有人数的三分之一就行了。
Q:在leetocde里,为什么n/2众数不需要第二次遍历,n/3众数就需要第二次遍历呢?
A:因为leetcode在1/2题目描述中,确保了肯定存在这个结果。如果没有这个确保, 也需要二次循环。而1/3题目描述中,并没有保证一定有一个/两个,所以要二次循环在统计一次投票数。
动态规划
递归+额外空间保存中间状态(减少重复计算)
模板套路,【必看】:https://www.cnblogs.com/frankcui/p/11674594.html
最长公共序列
题目链接:https://www.nowcoder.com/practice/6d29638c85bb4ffd80c020fe244baf11?tpId=295&tqId=991075&ru=%2Fpractice%2Fb4525d1d84934cf280439aeecc36f4af&qru=%2Fta%2Fformat-top101%2Fquestion-ranking&sourceUrl=%2Fexam%2Fcompany
题目难点:
1.需要先用dp数据找出最长公共序列的长度
2.还需要从dp数组末尾,倒序去拼回公共序列(只有来自左上的时候才添加本级字符)


位运算
- 与(&)、非(~)、或(|)、异或(^)
- 位运算的优先级小于==, 因此在该位运算语句中 if ( (nums[i] & mask) == 0),要加入括号。
- 详细解析:位运算 - 位运算的常见用法/题目
海量数据
- 不能把所有的数据加载进内存中
- 需要维护一个固定大小的容器,每进来一个元素进行比较和保留
例题:
面试题40. 最小的k个数
题目描述的暗示
暗示你用HashMap
“假设数组中没有重复的数字”/ “保证只有一个答案”
暗示你用int[26],int[52],int[256]
当题干提示“只包含大写or小写字母”,那么使用int[26];
如果大小写均有,使用int[52];
如果是任意字符,则是int[256] ; 例如:https://leetcode-cn.com/explore/featured/card/bytedance/242/string/1012/
![]()
暗示你用数组解题
“在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。”
正序会覆盖,试试倒序
有时题目会要求在O(k) (或者比正常思路小)的空间复杂度里操作,可能需要重复利用空间。
如果从0往后使用,极有可能会overlap。这时可以倒序处理。
119. Pascal's Triangle II
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> 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

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

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,跳出循环。

-------------------------- 易错点 --------------------------
快速排序
相等的情况怎么处理,特别容易出错
1-在普通情况下,在while循环当中,两个while循环都要包含==情况

易错点
- benchmark采用left坐标
- benchmark和left, right相等的情况,继续move
- 如果需要自定义大小关系,不要用boolean函数,而是返回1,0,-1来代表大于,等于,小于。这样可以完美融合原来的逻辑。
2-在需要自定义“大小排序”函数时,不要声明boolean返回值的判断函数,一定要是int返回型得函数 (0代表相等,>0代表n1>n2, <0代表n1<n2)。方便单独处理等于情况。
大数
1-当题目要求输出max/min时,中途会有max/min操作。此时切记不要中途做mod操作,因为原本A>B,但是如果因为A超过了规定的模值,提前mod的话,会导致A<B。正确的做法应该先全都保存原始数据在BigInteger中,最最后返回时再mod。例子:面试题14- II. 剪绳子 II
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.copyOfRange(int[] nums, int start, int end)
返回一个新的复制的数组, 范围依然是左闭右开 [start, end)
快排排序 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)
快速排序 - 自定义比较器
Comparator<T>中的T,代表着temp里每个item的类型
//Comparator<T>中的T,代表着temp里每个item的类型 int[][] temp = new int[][]{{1,2},{1,2}}; Arrays.sort(temp, new Comparator<int[]>(){ @Override public int compare(int[] a, int[]b){ return a[0] - b[0]; } });
注意:如果a[0],b[0]出现了正数-负数,那么直接相减会溢出....就必须使用Integer.compare()
例子:
class Solution {
//看这里!!!
private class LargerNumberComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
String order1 = a + b;
String order2 = b + a;
return order2.compareTo(order1);
}
}
public String largestNumber(int[] nums) {
// Get input integers as strings.
String[] asStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
asStrs[i] = String.valueOf(nums[i]);
}
//看这里!!!
// Sort strings according to custom comparator.
Arrays.sort(asStrs, new LargerNumberComparator());
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
// Build largest number from sorted array.
String largestNumberStr = new String();
for (String numAsStr : asStrs) {
largestNumberStr += numAsStr;
}
return largestNumberStr;
}
}
String
给String进行reverse
String reverse1=new StringBuffer("abcdefg").reverse().toString();
System.out.println("reverse1 = " + reverse1); //"gfedcba"
类型转换
List<String> -- String[]
/* * String[] --> List<String> */ String[] strs = new String[] {"aaa", "bbb", "ccc"}; List<String> list = Arrays.asList(strs); //二步合为一步 //List<String> list = Arrays.asList("aaa", "bbb", "ccc"); /* * List<String> --> String[] */ //传入初始化长度的数组对象,返回该对象数组 String[] array = list.toArray(new String[list.size()]); //没有参数,就会返回Object[] //Object[] array = list.toArray();
https://blog.csdn.net/meism5/article/details/89874236
List<int[]> <> int[][]
List<int[]> list = new ArrayList<>(); int[][] result = list.toArray(new int[list.size][]);
注意:此方法不适合一维数组, 因为如果一维数组调用“泛型”那个api,返回的应该是Integer[],而不是int[].
// from java.util.List interface Object[] toArray(); // 非泛型 <T> 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)); //具体逻辑 }
Character
判断是否是数字/字母
boolean Character.isLetter(int/char); //字母
boolean Character.isDigit(int/char); //数字
boolean Character.isLetterOrDigit(int/char); //字母或数字
String
直接比较数字大小 - String.compareTo()
可以直接调用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<E>
通用!!! 可以用来替代ArrayList, Stack, Queue
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
代替ArrayList
LinkedList<E>.get(int index); // inherit AbstractList<E>
LinkedList<E>.set(int index, E); // inherit AbstractList<E>
LinkedList<E>.add(E); // inherit AbstractList<E>
代替Stack
LinkedList本质上是一个Queue,只不过因其实现了Deque,所以可以双向操作。
Stack的push() : LinkedList<E>.add(E); // inherit AbstractList<E>, "function" equals addLast() -- inherit from Deque<E>
Stack的pop() : LinkedList<E>.pollLast(); // inherit from Deque<E>
Stack的peek() : LinkedList<E>.peekLast(); // inherit from Deque<E>
代替Queue
Queue的add() : LinkedList<E>.add(E); // inherit AbstractList<E>, "function" equals addLast() -- inherit from Deque<E>
Queue的poll() : LinkedList<E>.poll(); // inherit from Deque<E>
Queue的peek() : LinkedList<E>.peekLast(); // inherit from Deque<E>
深度拷贝 Deep Clone
list2 is the deep clone of list1.
LinkedList<E> list1 = new LinkedList<E>();
LinkedList<E> list2 = new LinkedList<E> (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<? extends E> c) {
this();
addAll(c);
}
下标存储/获取
T var = LinkedList<T>.get(int index)
int index = LinkedList<T>.indexOf(Object obj);
ArrayList<E>
复制新ArrayList容器
所有继承自Collections接口的容器(例如LinkedList, 详见 https://www.cnblogs.com/frankcui/p/13599021.html),都有该方法。很方便快速复制一个新容器。
//应用
ArrayList<Integer> newArrayList = new ArrayList<>(oldArrayList);
//API
/**
* 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 ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList<Integer>.remove(int) 删除的是(Integer)int元素,还是int坐标下的Integer?
答案:
ArrayList.remove(int)删除的是int坐标下的integer,但是如果调用ArrayList.add(int) -- 本身没有这个API,但会被自动装箱为ArrayList.add(Integer),是在尾部加入这个Integer
ArrayList.remove(Object)才是直接删除Integer
不能用[]下标访问
ArrayList<Integer> list = new ArrayList<>();
>>>THIS IS WRONG: Integer i = list[0];
>>>THIS IS RIGHT: Integer i = list.get(0);
初始化后,不能直接调用set(int index, E element)
ArrayList<Integer> 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<E>的size扩容文章。
HashMap<K,V>
HashMap常用方法
//get all map's key list
map.keySet();
//get all map's value list
map.values();
//get all map.Entry list
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
for(Map.Entry<Integer, Integer> entry : entries){
System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
}
HashMap遍历
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
if (e.getValue()==null)
return true;
}
方法二(推荐):
HashMap<Integer, Integer> map = new HashMap<>();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
}
HashMap没有contains()方法, 只有containsKey()和containsValue()方法
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
HashSet<E>
HashSet的遍历
HashSet<String> set = new HashSet<>();
Iterator<String> 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<Integer> minHeap = new PriorityQueue<Integer>(); //小顶堆,默认容量为11 PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(11,new Comparator<Integer>(){ //大顶堆,容量11 @Override public int compare(Integer i1,Integer i2){ return i2-i1;//大顶堆
//return i1-i2;//小顶堆
} });
常用方法
PriorityQueue的常用方法有:poll(),offer(Object),size(),peek()等。
- 插入方法 offer()、add() 时间复杂度为O(log(n)) --> n 是堆的大小;
- 删除方法 remove(Object) 时间复杂度为O(n)
- 包含方法 contains(Object) 时间复杂度为O(n);
- 检索方法(peek、element 和 size),弹出栈顶元素poll() ,时间复杂度为O(1)。
初始化capacity和size不一样
PriorityQueue<Integer> 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 Tips ----------------
二维数组“上下左右”的移动
构建下面的数组,然后遍历它即可实现上下左右的移动。
int[][] direction = new int[4][]; direction[0] = new int[]{-1, 0}; direction[1] = new int[]{1, 0}; direction[2] = new int[]{0, -1}; direction[3] = new int[]{0, 1};
奇数/偶数总个数,找Median中间值
对于奇数的情况,直接找到最中间的数即可; 偶数的话需要求最中间两个数的平均值。
若整个序列的长度为n,那无论n是奇数、偶数,都可以用下面的公式来找中间数的“索引”:
(n+1)/2 + (n+2)/2
__________________
2
n = 7 时,[ 8/2+9/2 ] / 2 = [4 + 4] / 2 = 4 (第四个,index为3,起始从0开始)-- 奇数的情况,直接找到最中间的数即可
n = 8 时,[9/2+10/2 ] / 2 = [4 + 5] / 2 -- 偶数的话需要求最中间两个数的平均值
int相加防止溢出
如果两个int n1和int n2,都接近了Integer.MAX_VALUE.然而又想求其平均值。
直接相加就会溢出。所以可以转换为:
n1+(n2-n1)/2
- = (2*n1)/2 + (n2-n1)/2 = (n1+n2)/2
数组打印格式 (int[]打印,ArrayList<T>打印)
在牛客网经常能看到以下格式的数组格式输出:
//一维数组
[2,3,4,1,2]
//二维数组 [[3,8,9],[0,5,2,3],[3,6,7,8],[1,4,3]]
ArrayList(Integer).toString() -- 能用于输出一维/多维数组
ArrayList<T> --> AbstractList<T> --> Collection<T>
在Collection<T>中,重写了toString(),可以输出一维/多维数组
//这样做行不通!!!!
// int[] arr = new int[2];
// new ArrayList<Integer>(arr).toString()
//二维数组
ArrayList<ArrayList<Integer>> res = new ArrayList<>(); ArrayList<Integer> res1 = new ArrayList<>(); res1.add(1); res1.add(1); res1.add(1); ArrayList<Integer> res2 = new ArrayList<>(); res2.add(2); res2.add(2); res2.add(2); res2.add(2); ArrayList<Integer> res3 = new ArrayList<>(); res3.add(3); res3.add(3); res.add(res1); res.add(res2); res.add(res3); System.out.println(res.toString()); //[[1, 1, 1], [2, 2, 2, 2], [3, 3]]
Arrays.toString(int[]/double[]/char[]/...) -- 只能用于输出一维数组
只能用于输出一维数组 int[], double[], char[],...
Arrays.toString(new int[]{5,1,2,3}); //输出[5,1,2,3] Arrays.toString(new double[]{5.0,1.0,2.0,3.0}); //输出[5.0,1.0,2.0,3.0]

截取
int[]截取
List<>截取
浙公网安备 33010602011771号