之前阿里的一个笔试题,能否存在三个点,将数组进行四分操作,O(n)复杂度的,只需利用一个数组加上一个HashMap 来进行缓存即可。
无思路:三板斧,digui,hash,类比,
时刻要有递归与动态规划结合的思想。
有树有图多用递归
实现大小堆的时候两种 方式 1.自己构造实现Comparator接口,在实例化PriorityQueue时自己传入参数
return a - b;就是从小到大,return b - a,就是从大到小
2.jdk8的环境支持直接传入比较器
Collections.sort(height, (a, b) -> {
if(a[0] != b[0])
return a[0] - b[0];
return a[1] - b[1];
});
Queue<Integer> pq = new PriorityQueue<>((a, b) -> (b - a));
218. The Skyline Problem
微信的那个考题,找出边界线
很不错的一个方法
一个很重要的思路:用高度的正负号进行缓存是入队列还是出队列。
思想还是降维度的思想
public List<int[]> getSkyline(int[][] buildings) {
List<int[]> result = new ArrayList<>();
List<int[]> height = new ArrayList<>();
for (int[] temp : buildings ) {
height.add(new int[] {temp[0], -temp[2]});
height.add(new int[] {temp[1], temp[2]});
}
Collections.sort(height, (a, b) -> {
if (a[0] != b[0]) {
return a[0] - b[0];
} else {
return a[1] - b[1];
}
});
Queue<Integer> pq = new PriorityQueue<Integer>((a, b) -> (b - a));
int pre = 0;
pq.offer(0);
for (int [] h : height) {
if (h[1] < 0) {
pq.offer(-h[1]);
} else {
pq.remove(h[1]);
}
int cur = pq.peek();
if (pre != cur) {
result.add(new int[]{h[0], cur});
pre = cur;
}
}
return result;
}
124. Binary Tree Maximum Path Sum
思路:依据最高点的想法,更新本身的参数,依次进行迭代处理,注意当节点小与0的时候
73. Set Matrix Zeroes
思路:如果m * n 矩阵中某个元素为0,那么整个行清零,整个列同时也清零。
O(m + n)空间复杂度,遍历了
最优解空间复杂度可以:缩减到O(1),用第一行,第一列进行标记
17. Letter Combinations of a Phone Number
给一串电话号码,输出其中电话号码代表的所有可能的字符串。
思路:1.递归回溯法,
2.迭代版本,生成一个map用来做映射每一个数组,然后对每一个字符进行迭代。
200. Number of Islands
思路:统计出孤立岛的个数,这个有一个很厉害的地方,存在1就至少存在一个岛,然后按照上下左右的方式进行蔓延。递归的方法,当数组越界的时候,或者当前这个结点不是1,就停止蔓延。
103. Binary Tree Zigzag Level Order Traversal
要求:“Z”字形遍历数组,返回一个list。
思路:用递归实现,传入level参数,正常来看是从左向右的,这里面加了一个小的技巧,就是偶数的话,正常从后面加,奇数的话,从前面开始插入,这样就保证了是后序。
49. Group Anagrams
要求:输出同一组的序列,包括颠倒了顺序的。
[“eat”, “tea”, “tan”, “ate”, “nat”, “bat”], 输出
[
[“ate”, “eat”,”tea”],
[“nat”,”tan”],
[“bat”]
]
最优解: 用HashMap对index进行缓存
33. Search in Rotated Sorted Array
注意只包含两个数的数组情况,两个小于等于
93. Restore IP Addresses
输入字符串,输出可能的IP地址信息
[“255.255.11.135”, “255.255.111.35”]. (Order does not matter)
设置3个循环变量,对IP地址进行4分段处理,半段每一个段是否符合要求。
131. Palindrome Partitioning
判断list里面的元素是不是回文,需要用到stack进行缓存,
判断String是不是回文,直接两个指针从两头开始循环即可
//这个是一个典型的回溯方法解决的问题,需要好好理解下
public class Solution {
private List<List<String>> list;
private List<String> currLst;
public List<List<String>> partition(String s) {
list = new ArrayList<List<String>>();
currLst = new ArrayList<String>();
backTrack(s, 0);
return list;
}
public void backTrack(String s, int l){
if(currLst.size()>0 //the initial str could be palindrome
&& l>=s.length()){
List<String> r = new ArrayList<String>();
for (String single : currLst) {
r.add(single);
}
list.add(r);
}
for(int i=l;i<s.length();i++){
if(isPalindrome(s,l,i)){
if(l==i)
currLst.add(Character.toString(s.charAt(i)));
else
currLst.add(s.substring(l,i+1));
backTrack(s,i+1);
currLst.remove(currLst.size()-1);
}
}
}
public boolean isPalindrome (String s, int i, int j) {
if (i == j) {
return true;
}
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
}
105. Construct Binary Tree from Preorder and Inorder Traversal
这个在用数组处理的时候需要注意,preorder根节点的index的变换问题
34. Search for a Range
给一个单调不递减函数,找出给定数的范围,这个数可能存在重复,首先找出左下届,然后缓存右下届的范围,二分法查找右下届。
207. Course Schedule
这个问题意思是判断这个图是否存在环路,
最优解是采用广度搜索的办法,两个数组,一个存储list,一个存储是否被依赖,degree。当degree为0的时候count++,返回时判断count是否与numcourse相等
236. Lowest Common Ancestor of a Binary Tree
先决条件是这两个结点都在二叉树中。二叉树的最小公共祖先结点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
return left == null ? right : right == null ? left : root;
}
55. Jump Game
Given an array of non-negative integers, you are initially positioned at the first index of the array.
给定一个非负的数组,每一个的元素代表的是从当前的位置最大能跳转的间距
思路:1,从后往前进行跳跃,方法很巧妙,就是寻找出位置为0的时候的能否被前面的结点给跳过去。
2.从初始位置进行累加,判断jumparea是否大约等于i
139. Word Break
单词拆解,给定字典,判断在不在字典中
思路: 1,递归加上map的缓存,注意递归基,递归调用,以及返回值
2.新建一个boolean数组,对数组中的每一个元素进行迭代处理。
56. Merge Intervals
合并重叠区间,
思路:1, 利用jdk8的排序功能, intervals.sort((i1, i2) -> Integer.compare(i1.start, i2.start));
依据开始进行升序排列。
最优2.对开始与结尾分别存储, 然后排序,判断开始是否大于上一个结尾,大于的话,新开辟空间。
134. Gas Station
环形跑道,加气gas[i], 消耗cost[i], 返回能跑下来的i的值
思路:1.分别从每个开始,迭代记录能否跑下来,不能的话换下一个起点。O(n平方)复杂度
2.最优解: 非常牛的一种解法! O(n)复杂度,一遍进行遍历即可, 思想:如果从A出发到不了B,那么A到B之间的任何点都到达不了B,只能从B+ 1出发,同时如果gas的和小于cost的和,怎么都到达不了。
227. Basic Calculator II
对一个符合加减乘除规则的字符串输入,
思路: 1.用stack, 进行缓存,默认是加法, 当是乘除的时候直接进行计算再入栈,符号直接取反再入栈。
2.因为只涉及到二级缓存, 所以三个变量即可
正常实现的思路是中缀转后缀的表达式。注意其中的出现的空格
148. Sort List
O(NlogN)时间复杂度,常数空间复杂度
最优解:采用归并排序,注意分割链表的时候,按照链表对前半部分的next设置为null
150. Evaluate Reverse Polish Notation
实现数的加减乘除,使用stack,需要注意的是String的equals方法。
210. Course Schedule II
之前的上课的升级版本,当课程可以完全被完成,返回上课顺序,
思路:数组缓存本课程有几个依赖,链表缓存依赖本课程的课程有什么,然后学习本课程的时候可以对好多数组的中的数进行减一操作
//典型的用链表构造图,并进行深度搜索与广度搜索的例子
//深度搜索在搜索的时候调用自己的搜索函数
//就是判断这个
public class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] incLinkCounts = new int[numCourses];
List<List<Integer>> adjs = new ArrayList<>(numCourses);
initialiseGraph(incLinkCounts, adjs, prerequisites);
return solveByBFS(incLinkCounts, adjs);
return solveByDFS(adjs);
}
private void initialiseGraph(int[] incLinkCounts, List<List<Integer>> adjs, int[][] prerequisites){
int n = incLinkCounts.length;
while (n-- > 0) adjs.add(new ArrayList<>());
for (int[] edge : prerequisites) {
incLinkCounts[edge[0]]++;
adjs.get(edge[1]).add(edge[0]);
}
}
private int[] solveByBFS(int[] incLinkCounts, List<List<Integer>> adjs){
int[] order = new int[incLinkCounts.length];
Queue<Integer> toVisit = new ArrayDeque<>();
for (int i = 0; i < incLinkCounts.length; i++) {
if (incLinkCounts[i] == 0) toVisit.offer(i);
}
int visited = 0;
while (!toVisit.isEmpty()) {
int from = toVisit.poll();
order[visited++] = from;
for (int to : adjs.get(from)) {
incLinkCounts[to]--;
if (incLinkCounts[to] == 0) toVisit.offer(to);
}
}
return visited == incLinkCounts.length ? order : new int[0];
}
private int[] solveByDFS(List<List<Integer>> adjs) {
BitSet hasCycle = new BitSet(1);
BitSet visited = new BitSet(adjs.size());
BitSet onStack = new BitSet(adjs.size());
Deque<Integer> order = new ArrayDeque<>();
for (int i = adjs.size() - 1; i >= 0; i--) {
if (visited.get(i) == false && hasOrder(i, adjs, visited, onStack, order) == false) return new int[0];
}
int[] orderArray = new int[adjs.size()];
for (int i = 0; !order.isEmpty(); i++) orderArray[i] = order.pop();
return orderArray;
}
private boolean hasOrder(int from, List<List<Integer>> adjs, BitSet visited, BitSet onStack, Deque<Integer> order) {
visited.set(from);
onStack.set(from);
for (int to : adjs.get(from)) {
if (visited.get(to) == false) {
if (hasOrder(to, adjs, visited, onStack, order) == false) return false;
} else if (onStack.get(to) == true) {
return false;
}
}
onStack.clear(from);
order.push(from);
return true;
}
}
208. Implement Trie (Prefix Tree)
实现一个单词查找树,之前在cc150里面看到过,不同的是,这里面采用children进行子字符的缓存。
//典型的一个单词查找树的例子
class TrieNode {
public char val;
public boolean isWord;
public TrieNode[] children = new TrieNode[26];
public TrieNode() {}
TrieNode(char c){
TrieNode node = new TrieNode();
node.val = c;
}
}
public class Trie {
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
root.val = ' ';
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode ws = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
if(ws.children[c - 'a'] == null){
ws.children[c - 'a'] = new TrieNode(c);
}
ws = ws.children[c - 'a'];
}
ws.isWord = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode ws = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
if(ws.children[c - 'a'] == null) return false;
ws = ws.children[c - 'a'];
}
return ws.isWord;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode ws = root;
for(int i = 0; i < prefix.length(); i++){
char c = prefix.charAt(i);
if(ws.children[c - 'a'] == null) return false;
ws = ws.children[c - 'a'];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
324. Wiggle Sort II
要求:1) Given nums = [1, 5, 1, 1, 6, 4], one possible answer is [1, 4, 1, 5, 1, 6].
摇摆排序,时间复杂度O(n),空间复杂度,O(1),此题综合了快速选择第k个最大的数,quickselect思想,选取出中位数,然后利用(1 + 2 * index)% (n | 1);得到映射后的idnex下标。
最优解:贴出代码如下:
public class Solution {
public void wiggleSort(int[] nums) {
int median = findKthLargest(nums, (nums.length + 1) / 2);
int n = nums.length;
int left = 0, i = 0, right = n - 1;
while (i <= right) {
if (nums[newIndex(i,n)] > median) {
swap(nums, newIndex(left++,n), newIndex(i++,n));
}
else if (nums[newIndex(i,n)] < median) {
swap(nums, newIndex(right--,n), newIndex(i,n));
}
else {
i++;
}
}
}
private int newIndex(int index, int n) {
return (1 + 2*index) % (n | 1);
}
public int findKthLargest(int[] a, int k) {
int n = a.length;
int p = quickSelect(a, 0, n - 1, n - k + 1);
return a[p];
}
// return the index of the kth smallest number
int quickSelect(int[] a, int lo, int hi, int k) {
// use quick sort's idea
// put nums that are <= pivot to the left
// put nums that are > pivot to the right
int i = lo, j = hi, pivot = a[hi];
while (i < j) {
if (a[i++] > pivot) swap(a, --i, --j);
}
swap(a, i, hi);
// count the nums that are <= pivot from lo
int m = i - lo + 1;
// pivot is the one!
if (m == k) return i;
// pivot is too big, so it must be on the left
else if (m > k) return quickSelect(a, lo, i - 1, k);
// pivot is too small, so it must be on the right
else return quickSelect(a, i + 1, hi, k - m);
}
void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
79. Word Search
比较好的一个思路,如果发现相等用亦或,防止重复利用
private boolean exist(char[][] board, int y, int x, char[] word, int i) {
if (i == word.length) return true;
if (y < 0 || x < 0 || y == board.length || x == board[y].length) return false;
if (board[y][x] != word[i]) return false;
board[y][x] ^= 256;
boolean exist = exist(board, y, x+1, word, i+1)
|| exist(board, y, x-1, word, i+1)
|| exist(board, y+1, x, word, i+1)
|| exist(board, y-1, x, word, i+1);
board[y][x] ^= 256;
return exist;
}
322. Coin Change
要求:输入所有零钱的金额,以及需要拼凑起来的所有总额,输出使用最小的硬币个数
思路 :1.采用原始的递归加缓存的大法宝,时间有点慢
2.最优解:利用迭代的方式进行处理,新建一个数组,从0 到 amout依次进行累加统计,好多的迭代版本的动态规划问题都可以参考这个模式
//与爬楼梯问题,走棋盘问题类似
public class Solution {
public int coinChange(int[] coins, int amount) {
if (amount < 0) {
return -1;
}
int[] dp = new int[amount + 1];
int sum = 0;
while (++sum <= amount) {
int min = -1;
for (int co : coins) {
if (co <= sum && dp[sum - co] != -1) {
int temp = dp[sum - co] + 1;
min = min < 0 ? temp : (temp < min ? temp : min);
}
}
dp[sum] = min;
}
return dp[amount];
}
}
54. Spiral Matrix
要求: 像蜗牛壳一样螺旋的访问向里访问数组元素
思路:利用数学公式计算出响应的下标,注意一维数组和一维列向量的情况, 需要对k进行限制。
5. Longest Palindromic Substring
找到并返回一个字符串的最长的回文子串
思路:以字符串的第i个字符作为回文字符串的中心,奇数个或者偶数个,从本身或者本身 + 1开始向两边进行扩展
3. Longest Substring Without Repeating Characters
找到并返回没有重复的子串长度
思路:1.从开始进行遍历,得到稍微长一点的,时间超时,非常low的方法
2.用hashmap或者数组进行缓存上一次出现的长度,O(n)时间复杂度,很不错的方法。
int max = 0;
Map<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0, j =0; i < s.length(); i++) {
if (map.containsKey(s.charAt(i))) {
j = Math.max(j, map.get(s.charAt(i)) + 1);
}
map.put(s.charAt(i), i);
max = Math.max(max, i - j + 1);
}
return max;
98. Validate Binary Search Tree
这个题目在cc150上做过
思路:1.利用查找二叉树的夹逼方法,long可以过
2.递归,这个竟然忘记了,中序遍历得到相应的解
179. Largest Number
For example, given [3, 30, 34, 5, 9], the largest formed number is 9534330.
给定数组,输出组成的最大的数
思路:本来是用Integer的sort方法,但是发现不好用,改用String的sort, 注意数组这块的排序问题,都只能使用包装类
15. 3Sum
For example, given array S = [-1, 0, 1, 2, -1, -4],
A solution set is:[ [-1, 0, 1], [-1, -1, 2]]
最优解:先对数组进行排序,然后再做累加处理
注意提前结束循环
非常巧妙的写法,不光实现了加减操作,同时去除了重复元素
if(sum <= 0) while(x < y && nums[x] == nums[x + 1]) x++ ;
if(sum >= 0) while(x < y && nums[y] == nums[y - 1]) y--;
127. Word Ladder
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]
As one shortest transformation is “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
return its length 5.
存在问题,暂时搁置
91. Decode Ways
Given encoded message “12”, it could be decoded as “AB” (1 2) or “L” (12).
要求:数字翻译成字母一共有多少种形式
// 思路: 当某个为等于0的时候,其实不作用,只和前面的那个一起作用,所以跳过,当不为0的时候,如果两位数在0 到 26之间,两种累计组合的方式,既可以和上一个不足和,也可以和上一个组合,所以加了两个。
public class Solution {
public int numDecodings(String s) {
int n = s.length();
if (n == 0) return 0;
int[] memo = new int[n+1];
memo[n] = 1;
memo[n-1] = s.charAt(n-1) != '0' ? 1 : 0;
for (int i = n - 2; i >= 0; i--)
if (s.charAt(i) == '0') continue;
else memo[i] = (Integer.parseInt(s.substring(i,i+2))<=26) ? memo[i+1]+memo[i+2] : memo[i+1];
return memo[0];
}
}