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<>截取