LeetCode Problems 201-300.
201. Bitwise AND of Numbers Range
https://leetcode.com/problems/bitwise-and-of-numbers-range/
实际上,如果将left
和right
转化为相同长度的二进制串,按位与的结果就是两个二进制串的最长相等前缀。因此,不断右移left
和right
,记录移动的位数,直到left
和right
相等;再将left
左移这个位数进行复原。
class Solution {
public int rangeBitwiseAnd(int left, int right) {
if (left == 0) {
return 0;
}
int count = 0;
while (left != right) {
left >>>= 1;
right >>>= 1;
count++;
}
return left <<= count;
}
}
202. Happy Number
https://leetcode.com/problems/happy-number/
逐位计算currentDigit
的平方和,累加得到当前的result
。每计算一次当前的result
都要确保result
不等于1或者4(陷入循环,一定不是happy number)。确认result
没有问题后,将其作为新一轮的输入,继续进行判断。
class Solution {
public boolean isHappy(int n) {
int result = 0, current = 0;
while (result != 1) {
// calculate current result
while (n != 0) {
current = n % 10;
n /= 10;
result += current * current;
}
// result cannot be 1 or 4, otherwise fall into loop
if (result > 1 && result != 4) {
n = result;
result = 0;
} else {
break;
}
}
return result == 1;
}
}
203. Remove Linked List Elements
https://leetcode.com/problems/remove-linked-list-elements/
使用cur
指针标记当前节点,prev
指针标记之前一个节点。正常情况下,两个指针每一次向右移动一格;但cur
指向的元素值与val
相等时,prev
指向cur
的下一个元素,跳过cur
,相当于删除了当前元素。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode prev = dummyHead, cur = head;
while (cur != null) {
// when current element's value is val, skip
if (cur.val == val) {
prev.next = cur.next;
} else {
prev = prev.next;
}
// always move forward cur
cur = cur.next;
}
return dummyHead.next;
}
}
204. Count Primes
https://leetcode.com/problems/count-primes/
此题借鉴了埃拉托斯特尼筛法的思想,即找到一个范围内的较小质数时,删去这个质数的倍数,因为他们一定是合数。
使用一个数组prime
标记所有小于n
的数字,如果有i < n
且i
为合数,则prime[i] = false
。初始情况下,所有数字都假定为质数。(初始化结果都是true)
一开始,假设比n
小的质数有n/2
个(即所有正奇数的个数;尽管1不是质数,但2恰好是唯一偶质数,二者抵消,数量上仍然是n
的一半)。
由于只需要寻找奇数中的合数,从3开始遍历所有小于sqrt(n)
的奇数。假定当前遍历的数字是i
,如果已经能确定i
是质数,则需要开启第二重循环,从i*i
开始遍历至n
。这一循环的目的是剔除掉所有i
的若干倍数,因为它们都是合数。注意:此处的起点是i*i
而非i
,因为在不重复的前提下,i*i
是通过i
得知的最小合数(固然,2*i
, 3*i
等等数都是合数,但是这些在i == 2
, i == 3
时已经考虑过了,会重复)。此外,只需要考虑i*i + 偶数*i
的情况,因为根据奇偶性,i*i + 奇数*i
一定是一个偶数&合数。
每通过以上步骤排除一个合数,就在原始结果n/2
的基础上减去1;同时,这个数在数组prime
中被标记为false。之后的遍历中,遇到这些合数就可以直接跳过了。
class Solution {
public int countPrimes(int n) {
if (n < 3) {
return 0;
}
boolean[] prime = new boolean[n];
Arrays.fill(prime, true);
// assume all odd numbers are prime number
int result = n / 2;
for (int i = 3; i * i < n; i += 2) {
// skip a composite number
if (!prime[i]) {
continue;
}
// multiples of composite numbers are skipped
for (int j = i * i; j < n; j += 2 * i) {
// this multiple of i has not been excluded
if (prime[j]) {
result--;
prime[j] = false;
}
}
}
return result;
}
}
205. Isomorphic Strings
https://leetcode.com/problems/isomorphic-strings/
人造两个map,使得A中相同下标的字符能对应B,B中相同下标的字符能对应A。
class Solution {
public boolean isIsomorphic(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
char[] sMap = new char[256];
char[] tMap = new char[256];
for (int i = 0; i < sChars.length; i++) {
char a = sChars[i], b = tChars[i];
// not yet mapped
if (sMap[a] == 0 && tMap[b] == 0) {
sMap[a] = b;
tMap[b] = a;
} else if (sMap[a] != b || tMap[b] != a) {
return false;
}
}
return true;
}
}
206. Reverse Linked List
https://leetcode.com/problems/reverse-linked-list/
第一种,使用非递归方法,用prev
指向head
的前一个元素。从头到尾,每一次循环都掉转一个指针的方向,即从head
指向prev
,再分别让两个指针向右移。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode prev = null;
while (head != null) {
ListNode next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
}
第二种,使用递归方法。与第一种方法类似,只是prev
与head
向前移动的迭代过程变成了递归调用。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(head, null);
}
private ListNode reverse(ListNode head, ListNode prev) {
if (head == null) {
return prev;
}
ListNode next = head.next;
head.next = prev;
return reverse(next, head);
}
}
207. Course Schedule
https://leetcode.com/problems/course-schedule/
此题运用DFS,将先修课程数组转化为图的形式,对于每门课程,维护一个状态数组,分别是0:未访问 1:访问中 2:已访问。在DFS的时候检查当前课程和后续课程的状态,判断是否存在两门课互为前提的情况,如果有,则说明不能安排课程。
class Solution {
boolean isCycle = false;
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<Integer>());
}
// add all subsequent courses for each course
for (int[] prerequisite : prerequisites)
graph.get( prerequisite[1] ).add( prerequisite[0] );
int[] courseStatus = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
if (courseStatus[i] == 0) {
dfs(courseStatus, i, graph);
}
}
return !isCycle;
}
// 0 unvisited, 1 visiting, 2 visited
private void dfs(int[] courseStatus, int node, List<List<Integer>> graph) {
courseStatus[node] = 1;
for (int next : graph.get(node)) {
if (courseStatus[next] == 0) {
dfs(courseStatus, next, graph);
}
if (courseStatus[next] == 1) {
isCycle = true;
}
}
courseStatus[node] = 2;
}
}
208. Implement Trie (Prefix Tree)
https://leetcode.com/problems/implement-trie-prefix-tree/
单词拆分为逐个字母,每个字母对应到一个树节点。节点存储两个信息:一个是当前节点是否为单词的最后一个字母;另一个存储所有下一个字母的信息。
class Trie {
class TrieNode {
public TrieNode() {}
boolean isEnd = false;
TrieNode[] children = new TrieNode[26];
}
private TrieNode root = new TrieNode();
/** Initialize your data structure here. */
public Trie() {}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isEnd = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
return false;
}
cur = cur.children[c - 'a'];
}
// must exactly match, so the word in trie should also end
return cur.isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode cur = root;
for (char c : prefix.toCharArray()) {
if (cur.children[c - 'a'] == null) {
return false;
}
cur = cur.children[c - 'a'];
}
return true;
}
}
209. Minimum Size Subarray Sum
https://leetcode.com/problems/minimum-size-subarray-sum/
使用双指针法,sum
总是先加上nums[right]
,并且右指针右移;在sum
大于s
的前提下,再试图删去尽可能多的nums[left]
,使得left
和right
差值尽量小。如果遍历完整个数组后都没有符合条件的情况,则返回0。
class Solution {
public int minSubArrayLen(int s, int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int sum = 0, left = 0, right = 0;
// By default, the minimum length is Max integer
int result = Integer.MAX_VALUE;
while (right < nums.length) {
sum += nums[right];
right++;
// delete as many nums[left] as possible
while (sum >= s) {
result = Math.min(result, right - left);
sum -= nums[left];
left++;
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
210. Course Schedule II
https://leetcode.com/problems/course-schedule-ii/
在Course Schedule的基础上添加结果数组,每个dfs到达终点时表示当前最后选修的课程,这门课程应该放置于当前结果数组的末尾。
class Solution {
boolean isCycle = false;
int[] result;
int resultIndex;
public int[] findOrder(int numCourses, int[][] prerequisites) {
result = new int[numCourses];
resultIndex = numCourses - 1; // start from the last course
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<Integer>());
}
// add all subsequent courses for each course
for (int[] prerequisite : prerequisites) {
graph.get( prerequisite[1] ).add( prerequisite[0] );
}
int[] courseStatus = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
if (courseStatus[i] == 0) {
dfs(courseStatus, i, graph);
}
}
return isCycle ? new int[]{} : result;
}
// 0 unvisited, 1 visiting, 2 visited
private void dfs(int[] courseStatus, int node, List<List<Integer>> graph) {
courseStatus[node] = 1;
for (int next : graph.get(node)) {
if (courseStatus[next] == 0) {
dfs(courseStatus, next, graph);
}
if (courseStatus[next] == 1) {
isCycle = true;
}
}
courseStatus[node] = 2;
result[resultIndex] = node;
resultIndex--;
}
}
211. Design Add and Search Words Data Structure
https://leetcode.com/problems/design-add-and-search-words-data-structure/
借鉴Trie的思想,添加单词操作不变;搜索单词则略有变化,因为要处理.
的情况。思路是使用index
跟踪当前已遍历的长度,正常情况下保证子节点不空,并且下一个字符匹配即可;但如果遇到.
时,由于这个字符能匹配任何字符,因此需要检查所有但非空子节点,判断之后是否能匹配。
class WordDictionary {
public class TrieNode {
public TrieNode() {}
TrieNode[] children = new TrieNode[26];
boolean isEnd = false;
public boolean match(String word, int index) {
if (index == word.length()) {
return isEnd;
}
// check all not-null children
if (word.charAt(index) == '.') {
for (int i = 0 ; i < 26; i++) {
if (children[i] != null && children[i].match(word, index + 1)) {
return true;
}
}
return false;
} else {
return children[word.charAt(index) - 'a'] != null && children[word.charAt(index) - 'a'].match(word, index + 1);
}
}
}
private TrieNode root = new TrieNode();
/** Initialize your data structure here. */
public WordDictionary() {}
/** Adds a word into the data structure. */
public void addWord(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isEnd = true;
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
public boolean search(String word) {
return root.match(word, 0);
}
}
212. Word Search II
https://leetcode.com/problems/word-search-ii/
构造Prefix Tree,将输入的words
中每一个单词存入每个TrieNode中,注意完成输入后,在最后一个字母的节点处将word
属性设为当前单词;再使用DFS,如果遍历的过程中遇到某个TrieNode中的单词不为空,说明已经遍历了一个完整的单词,将其添加到结果中。
class Solution {
public class TrieNode {
TrieNode[] children = new TrieNode[26];
String word;
}
public TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String word : words) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
// only set cur.word when finishing inserting a word
cur.word = word;
}
return root;
}
public List<String> findWords(char[][] board, String[] words) {
List<String> result = new ArrayList<>();
TrieNode root = buildTrie(words);
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
dfs(board, i, j, root, result);
}
}
return result;
}
public void dfs(char[][] board, int i, int j, TrieNode cur, List<String> result) {
char c = board[i][j];
if (c == '#' || cur.children[c - 'a'] == null) {
return;
}
cur = cur.children[c - 'a'];
// end of current word
if (cur.word != null) {
result.add(cur.word);
// avoid repeat searching
cur.word = null;
}
// temporarily mark visited character
board[i][j] = '#';
// try four adjacent grids
if (i > 0) {
dfs(board, i - 1, j, cur, result);
}
if (j > 0) {
dfs(board, i, j - 1, cur, result);
}
if (i < board.length - 1) {
dfs(board, i + 1, j, cur, result);
}
if (j < board[0].length - 1) {
dfs(board, i, j + 1, cur, result);
}
board[i][j] = c;
}
}
213. House Robber II
https://leetcode.com/problems/house-robber-ii/
为了避免圆环交接处相邻两户民居报警,只能重新划定范围,相当于沿用House Robber I的思路,进行比较:一次抢[0, nums.length - 2]
的范围,另一次抢[1, nums.length - 1]
的范围,选取收益大的那一次。
class Solution {
private int helper(int[] nums, int low, int high) {
// prev2: max profit without current number's prior number
// prev: max profit between (1. without current number 2. prev2 + current number)
int prev2 = 0, prev = 0;
for (int i = low; i <= high; i++) {
int temp = prev;
prev = Math.max(prev, prev2 + nums[i]);
prev2 = temp;
}
return prev;
}
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
} else if (nums.length == 1) {
return nums[0];
}
return Math.max(helper(nums, 0, nums.length - 2), helper(nums, 1, nums.length - 1));
}
}
214. Shortest Palindrome
https://leetcode.com/problems/shortest-palindrome/
显然,最直接的考虑是直接在s
前添加s
的逆,这样一定能得到回文串。但由于题目要求是最短但回文串,因此需要考虑的是,如何删减去粗浅思路的中间部分。换言之,如何找出中间的一部分“公共字符串”,能够用来构造回文串。例如,如果输入为s = aabba
,则aa
就是公共字符串:
原始构造:abbaa aabba
优化后构造: abb aa bba
为了寻找中间的字符串,使用两个指针分别从s
的头尾开始遍历。如果头尾指针指向的字符相等,则头指针向后移动一位;而尾指针每一次循环都向前移动一位。这一过程中两个指针必然相遇,因此头指针至少会移动一次。在这种情况下,便能够根据头指针的位置,将s
分成两个部分,其中一部分是后缀,另一部分进一步需要处理成为公共字符串,构成的公式是:后缀的倒序 + 待处理公共字符串 + 后缀。当然,倘若头指针直接移动到了s
末尾,说明s
已经是一个回文串了。
class Solution {
public String shortestPalindrome(String s) {
if (s.length() == 0 || s.length() == 1) {
return s;
}
// try to divide the string into two parts, using head
int head = 0;
for (int tail = s.length() - 1; tail >= 0; tail--) {
if (s.charAt(head) == s.charAt(tail)) {
head++;
}
}
// already a palindrome
if (head == s.length()) {
return s;
}
String suffix = s.substring(head);
return new StringBuilder(suffix).reverse().toString() + shortestPalindrome(s.substring(0, head)) + suffix;
}
}
215. Kth Largest Element in an Array
https://leetcode.com/problems/kth-largest-element-in-an-array/
使用快速选择算法,利用pivot
元素为轴,交换双指针对应的数nums[left]
和nums[right]
,小于pivot
的数放在pivot
左侧,大于pivot
的数放右侧。时间复杂度为O(n)
。
class Solution {
public int findKthLargest(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return -1;
}
// kth largest is equal to (nums.length - k)th smallest
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
// quick select: kth smallest
public int quickSelect(int[] nums, int begin, int end, int k) {
if (begin == end) {
return nums[begin];
}
if (begin > end) {
return Integer.MAX_VALUE;
}
int pivot = nums[begin + (end - begin) / 2];
int left = begin;
int right = end;
while (left <= right) {
while (left <= right && nums[left] < pivot) {
left++;
}
while (left <= right && nums[right] > pivot) {
right--;
}
if (left <= right) {
swap(nums, left, right);
left++;
right--;
}
}
if (right >= k && right >= begin) {
return quickSelect(nums, begin, right, k);
} else if (left <= k && left <= end) {
return quickSelect(nums, left, end, k);
} else {
return nums[k];
}
}
private void swap(int[] A, int i, int j) {
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
216. Combination Sum III
https://leetcode.com/problems/combination-sum-iii/
套用此前总结的回溯问题模版即可,注意迭代时的变量含义。
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List< List<Integer> > result = new ArrayList<>();
backtrack(result, new ArrayList<>(), k, n, 1);
return result;
}
private void backtrack(List< List<Integer> > result, List<Integer> temp, int k, int n, int start) {
if (temp.size() == k && n == 0) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = start; i <= 9; i++) {
temp.add(i);
// need k numbers; remain sum is n - i; starting index is i + 1
backtrack(result, temp, k, n - i, i + 1);
temp.remove(temp.size() - 1);
}
}
}
217. Contains Duplicate
https://leetcode.com/problems/contains-duplicate/
向HashSet中添加元素,重复的数字无法被添加。
class Solution {
public boolean containsDuplicate(int[] nums) {
if (nums.length <= 1) {
return false;
}
Set<Integer> set = new HashSet<Integer>();
for (Integer num : nums) {
if (!set.add(num)) {
return true;
}
}
return false;
}
}
218. The Skyline Problem
https://leetcode.com/problems/the-skyline-problem/
把所有楼房的坐标与高度信息提取出来并排序,排序时按照先坐标(从左到右)后高度(从低到高)的顺序进行排列。注意提取时,可以将left edge的高度取个负数处理,这样做的目的是更好区分出left edge和right edge,方便之后的计算。
完成排序后,利用TreeMap的自动排序特性,以楼房高度为key,具有这个高度的楼房数量为value,把刚才提取出来的信息整理到TreeMap中。分情况讨论:
- 如果高度是负数,说明提取到的是left edge,意味着遇到了一栋新楼,直接把信息存进TreeMap中
- 如果高度为正,并且这个高度在TreeMap中的value是1,说明当前只有一栋楼是这个高度,并且此时我们已经遇到了其右边界。因此,我们需要把这个高度key移出TreeMap,这样子之后的轮廓打印中才能够正确打印出第二高的楼房高度
- 如果高度为正,并且这个高度在TreeMap中的value不止是1,说明不止一栋楼是这个高度,我们只是遇到了其中一栋楼的右边界,仍然还有其他的楼是这个高度。因此我们只需要修改TreeMap中这个高度key对应的value即可
完成TreeMap处理后,获取到TreeMap的最后一个key,也就是当前能被看到的最高的高度。这个高度与其对应的坐标点就是城市天际线中的轮廓点之一。需要注意,只有这一轮key发生了改变,才需要输出(不变的key意味着轮廓重合,不需要被打印出来)。
class Solution {
public List<List<Integer>> getSkyline(int[][] buildings) {
List<List<Integer>> result = new ArrayList();
List<int[]> height = new ArrayList();
for (int[] b : buildings) {
height.add(new int[]{b[0], -b[2]});
height.add(new int[]{b[1], b[2]});
}
// first sort by x coordinate, then sort by height
Collections.sort(height, (a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
// <height, num>
TreeMap<Integer, Integer> bst = new TreeMap();
bst.put(0, 1);
int prev = 0;
for (int[] h : height) {
if (h[1] < 0) {
// a new building is incoming
bst.put(-h[1], bst.getOrDefault(-h[1], 0) + 1);
} else if (bst.get(h[1]) == 1) {
// current highest building reaches end, should output second highest building later
bst.remove(h[1]);
} else {
bst.put(h[1], bst.get(h[1]) - 1);
}
// output highest new building
int cur = bst.lastKey();
if (cur != prev) {
result.add(new ArrayList(Arrays.asList(h[0], cur)));
prev = cur;
}
}
return result;
}
}
219. Contains Duplicate II
https://leetcode.com/problems/contains-duplicate-ii/
用一个HashMap存储当前数nums[i]
和下标i
。利用HashMap.put()
方法的返回值,判断遇到相同nums[i]
时下标之差为多少。
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
// if not present, new entry, else the old entry will be updated
Integer idx = map.put(nums[i], i);
if (idx != null && Math.abs(idx - i) <= k) {
return true;
}
}
return false;
}
}
220. Contains Duplicate III
https://leetcode.com/problems/contains-duplicate-iii/
由于题目提示问题可以在O(nlogk)
的时间复杂度内解决,因此想到了二叉搜索树,而结合二叉搜索树与去重复性特点的数据结构正好是TreeSet。
构造一个TreeSet,利用两个TreeSet的函数floor()
和ceiling()
分别找出小于等于当前num
的最大元素与集合中大于等于当前num
的最小元素,以此便能判断差值是否在范围内。同时,注意到题目限定了常数k
作为判定范围,实际上便是提示维持一个长度为k
的窗口大小用于判断内部的重复性,因此循环次数超过k
时,要将向前k
个位置的元素从TreeSet中删除。
最后,题目中可能出现整数溢出,因此应该将TreeSet初始化为Long格式。
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (nums == null || nums.length < 2) {
return false;
}
TreeSet<Long> set = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {
long num = (long)nums[i];
// get upper bound and lower bound of current num
// find if there is any element in this range
Long floor = set.floor(num), ceiling = set.ceiling(num);
if ((floor != null && num - floor <= t) || (ceiling != null && ceiling - num <= t)) {
return true;
}
// add current num to set
set.add(num);
// delete k-th number backward
if (i >= k) {
set.remove((long)nums[i - k]);
}
}
return false;
}
}
221. Maximal Square
https://leetcode.com/problems/maximal-square/
使用动态规划,dp[i][j
]代表在以[i, j]
这一格为右下角的正方形边长。若这一格的值为1,那这个正方形的边长就是他的上,左,斜上的最小值边长+1,因为任何一边短缺都构成不了正方形。
class Solution {
public int maximalSquare(char[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int row = matrix.length, col = matrix[0].length;
int[][] dp = new int[row + 1][col + 1];
int result = 0;
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
if (matrix[i-1][j-1] == '1') {
// minimum of up, left, upper left grids
dp[i][j] = Math.min(Math.min(dp[i-1][j-1], dp[i-1][j]), dp[i][j-1]) + 1;
result = Math.max(dp[i][j], result);
}
}
}
return result * result;
}
}
222. Count Complete Tree Nodes
https://leetcode.com/problems/count-complete-tree-nodes/
对于每一个节点:
- 计算左子树的高度
- 计算右子树的高度
- 左右相同,直接利用完全二叉树的特性算出答案
- 左右不同,递归至下一层继续计算子节点的左右子树深度。注意最后+1(自身节点的数量)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int countNodes(TreeNode root) {
int leftDepth = getLeftDepth(root);
int rightDepth = getRightDepth(root);
if (leftDepth == rightDepth) {
return (1 << leftDepth) - 1;
}
return 1 + countNodes(root.left) + countNodes(root.right);
}
private int getLeftDepth(TreeNode node) {
int depth = 0;
while (node != null) {
node = node.left;
depth++;
}
return depth;
}
private int getRightDepth(TreeNode node) {
int depth = 0;
while (node != null) {
node = node.right;
depth++;
}
return depth;
}
}
223. Rectangle Area
https://leetcode.com/problems/rectangle-area/
核心在于求出重叠部分的面积。要出现重叠,两矩形左边的最大值一定会小于右边的最小值;同理,下边的最大值会小于上边的最小值。
class Solution {
public int computeArea(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) {
if (bx1 > ax2 || bx2 < ax1) {
return (ax2 - ax1) * (ay2 - ay1) + (bx2 - bx1) * (by2 - by1);
}
int left = Math.max(ax1, bx1);
int right = Math.min(ax2, bx2);
int bottom = Math.max(ay1, by1);
int top = Math.min(ay2, by2);
int overlap = 0;
if (right > left && top > bottom) {
overlap = (right - left) * (top - bottom);
}
return (ax2 - ax1) * (ay2 - ay1) + (bx2 - bx1) * (by2 - by1) - overlap;
}
}
224. Basic Calculator
https://leetcode.com/problems/basic-calculator/
使用栈,遇到数字修改num
;遇到加减号将数字累计到结果中,并修改符号;遇到左括号把当前符号数弹入栈;遇到右括号则弹出栈顶符号数。得到最终结果前,需要将栈中的num
取出,结合对应的符号累加。
class Solution {
public int calculate(String s) {
if (s == null || s.length() == 0) {
return 0;
}
Stack<Integer> stack = new Stack<Integer>();
int result = 0, sign = 1, num = 0;
stack.push(sign);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
} else if (c == '+' || c == '-') {
result += sign * num;
sign = stack.peek() * (c == '+' ? 1 : -1);
num = 0;
} else if (c == '(') {
stack.push(sign);
} else if (c == ')') {
stack.pop();
}
}
result += sign * num;
return result;
}
}
225. Implement Stack using Queues
https://leetcode.com/problems/implement-stack-using-queues/
队列实现栈时,主要关注push()
。Queue添加元素默认添加在尾部,但可以将前面的元素全部逐个弹出再逐个加入到末尾,形成一种循环机制,以此达到“添加在头部”的效果。
class MyStack {
private Queue<Integer> queue = new ArrayDeque<>();
/** Initialize your data structure here. */
public MyStack() {}
/** Push element x onto stack. */
public void push(int x) {
queue.offer(x);
for (int i = 1; i < queue.size(); i++) {
queue.offer(queue.poll());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue.poll();
}
/** Get the top element. */
public int top() {
return queue.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue.isEmpty();
}
}
226. Invert Binary Tree
https://leetcode.com/problems/invert-binary-tree/
对于当前节点,左子树递归调用右支作为参数的invertTree()
,右子树调用左支作为参数的invertTree()
。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = root.left, right = root.right;
root.left = invertTree(right);
root.right = invertTree(left);
return root;
}
}
227. Basic Calculator II
https://leetcode.com/problems/basic-calculator-ii/
仍然使用栈,但与Basic Calculator不同,此次不需要考虑正负号,因此直接根据相应的运算符号处理栈即可。
需要注意,遇到空格时直接跳过;在处理完栈之后,要及时更新符号op
和操作数num
。此外,为了不遗漏算式中的最后一个数,可以使用一个技巧,即在原始的字符串后加一个+
号,这样使得最后一个数也能被推入栈中。最后累加栈中的所有数即可。
class Solution {
public int calculate(String s) {
if (s == null || s.length() == 0) {
return 0;
}
Stack<Integer> stack = new Stack<>();
// ensure that the last number can be pushed into the stack
s += '+';
// operand
int num = 0;
// operator
char op = '+';
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
continue;
}
if (c == ' ') {
continue;
}
if (op == '+') {
stack.push(num);
}
if (op == '-') {
stack.push(-num);
}
if (op == '*') {
stack.push(stack.pop() * num);
}
if (op == '/') {
stack.push(stack.pop() / num);
}
op = c;
num = 0;
}
int result = 0;
while (!stack.isEmpty()) {
result += stack.pop();
}
return result;
}
}
228. Summary Ranges
https://leetcode.com/problems/summary-ranges/
遍历数组,先记录当前的页码,之后向后搜索尽可能长的连续页码,直到下标无法继续移动为止。如果移动后下标指向的页码与当前页码不同,说明存在着连续页码;反之说明是单页。
class Solution {
public List<String> summaryRanges(int[] nums) {
List<String> result = new LinkedList<>();
if (nums == null || nums.length == 0) {
return result;
}
if (nums.length == 1) {
result.add(nums[0] + "");
return result;
}
for (int i = 0; i < nums.length; i++) {
int temp = nums[i];
// continuous pages
while (i + 1 < nums.length && (nums[i+1] - nums[i] == 1)) {
i++;
}
if (temp != nums[i]) {
result.add(temp + "->" + nums[i]);
} else {
result.add(temp + "");
}
}
return result;
}
}
229. Majority Element II
https://leetcode.com/problems/majority-element-ii/
与Majority Element II思想近似,仍旧采用投票表决的思想。由于题目要求选出超过出现⌊ n/3 ⌋
次的数,因此这样的数最多有两个,设定为candidate1
和candidate2
。
遍历数组时,遇到当前选出的candidate1
和candidate2
则次数+1;否则,次数各自-1(相当于当前数成对抵消了candidate1
和candidate2
)。如果原来的数次数都为0,且遇到了新的数,则candidate1
或candidate2
更换。第一次遍历数组后,可以最终投票表决出符合条件的数具体是多少,第二次遍历就只需统计这些数具体出现了几次。
class Solution {
public List<Integer> majorityElement(int[] nums) {
int count1 = 0, count2 = 0;
Integer candidate1 = null, candidate2 = null;
for (int num : nums) {
if (candidate1 != null && candidate1 == num) {
count1++;
} else if (candidate2 != null && candidate2 == num) {
count2++;
} else if (count1 == 0) {
// candidate1 has been updated
candidate1 = num;
count1++;
} else if (count2 == 0) {
// candidate2 has been updated
candidate2 = num;
count2++;
} else {
// reduce the portion of candidate1 and candidate2
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
for (int num : nums) {
if (candidate1 != null && candidate1 == num) {
count1++;
}
if (candidate2 != null && candidate2 == num) {
count2++;
}
}
List<Integer> result = new ArrayList<>();
if (count1 > nums.length/3) {
result.add(candidate1);
}
if (count2 > nums.length/3) {
result.add(candidate2);
}
return result;
}
}
230. Kth Smallest Element in a BST
https://leetcode.com/problems/kth-smallest-element-in-a-bst/
沿用中序遍历思想,直接遍历至树的最左端,再逐渐向右遍历,直到次数为k
即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int count = 0;
private boolean resultModified = false;
private int result = Integer.MIN_VALUE;
public int kthSmallest(TreeNode root, int k) {
helper(root, k);
return result;
}
private void helper(TreeNode node, int k) {
if (node == null || resultModified) {
return;
}
helper(node.left, k);
count++;
if (count == k) {
result = node.val;
resultModified = true;
return;
}
helper(node.right, k);
}
}
231. Power of Two
https://leetcode.com/problems/power-of-two/
二的次幂转换为二进制时,只有首位为1,其余都是0;利用这个特点,只需要判断n
和n-1
的与是否为0即可。
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
}
232. Implement Queue using Stacks
https://leetcode.com/problems/implement-queue-using-stacks/
使用双栈实现队列,一进一出。大致思路是队列加入元素时统一推进入栈in
;之后再一一弹出,进入出栈out
,由此模拟元素先进先出的顺序。因此,查看队首元素时,将入栈的元素推入出栈,获取出栈的栈顶元素即为队首元素。
class MyQueue {
Stack<Integer> in;
Stack<Integer> out;
/** Initialize your data structure here. */
public MyQueue() {
in = new Stack<Integer>();
out = new Stack<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
in.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
peek();
return out.pop();
}
/** Get the front element. */
public int peek() {
// if out is empty, pop top element of in and push it into out
if (out.isEmpty()) {
while (!in.isEmpty()) out.push(in.pop());
}
return out.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return in.isEmpty() && out.isEmpty();
}
}
如果场景升级,要求实现一个线程安全版本双栈实现队列,只需要利用Java的synchronized函数,在push()
和peek()
时加锁即可:
class MyQueue {
Object lock;
Stack<Integer> in;
Stack<Integer> out;
/** Initialize your data structure here. */
public MyQueue() {
lock = new Object();
in = new Stack<Integer>();
out = new Stack<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
// must get lock to run the code inside synchronized
synchronized(lock) {
in.push(x);
}
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
peek();
return out.pop();
}
/** Get the front element. */
public int peek() {
// if out is empty, pop top element of in and push it into out
if (out.isEmpty()) {
synchronized(lock) {
while (!in.isEmpty()) out.push(in.pop());
}
}
return out.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return in.isEmpty() && out.isEmpty();
}
}
233. Number of Digit One
https://leetcode.com/problems/number-of-digit-one/
纯数学推理题。观察,每10个数进行排列:
0, 1, 2, 3 ... 9 (1)
10, 11, 12, 13 ... 19 (1) + 10
20, 21, 22, 23 ... 29 (1)
...
90, 91, 92, 93 ... 99 (1)
100, 101, 102, 103 ... 109 (10 + 1)
110, 111, 112, 113 ... 119 (10 + 1) + 10
120, 121, 122, 123 ... 129 (10 + 1)
...
190, 191, 192, 193 ... 199 (10 + 1)
最基础的情况下,每10个数里一定有1个数个位为1,每100个数里一定有10个数十位为1,每1000个数里一定有100个数百位为1…因此核心思想是用10的次幂k
作为基底进行遍历。令r = n / k
,则以上的分析将产生1的个数:
r / 10 * k
接着考虑特殊行,也就是10,11,12… 100,101,102…特殊行的特点在于十位/百位/…总有一个1。所以,采用取模的办法,令m = n % k
,则在这一位上能够产生m + 1
个1。即:
r % 10 == 1 ? m + 1 : 0
最后,某位大于等于2时,情况一致,直接添加在原始情况上即可。总和是:
(r + 8) / 10 * k + (r % 10 == 1 ? m + 1 : 0)
class Solution {
public int countDigitOne(int n) {
int result = 0;
for (long k = 1; k <= n; k *= 10) {
long r = n / k, m = n % k;
result += (r + 8) / 10 * k + (r % 10 == 1 ? m + 1 : 0);
}
return result;
}
}
234. Palindrome Linked List
https://leetcode.com/problems/palindrome-linked-list/
使用快慢指针找到后半部分链表起点,并调转后半部分链表,再循环判断对称元素是否相等。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// odd length
if (fast != null) {
slow = slow.next;
}
// reverse second half of the list
slow = reverse(slow);
fast = head;
// compare two halves
while (slow != null) {
if (slow.val != fast.val) {
return false;
}
slow = slow.next;
fast = fast.next;
}
return true;
}
private ListNode reverse(ListNode head) {
ListNode prev = null;
while (head != null) {
ListNode next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
}
235. Lowest Common Ancestor of a Binary Search Tree
https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
如果当前节点root
的值root.val
位于[p, q]
内,说明root
已经是p
和q
的最小公共祖先;否则,根据root
和p.val
与q.val
的大小比较,调整搜索方向继续查找。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// root.val is not between p.val and q.val
while ((root.val - p.val) * (root.val - q.val) > 0) {
root = p.val < root.val ? root.left : root.right;
}
return root;
}
}
236. Lowest Common Ancestor of a Binary Tree
https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/
如果某一个节点的左右子树里都能找到p
或q
,说明当前节点就是最小的公共祖先;否则,哪一边找不到,就说明这一边没有,去另一边的子树找。当然,如果两边都没有,说明路径不对,返回null
退回上层。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
// whether can we find p or q in left & right subtrees
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// wrong path
if (left == null && right == null) {
return null;
}
// current node is LCA
if (left != null && right != null) {
return root;
}
// null in one subtree, find in the other
return left == null ? right : left;
}
}
237. Delete Node in a Linked List
https://leetcode.com/problems/delete-node-in-a-linked-list/
由于只给了一个node
节点的引用,因此不知道链表的头在哪里,无法真正意义上删除node
节点,只能将node
的下一个节点的值赋值给node
,再跳过node
的下一个节点,起到了删除的效果。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
238. Product of Array Except Self
https://leetcode.com/problems/product-of-array-except-self/
使用一个动态规划数组result
,第一次从左到右循环,得到每个nums[i]
左侧所有数的累乘结果,储存在result
中;第二次从右到左循环,利用一个变量right
乘以之前result
中的结果,并让right
乘以当前nums[i]
,以此获得nums[i]
右侧所有数的累乘结果。
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
int[] result = new int[len];
result[0] = 1;
// product of left part
for (int i = 1; i < len; i++) {
result[i] = result[i-1] * nums[i-1];
}
// product of right part
int right = 1;
for (int i = len - 1; i >= 0; i--) {
result[i] *= right;
right *= nums[i];
}
return result;
}
}
239. Sliding Window Maximum
https://leetcode.com/problems/sliding-window-maximum/
使用一个LinkedList模拟双头队列,队列存储当前窗口内所有元素的下标。流程有三步:
- 关注
[i - k + 1, i]
的窗口,当某个元素在队列中且不在窗口内时,将其从队首弹出 - 从队列尾部开始,将所有对应小于
nums[i]
的下标丢弃。这是因为如果nums[x] < nums[i]
且x < i
,在当前窗口和之后的窗口里nums[x]
都不可能成为最大值,因为nums[i]
一定是更好的选择 - 将当前下标添加至队列,再将下标对应的值添加值最终的
result
数组
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || nums.length < k) {
return new int[] {};
}
int len = nums.length;
int[] result = new int[len - k + 1];
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < len; i++) {
// remove first element
if (!list.isEmpty() && list.peek() < i - k + 1) {
list.poll();
}
// remove unnecessary indices
while (!list.isEmpty() && nums[i] >= nums[list.peekLast()]) {
list.pollLast();
}
// new result
list.offer(i);
if (i - k + 1 >= 0) {
result[i - k + 1] = nums[list.peek()];
}
}
return result;
}
}
240. Search a 2D Matrix II
https://leetcode.com/problems/search-a-2d-matrix-ii/
直接从第一行最右侧开始搜索,如果当前数比target
小,则向左一列;当前数比target
大,则向下一行。不断重复,若直到列或者行超出边界还没有找到target
,就说明target
不在数组中。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int row = 0, col = matrix[0].length - 1;
// start from the upper right
while (col >= 0 && row < matrix.length) {
if (matrix[row][col] == target) {
return true;
} else if (matrix[row][col] < target) {
row++;
} else {
col--;
}
}
return false;
}
}
241. Different Ways to Add Parentheses
https://leetcode.com/problems/different-ways-to-add-parentheses/
核心是使用分治,遍历输入字符串,当前字符为界将字符串分为左、右两个子字符串,根据具体的运算符算出临时结果。为了节省时间,可以使用map存储算过的结果。
class Solution {
// <current input string, all possible results>
private Map<String, List<Integer>> map = new HashMap<>();
public List<Integer> diffWaysToCompute(String input) {
// check history
List<Integer> result = map.get(input);
if (result != null) {
return result;
}
result = new ArrayList<>();
// only one digit left, put current result to map
if (isDigit(input)) {
result.add(Integer.parseInt(input));
map.put(input, result);
return result;
}
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '+' || c == '-' || c == '*') {
List<Integer> left = diffWaysToCompute(input.substring(0,i));
List<Integer> right = diffWaysToCompute(input.substring(i+1));
for (Integer il : left) {
for (Integer ir : right) {
if (c == '+') {
result.add(il + ir);
} else if (c == '-') {
result.add(il - ir);
} else if (c == '*') {
result.add(il * ir);
}
}
}
}
}
map.put(input, result);
return result;
}
private boolean isDigit(String s) {
for (Character c : s.toCharArray()) {
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
}
242. Valid Anagram
https://leetcode.com/problems/valid-anagram/
人造一个map,存储s中每个字符出现的次数,再验证t中的每个字符出现的次数能否与之对应。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] map = new int[256];
// how many times each characters occur in s
for (char c : s.toCharArray()) {
map[c]++;
}
// check whether match occurrence in t
for (char c : t.toCharArray()) {
map[c]--;
if (map[c] < 0) {
return false;
}
}
return true;
}
}
243. Shortest Word Distance
https://leetcode.com/problems/shortest-word-distance/
遍历words
中的每一个单词,如果当前的单词等于word1
或者word2
,则对应修改两个单词的下标index1
和index2
;当下标都大于0时,更新当前的下标间距最小值。
class Solution {
public int shortestDistance(String[] words, String word1, String word2) {
int result = Integer.MAX_VALUE;
int index1 = -1, index2 = -1;
for (int i = 0; i < words.length; i++) {
// always update indices
if (words[i].equals(word1)) {
index1 = i;
} else if (words[i].equals(word2)) {
index2 = i;
} else {
continue;
}
if (index1 >= 0 && index2 >= 0) {
result = Math.min(result, Math.abs(index1 - index2));
}
}
return result;
}
}
244. Shortest Word Distance II
https://leetcode.com/problems/shortest-word-distance-ii/
由于计算最短间距的函数可能被调用多次,因此直接的想法是将数组中每一个单词出现的下标都收录起来,整理成一个list。检查两个单词时,使用两个指针p1
和p2
分别遍历下标list,找到尽可能小的间距。
class WordDistance {
// for each word, store its occurrence index
HashMap<String, List<Integer>> map;
public WordDistance(String[] words) {
map = new HashMap();
for (int i = 0; i < words.length; i++) {
String currentWord = words[i];
if (!map.containsKey(currentWord)) {
map.put(currentWord, new ArrayList<>());
}
map.get(currentWord).add(i);
}
}
public int shortest(String word1, String word2) {
List<Integer> list1 = map.get(word1), list2 = map.get(word2);
int result = Integer.MAX_VALUE;
int p1 = 0, p2 = 0;
while (p1 < list1.size() && p2 < list2.size()) {
int index1 = list1.get(p1), index2 = list2.get(p2);
// make two pointers as close as possible
// shift the smaller index to its maximum value
if (index1 > index2) {
p2++;
} else {
p1++;
}
result = Math.min(result, Math.abs(index1 - index2));
}
return result;
}
}
245. Shortest Word Distance III
https://leetcode.com/problems/shortest-word-distance-iii/
遍历循环。
class Solution {
public int shortestWordDistance(String[] words, String word1, String word2) {
int result = Integer.MAX_VALUE;
int index1 = -1, index2 = -1;
boolean isEqual = word1.equals(word2);
for (int i = 0; i < words.length; i++) {
if (words[i].equals(word1)) {
index1 = i;
} else if (words[i].equals(word2)) {
index2 = i;
} else {
continue;
}
if (index1 >= 0 && index2 >= 0) {
result = Math.min(result, Math.abs(index1 - index2));
}
// if word1 and word2 are equal, their indices should be set equally before next loop
if (isEqual) {
index2 = index1;
}
}
return result;
}
}
246. Strobogrammatic Number
https://leetcode.com/problems/strobogrammatic-number/
使用头尾指针标记num
,不断移动指针,完整遍历num
,判断每次头尾指针组成的数是否组成了一对strobogrammatic number pairs。
class Solution {
public boolean isStrobogrammatic(String num) {
char[] check = new char[10];
// strobogrammatic number
check[1] = '1';
check[6] = '9';
check[0] = '0';
check[9] = '6';
check[8] = '8';
int low = 0, high = num.length() - 1;
while (low <= high) {
// must form strobogrammatic number pairs
if (num.charAt(low) != check[num.charAt(high) - '0']) {
return false;
}
low++;
high--;
}
return true;
}
}
247. Strobogrammatic Number II
https://leetcode.com/problems/strobogrammatic-number-ii/
调用递归,让当前字符数量不断减少2,以此能在首尾各自添加一个数字。添加的数字可以为11, 69, 96, 88;如果不是开头和结尾,还可以添加00。
class Solution {
public List<String> findStrobogrammatic(int n) {
return helper(n, n);
}
private List<String> helper(int current, int n) {
if (current == 0) {
return new ArrayList<String>(Arrays.asList(""));
}
if (current == 1) {
return new ArrayList<String>(Arrays.asList("0", "1", "8"));
}
// reduce current digits count by 2 and add two new digits to head and tail
List<String> list = helper(current - 2, n);
List<String> result = new ArrayList<String>();
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (current != n) {
// 00 cannot be head and tail
result.add("0" + s + "0");
}
result.add("1" + s + "1");
result.add("6" + s + "9");
result.add("9" + s + "6");
result.add("8" + s + "8");
}
return result;
}
}
248. Strobogrammatic Number III
https://leetcode.com/problems/strobogrammatic-number-iii/
使用DFS,提前列举好所有的strobogrammatic number pairs,之后使用头尾指针判断是否符合当前指针所指的两个数是否符合strobogrammatic规则。当头尾指针重合后,需要额外判断当前数是否超出了给定[low, high]
的范围,如果在范围内才算做一个合法的数字。
class Solution {
public int strobogrammaticInRange(String low, String high) {
int n = low.length();
while (n <= high.length()) {
dfs(0, n - 1, new char[n], low, high);
n++;
}
return result;
}
private char[][] converts = new char[][]{new char[]{'0' ,'0'}, new char[]{'1' ,'1'},
new char[]{'6' ,'9'}, new char[]{'8' ,'8'}, new char[]{'9' ,'6'}};
private int result = 0;
private void dfs(int i, int j, char[] num, String low, String high) {
if (i > j) {
if (!isSmallerThan(num, low) && !isLargerThan(num, high)) {
result++;
}
return;
}
// try two digits
for (char[] convert : converts) {
num[i] = convert[0];
num[j] = convert[1];
if (i == 0 && num.length > 1 && convert[0] == '0') {
continue;
}
if (i == j && convert[0] != convert[1]) {
continue;
}
dfs(i + 1, j - 1, num, low, high);
}
}
private boolean isLargerThan(char[] num, String high) {
if (num.length < high.length()) {
return false;
}
int i = 0;
while (i < high.length()) {
if (num[i] == high.charAt(i)) {
i++;
continue;
}
return num[i] > high.charAt(i);
}
return false;
}
private boolean isSmallerThan(char[] num, String low) {
if (num.length > low.length()) {
return false;
}
int i = 0;
while (i < low.length()) {
if (num[i] == low.charAt(i)) {
i++;
continue;
}
return num[i] < low.charAt(i);
}
return false;
}
}
249. Group Shifted Strings
https://leetcode.com/problems/group-shifted-strings/
对每一个输入字符串进行编辑,其他字母和首字母进行比较,如果其他字母的ASCII码比首字母小,需要补26作为循环,由此生成一个对应当前结构的key
。再使用一个map,存储每个key
对应的所有字符串,最后直接利用map.values()
自动整理生成归类后的二维list。
class Solution {
public List<List<String>> groupStrings(String[] strings) {
Map<String, List<String> > map = new HashMap<>();
for (String str : strings) {
String key = getKey(str);
if (map.containsKey(key)) {
map.get(key).add(str);
} else {
List<String> temp = new ArrayList<>();
temp.add(str);
map.put(key, temp); // add new key
}
}
// generate 2D list
return new ArrayList<>(map.values());
}
private String getKey(String str) {
StringBuilder key = new StringBuilder();
int diff = str.charAt(0) - 'a';
// if other character's ascii code is smaller than first character, complement 26 for rotation
for (int i = 0; i < str.length(); i++) {
int value = str.charAt(i) - 'a' < diff ? str.charAt(i) - 'a' - diff + 26
: str.charAt(i) - 'a' - diff;
key.append((char)value);
}
return key.toString();
}
}
250. Count Univalue Subtrees
https://leetcode.com/problems/count-univalue-subtrees/
使用后序遍历,在左右子树都是univalue树的前提下,判断当前节点和左、右节点是否一致。如果左、右任一节点存在且与当前节点不一样,就说明当前节点为根的树不是univalue;反之,univalue树的数量加1。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int countUnivalSubtrees(TreeNode root) {
int[] result = new int[1]; // modify global value to get final result
helper(root, result);
return result[0];
}
private boolean helper(TreeNode node, int[] result) {
if (node == null) {
return true;
}
boolean left = helper(node.left, result);
boolean right = helper(node.right, result);
// both left and right subtrees are univalue trees, check subtrees
if (left && right) {
// current node's val must be same with left child and right child
if ((node.left != null && node.val != node.left.val)
|| (node.right != null && node.val != node.right.val)) {
return false;
}
result[0]++;
return true;
}
// one subtree is not univalue tree, no more further check
return false;
}
}
251. Flatten 2D Vector
https://leetcode.com/problems/flatten-2d-vector/
使用一个二维数组temp
,浅拷贝传入的数组。
检查是否存在下一个元素:
- 当前行没遍历到结尾,肯定还有下一个元素
- 当前行遍历到结尾,尝试跳转到下一行,检查行数是否越界。
获取下一个元素,移动相应二维数组下标即可。
class Vector2D {
int[][] temp; // shallow copy input array
int row = 0, col = 0;
public Vector2D(int[][] v) {
temp = v;
}
public int next() {
// if there is next element, fetch it
return hasNext() ? temp[row][col++] : -1;
}
public boolean hasNext() {
// one row's end, jump to next row
while (row < temp.length && col == temp[row].length) {
row++;
col = 0;
}
// whether go through all rows
return row < temp.length;
}
}
252. Meeting Rooms
https://leetcode.com/problems/meeting-rooms/
对每个子数组按照起始时间大小进行排序,之后检查是否有相邻会议的结束时间和起始时间重叠。
class Solution {
public boolean canAttendMeetings(int[][] intervals) {
if (intervals.length == 0 || intervals[0].length == 0) {
return true;
}
Arrays.sort(intervals, (int[] a, int[] b) -> {
return a[0] - b[0];
});
for (int i = 1; i < intervals.length; i++) {
// overlap with previous interval
if (intervals[i][0] < intervals[i - 1][1]) {
return false;
}
}
return true;
}
}
253. Meeting Rooms II
https://leetcode.com/problems/meeting-rooms-ii/
将所有会议对开始时间和结束时间提取出来,分别整理成独立数组并排序。假设每开始一场会议都要占用一个房间,然而如果之前的一场会议已经结束,说明之前的这个房间可以被释放并挪用于当前的会议。因此,使用一个变量endIdx
跟踪当前会议的结束情况,如果当前的会议的起始时间不小于之前最早开始的一场会议的结束时间,就不需要额外开一个新会议室。
class Solution {
public int minMeetingRooms(int[][] intervals) {
if (intervals == null || intervals.length == 0 || intervals[0].length == 0) {
return 0;
}
int len = intervals.length;
// fetch all start and end time, then sort
int[] starts = new int[len];
int[] ends = new int[len];
for (int i = 0; i < len; i++) {
starts[i] = intervals[i][0];
ends[i] = intervals[i][1];
}
Arrays.sort(starts);
Arrays.sort(ends);
// use endIdx to track current ending meeting
int result = 0, endIdx = 0;
for (int i = 0; i < len; i++) {
// assume each meeting needs a room first
result++;
// current start time is after one ending meeting, no need to arrange one room
if (starts[i] >= ends[endIdx]) {
result--;
endIdx++;
}
}
return result;
}
}
254. Factor Combinations
https://leetcode.com/problems/factor-combinations/
本质上这题仍然是传统的backtrack问题。然而,技巧在于此题在下一轮dfs前进行加速,即:每一轮向临时数组添加时,i
和n/i
成对搭配,这样一来既能马上凑出一对combination,也可以节省时间,使得i
的遍历只需要到达sqrt(i)
即可。
class Solution {
public List<List<Integer>> getFactors(int n) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
backtrack(result, new ArrayList<>(), n, 2);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> temp, int n, int start) {
// when n < 2, we must ensure we have more than 1 combination to get one result
if (n < 2) {
if (temp.size() > 1) {
result.add(new ArrayList<>(temp));
}
return;
}
for (int i = start; i * i <= n; i++) {
if (n % i == 0) {
// add both i and n/i
temp.add(i);
temp.add(n / i);
result.add(new ArrayList<>(temp));
temp.remove(temp.size() - 1); // remove n/i
backtrack(result, temp, n / i, i);
temp.remove(temp.size() - 1); // remove i
}
}
}
}
255. Verify Preorder Sequence in Binary Search Tree
https://leetcode.com/problems/verify-preorder-sequence-in-binary-search-tree/
模拟一个栈。假如遇到了比上一个栈顶值小的数,说明我们还在所有栈元素的左子树上,因此只需要把这个数也加入栈。然而,在此之前需要把所有更小的祖先值给踢出,因此我们遍历完之后需要跳转到右支了。设立弹出值为下界,之后遍历右支意味着不能遇到一个比下界更小的数,否则就是不合法的。
class Solution {
public boolean verifyPreorder(int[] preorder) {
// lower bound
int low = Integer.MIN_VALUE, i = -1;
for (int p : preorder) {
// we cannot meet an element smaller than lower bound
if (p < low) {
return false;
}
// assume current p is root node, we need to find the furthest node preorder[i] < p
// this indicates that we are still on the left subtree
while (i >= 0 && p > preorder[i]) {
low = preorder[i];
i--;
}
// to right subtree
i++;
preorder[i] = p;
}
return true;
}
}
256. Paint House
https://leetcode.com/problems/paint-house/
从第二行起直接修改输入的cost
数组,cost[i][j]
表示当前颜色成本加上之前使用另两种颜色刷房子的成本的最小值。
class Solution {
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) {
return 0;
}
// current house's cost and two other houses' previous total cost
for (int i = 1; i < costs.length; i++) {
costs[i][0] += Math.min(costs[i-1][1], costs[i-1][2]);
costs[i][1] += Math.min(costs[i-1][0], costs[i-1][2]);
costs[i][2] += Math.min(costs[i-1][0], costs[i-1][1]);
}
int n = costs.length - 1;
return Math.min(Math.min(costs[n][0], costs[n][1]), costs[n][2]);
}
}
257. Binary Tree Paths
https://leetcode.com/problems/binary-tree-paths/
使用前序遍历到达叶节点,之后回溯遍历其他路径。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<Integer> list = new ArrayList<>();
List<String> paths = new ArrayList<>();
helper(root, list, paths);
return paths;
}
private void helper(TreeNode root, List<Integer> list, List<String> paths) {
if (root == null) {
return;
}
// leaf node, add previous string and current value
if (root.left == null && root.right == null) {
StringBuilder sb = new StringBuilder();
for (int element : list) {
sb.append(element);
sb.append("->");
}
sb.append(root.val);
paths.add(sb.toString());
return;
}
// backtrack
list.add(root.val);
helper(root.left, list, paths);
helper(root.right, list, paths);
list.remove(list.size() - 1);
}
}
258. Add Digits
https://leetcode.com/problems/add-digits/
直接归纳总结公式。
class Solution {
public int addDigits(int num) {
return 1 + (num - 1) % 9;
}
}
259. 3Sum Smaller
https://leetcode.com/problems/3sum-smaller/
先排序,再使用三指针法,注意计数时,固定first
和second
,计算second
和third
之间的差值。
class Solution {
public int threeSumSmaller(int[] nums, int target) {
int result = 0;
Arrays.sort(nums);
int len = nums.length;
for (int first = 0; first < len - 2; first++) {
if (nums[first] * 3 >= target) {
break;
}
int second = first + 1, third = len - 1;
// fix first, range count in [second + 1, third]
while (second < third) {
if (nums[first] + nums[second] + nums[third] < target) {
result += third - second;
second++;
} else {
third--;
}
}
}
return result;
}
}
260. Single Number III
https://leetcode.com/problems/single-number-iii/
第一次遍历,与Single Number相同思想,找出两个single number的异或值。由于这两个数不同,因此异或的结果一定有一位是1(Set Bit),任意保存一位即可。(例如,通过和负数取与,保留最后一位)
第二次遍历,将所有数分类,一类与异或数取与为0(不包含Set Bit),另一类为1(包含Set Bit),由于其他数一定能两两配对,因此最后分类后的异或结果就是两个single number。
class Solution {
public int[] singleNumber(int[] nums) {
if (nums == null || nums.length < 2) {
return new int[] {};
}
// get xor of two single numbers, keep only the last set bit
int xor = 0;
for (int num : nums) {
xor ^= num;
}
xor &= -xor;
// use set bit to classify, each group's result is a single number
int[] result = {0, 0};
for (int num : nums) {
if ((num & xor) == 0) {
result[0] ^= num;
} else {
result[1] ^= num;
}
}
return result;
}
}
261. Graph Valid Tree
https://leetcode.com/problems/graph-valid-tree/
假定每个点是一个孤岛,使用union-find的思想,查找出每条边延伸下去最末端的孤岛是什么。如果末端相等,说明重合,即树中存在环,不合法;否则,更新当前的岛连接情况。
class Solution {
public boolean validTree(int n, int[][] edges) {
if (edges.length != n - 1) {
return false;
}
// n nodes
int[] nums = new int[n];
Arrays.fill(nums, -1);
for (int i = 0; i < edges.length; i++) {
int x = find(nums, edges[i][0]);
int y = find(nums, edges[i][1]);
// furthest nodes are the same, so there must be a cycle
if (x == y) {
return false;
}
nums[x] = y;
}
return true;
}
// find the furthest node
private int find(int[] nums, int i) {
if (nums[i] == -1) {
return i;
}
return find(nums, nums[i]);
}
}
262. Trips and Users
https://leetcode.com/problems/trips-and-users/
拼接两张表,限制时间,用户账号状态作为筛选条件,选出所有取消的订单并除以总订单数。使用ROUND()
函数保留两位小数。
SELECT request_at as Day, Round(Sum(If(status!="completed",1,0)) / count(status), 2) as "Cancellation Rate"
FROM trips
WHERE request_at BETWEEN "2013-10-01" AND "2013-10-03"
AND client_id NOT IN (SELECT users_id FROM Users WHERE banned = "yes")
AND driver_id NOT IN (SELECT users_id FROM Users WHERE banned = "yes")
GROUP BY request_at
263. Ugly Number
https://leetcode.com/problems/ugly-number/
让num
循环除2-5之间的数,判断最后结果是否为1。
class Solution {
public boolean isUgly(int num) {
if (num <= 0) {
return false;
}
// keep divide by 2, then 3, then 5
for (int i = 2; i < 6 && num > 0; i++) {
while (num % i == 0) {
num /= i;
}
}
return num == 1;
}
}
264. Ugly Number II
https://leetcode.com/problems/ugly-number-ii/
每个数实际上由因数和序数相乘得到,因此使用动态规划,直接按大小添加当前的ugly number,再根据其因数更新对应的序数即可。
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int factor2 = 2, factor3 = 3, factor5 = 5;
int index2 = 0, index3 = 0, index5 = 0;
for (int i = 1; i < n; i++) {
int min = Math.min(Math.min(factor2, factor3), factor5);
// current ugly number
dp[i] = min;
// update last ugly number using 2/3/5 as factor, then increment corresponding index
if (factor2 == min) {
index2++;
factor2 = 2 * dp[index2];
}
if (factor3 == min) {
index3++;
factor3 = 3 * dp[index3];
}
if (factor5 == min) {
index5++;
factor5 = 5*dp[index5];
}
}
return dp[n - 1];
}
}
265. Paint House II
https://leetcode.com/problems/paint-house-ii/
正常情况下,对于每一间房子的选择,只需要选出成本最低的即可。但由于相邻两间房子不能使用相同颜色,因此我们需要使用两个变量分别保存全局的第一小和第二小成本,并且额外使用一个变量index1
记录上一间房最后使用的颜色。在当前的成本到总成本时需要观察,根据上一轮房子选择的情况,选择要累加的变量(min1st
或者min2nd
)。
class Solution {
public int minCostII(int[][] costs) {
if (costs.length == 0) {
return 0;
}
// 1st-min cost, 2nd-min cost, maintain the index of min1st color cost of "last house"
int min1st = 0, min2nd = 0, index1 = -1;
for (int i = 0; i < costs.length; i++) {
// temp value
int m1 = Integer.MAX_VALUE, m2 = Integer.MAX_VALUE, idx = -1;
for (int j = 0; j < costs[0].length; j++) {
// adjacent houses cannot have same color
int cost = costs[i][j] + (j != index1 ? min1st : min2nd);
if (cost < m1) {
// cost < m1 < m2, update 1st-min, 2nd-min cost and index
m2 = m1;
m1 = cost;
idx = j;
} else if (cost < m2) {
// m1 < cost < m2, update 2nd-min cost
m2 = cost;
}
}
min1st = m1;
min2nd = m2;
index1 = idx;
}
return min1st;
}
}
266. Palindrome Permutation
https://leetcode.com/problems/palindrome-permutation/
使用Set, 不断添加当前字符到Set中。如果已有这个字符,说明这两个相同字符可以组成一个pair而抵消;反之,作为一个新字符加入。遍历完之后,根据奇偶性讨论,Set中至多只能有1个字符。
class Solution {
public boolean canPermutePalindrome(String s) {
if (s == null || s.length() == 0) {
return true;
}
Set<Character> set = new HashSet<>();
for (char c : s.toCharArray()) {
// one pair, remove
if (set.contains(c)) {
set.remove(c);
} else {
set.add(c);
}
}
// odd or even
return set.size() <= 1;
}
}
267. Palindrome Permutation II
https://leetcode.com/problems/palindrome-permutation-ii/
先统计每个字符出现的次数,排除掉肯定不能组成palindromic permutation的情况。之后使用回溯算法,只要当前字符次数还有剩余,就将2个字符分配到当前字符串的首尾,并继续调用下一层构造函数。
class Solution {
public List<String> generatePalindromes(String s) {
// each character's count
int[] count = new int[256];
for (char c : s.toCharArray()) {
count[c]++;
}
// odd character count
int oddChars = 0;
Character odd = null;
for (int i = 0; i < count.length; i++) {
if (count[i] % 2 == 1) {
oddChars++;
odd = (char)(i);
}
}
// no palindromic permutations
if (oddChars > 1) {
return new ArrayList<>();
}
char[] local = new char[s.length()];
// one odd char, must be in the middle of the string
if (oddChars == 1) {
local[local.length / 2] = odd;
count[odd]--;
}
List<String> result = new ArrayList<>();
backtrack(count, 0, local, result);
return result;
}
private void backtrack(int[] count, int index, char[] local, List<String> result) {
// base case
if (index == local.length / 2) {
result.add(new String(local));
return;
}
// try all even number chars
for (int i = 0; i < count.length; i++) {
if (count[i] > 0) {
// allocate (char)i to head and tail of current string
local[index] = local[local.length - 1 - index] = (char)(i);
count[i] -= 2;
backtrack(count, index + 1, local, result);
count[i] += 2;
}
}
}
}
268. Missing Number
https://leetcode.com/problems/missing-number/
第一种方法,使用异或。每个数的下标与本身本应是一致的,因此二者异或结果是0。只有缺失的这个数,其本身将无法被异或填补。
例如以[0, 1, 2, 3, 4, 5, 6, 7, 9, 10]
为例:
下标i:
0 1 2 3 4 5 6 7 8 9 ...
数值nums[i]:
0 1 2 3 4 5 6 7 9 10 ...
在最后补充异或nums.length
,恰好能消除掉最后一个nums[i]
,这样剩下的就是缺失的数字了。
class Solution {
public int missingNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int result = 0;
for (int i = 0; i < nums.length; i++) {
result = result ^ i ^ nums[i];
}
// nums.length is exactly the last nums[i]
return result ^ nums.length;
}
}
第二种方法,因为缺失了一个数,导致数本身的和会大于等于下标之和。因此,令初始结果为最大数,累加所有的下标之和,减去所有数之和,最终会得到缺失的数。这种方法只能在不造成溢出的情况下使用。
class Solution {
public int missingNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int sum = nums.length;
for (int i = 0; i < nums.length; i++) {
sum += i - nums[i];
}
return sum;
}
}
269. Alien Dictionary
https://leetcode.com/problems/alien-dictionary/
构建二维邻接矩阵,按照相邻单词相同下标的字母顺序构建边,输入到邻接矩阵中;再使用DFS遍历邻接矩阵得到遍历顺序。
class Solution {
private final int N = 26;
public String alienOrder(String[] words) {
// words[i+1:] order is unclear
for (int i = 0; i < words.length - 1; i++) {
if (words[i].length() > words[i+1].length() && words[i].startsWith(words[i+1])) {
return "";
}
}
// adjacency matrix
boolean[][] adj = new boolean[N][N];
int[] visited = new int[N];
buildGraph(words, adj, visited);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
if (visited[i] == 0 && !dfs(adj, visited, sb, i)) {
return "";
}
}
return sb.reverse().toString();
}
public void buildGraph(String[] words, boolean[][] adj, int[] visited) {
// -1 = not existed
Arrays.fill(visited, -1);
for (int i = 0; i < words.length; i++) {
for (char c : words[i].toCharArray()) {
visited[c - 'a'] = 0;
}
if (i > 0) {
// for characters of same indices, form an edge in matrix
String w1 = words[i - 1], w2 = words[i];
int len = Math.min(w1.length(), w2.length());
for (int j = 0; j < len; j++) {
char c1 = w1.charAt(j), c2 = w2.charAt(j);
if (c1 != c2) {
adj[c1 - 'a'][c2 - 'a'] = true;
break;
}
}
}
}
}
public boolean dfs(boolean[][] adj, int[] visited, StringBuilder sb, int i) {
// 1 = visiting
visited[i] = 1;
for (int j = 0; j < N; j++) {
// connected
if (adj[i][j]) {
// 1 => 1, cycle
if (visited[j] == 1) {
return false;
}
// 0 = unvisited, and must need to find another unvisited node
if (visited[j] == 0 && !dfs(adj, visited, sb, j)) {
return false;
}
}
}
// 2 = visited
visited[i] = 2;
sb.append((char) (i + 'a'));
return true;
}
}
270. Closest Binary Search Tree Value
https://leetcode.com/problems/closest-binary-search-tree-value/
存储一个最接近target
的变量,再根据节点值的大小调整遍历的方向。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int closestValue(TreeNode root, double target) {
int result = root.val;
while (root != null) {
if (Math.abs(result - target) > Math.abs(root.val - target)) {
result = root.val;
}
root = (root.val > target) ? root.left : root.right;
}
return result;
}
}
271. Encode and Decode Strings
https://leetcode.com/problems/encode-and-decode-strings/
编码时,在输入的每一个字符串前加入当前字符串的长度,并用*
号间隔。解码时根据长度截取*
之后的子字符串。
public class Codec {
// Encodes a list of strings to a single string.
public String encode(List<String> strs) {
StringBuilder sb = new StringBuilder();
for (String s : strs) {
// s.length() + '*' + s
sb.append(s.length()).append('*');
sb.append(s);
}
return sb.toString();
}
// Decodes a single string to a list of strings.
public List<String> decode(String s) {
int idx = 0;
List<String> result = new ArrayList<>();
while (idx < s.length()) {
// first index of *
int pos = s.indexOf('*', idx);
int size = Integer.valueOf(s.substring(idx, pos));
// next index of *
idx = pos + size + 1;
result.add(s.substring(pos + 1, idx));
}
return result;
}
}
272. Closest Binary Search Tree Value II
https://leetcode.com/problems/closest-binary-search-tree-value-ii/
使用一个LinkedList作为队列,使用中序遍历(保证遍历的元素值从小到大),当队列中的元素数量达到k
时检查当前元素是否更加接近target
,没有就直接返回,有的话将首个元素踢掉。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> closestKValues(TreeNode root, double target, int k) {
LinkedList<Integer> result = new LinkedList<>();
helper(result, root, target, k);
return result;
}
private void helper(LinkedList<Integer> result, TreeNode node, double target, int k) {
if (node == null) {
return;
}
// in-order traverse
helper(result, node.left, target, k);
// if already k elements in result, remove first element
// since we use in-order element, we can ensure that first element is the smallest and has max difference with current
if (result.size() == k) {
if (Math.abs(target - node.val) < Math.abs(target - result.peekFirst())) {
result.removeFirst();
} else {
return;
}
}
result.add(node.val);
helper(result, node.right, target, k);
}
}
273. Integer to English Words
https://leetcode.com/problems/integer-to-english-words/
默写出0-20对应的英文单词,以及0-100中所有的整十数的英文单词。由于限定了输入的范围不会超过2147483647,因此需要考虑6组范围:
- 0 - 19,直接调用20以内的单词
- 20 - 99,十位数调用整十数,个位数调用0-20
- 100 - 999,分割后2位数和其余位数,用”Hundred”衔接
- 1,000 - 999,999,分割后3位数和其余位数,用”Thousand”衔接
- 1,000,000 - 999,999,999,分割后6位数和其余位数,用”Million”衔接
- 1,000,000,000,分割后9位数和其余位数,用”Billion”衔接
核心思路就是不断将大数变小,只直接处理小于100的数字,更大的数字按照相应单位进行拆分。
class Solution {
private final String[] belowTwenty = new String[] {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
private final String[] tens = new String[] {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
public String numberToWords(int num) {
return (num == 0) ? "Zero" : helper(num);
}
private String helper(int num) {
String result = new String();
if (num < 20) {
result = belowTwenty[num];
} else if (num < 100) {
result = tens[num / 10] + " " + belowTwenty[num % 10];
} else if (num < 1000) {
result = helper(num / 100) + " Hundred " + helper(num % 100);
} else if (num < 1000000) {
result = helper(num / 1000) + " Thousand " + helper(num % 1000);
} else if (num < 1000000000) {
result = helper(num/1000000) + " Million " + helper(num % 1000000);
} else {
result = helper(num/1000000000) + " Billion " + helper(num % 1000000000);
}
return result.trim();
}
}
274. H-Index
https://leetcode.com/problems/h-index/
构造一个长度为总论文篇数 + 1的桶,每个桶的索引是论文的引用次数i
,桶内存放着引用次数为i
的论文篇数。
遍历所有论文,如果某篇论文引用次数超过总论文篇数,累加到最后一个桶;否则,按照次数索引累加相应的桶。完成后,从后向前遍历每个桶,并且累加桶里的值。如果累加值大于等于当前桶的索引,说明发现了h-index。
class Solution {
public int hIndex(int[] citations) {
if (citations == null || citations.length == 0) {
return 0;
}
// total paper count
int len = citations.length;
// bucket[i] means count of paper with citation i
int[] buckets = new int[len + 1];
for (int c : citations) {
// if one paper's citation exceeds total paper count, sum up to last bucket
if (c >= len) {
buckets[len]++;
} else {
buckets[c]++;
}
}
// search backwards to find the largest h-index
int count = 0;
for (int i = len; i >= 0; i--) {
count += buckets[i];
if (count >= i) {
return i;
}
}
return 0;
}
}
275. H-Index II
https://leetcode.com/problems/h-index-ii/
因为citations
已经排好序了,所以直接使用二分搜索,如果mid
索引的引用次数大于篇数,说明引用次数设定的过大,因此right
取mid
左侧;反之left
取mid
右侧。
class Solution {
public int hIndex(int[] citations) {
if (citations == null || citations.length == 0) {
return 0;
}
int len = citations.length;
int left = 0, right = len - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
// len - mid: have at least h citations
if (citations[mid] >= (len - mid)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return len - left;
}
}
276. Paint Fence
https://leetcode.com/problems/paint-fence/
考虑第n
个post
的染色数量,有两种情况:
- 与第
n-1
个不一致。任选和第n-1
个post
不同的颜色即可 - 与第
n-1
个一致,那么第n-1
个必须和第n-2
个post
颜色不一致,问题转化为当前post
与第n-2
个不一致的问题
因此综合而言,如果使用动态规划,dp[i]
依赖于dp[i-1]
和dp[i-2]
的值。不管是哪一种情况,都有k-1
种其他颜色可选,因此总和就是(dp[i-1] + dp[i-2]) * (k-1)
。
class Solution {
public int numWays(int n, int k) {
if (n == 0) {
// no posts
return 0;
} else if (n == 1) {
// one post and k colors
return k;
} else if (n == 2) {
// two posts, each has k colors
return k * k;
}
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = k;
dp[2] = k * k;
for (int i = 3; i <= n; i++) {
dp[i] = (dp[i-1] + dp[i-2]) * (k-1);
}
return dp[n];
}
}
277. Find the Celebrity
https://leetcode.com/problems/find-the-celebrity/
使用两次循环,第一次循环找出名人的候选,即如果candidate
认识i
,则把i
换成新的candidate
。第二次循环,再检查这个选出来的candidate
是否认识号数在他之前的人。注意,第二次循环对于candidate
是0的情况并不适用,因此需要额外讨论candidate
为0的情况。
/* The knows API is defined in the parent class Relation.
boolean knows(int a, int b); */
public class Solution extends Relation {
public int findCelebrity(int n) {
// only two people and they don't know about each other
if (n == 2 && !knows(0, 1) && !knows(1, 0)) {
return -1;
}
int candidate = 0;
// find potential celebrity candidate
for (int i = 1; i < n; i++) {
if (knows(candidate, i)) {
candidate = i;
}
}
// additionally judge 0-th person
if (candidate == 0) {
for (int i = 1; i < n; i++) {
if (!knows(i, candidate)) {
return -1;
}
}
return 0;
}
// if this celebrity candidate knows someone,
// or somebody doesn't know him, exclude him
for (int i = 0; i < candidate; i++) {
if (knows(candidate, i) || !knows(i, candidate)) {
return -1;
}
}
return candidate;
}
}
278. First Bad Version
https://leetcode.com/problems/first-bad-version/
问题需要找出最小的bad version,正好对应左边界场景下的二分查找问题。如果某个版本号不是bad version,就向上搜索版本号;反之向下搜索,看是否还有更小的版本号也是bad version。
/* The isBadVersion API is defined in the parent class VersionControl.
boolean isBadVersion(int version); */
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int left = 1, right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (isBadVersion(mid) == false) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
279. Perfect Squares
https://leetcode.com/problems/perfect-squares/
使用动态规划思想,记录每个数中最少可用几个完全平方数表示。从1开始到sqrt(i)
,尽可能地填入已知的最大完全平方数。
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
for (int i = 0; i * i <= n; i++) {
dp[i * i] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; i + j * j <= n; j++) {
dp[i + j * j] = Math.min(dp[i + j * j], dp[i] + 1);
}
}
return dp[n];
}
}
280. Wiggle Sort
https://leetcode.com/problems/wiggle-sort/
第一个数不动,之后的数按照下标奇偶,如果奇数下标的数比前一个数小,或是偶数下标的数比前一个数大,则交换当前数和前一个数。
class Solution {
public void wiggleSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
if (i % 2 == 1 && nums[i-1] > nums[i]) {
// odd index must be greater than previous one
swap(nums, i-1, i);
} else if (i > 0 && i % 2 == 0 && nums[i-1] < nums[i]) {
// even index (except 0) must be samller than previous one
swap(nums, i-1, i);
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
281. Zigzag Iterator
https://leetcode.com/problems/zigzag-iterator/
将两个List的信息复制到全局变量中,同时引入两个指针变量用于标注当前遍历List的进度。必须时刻确保两个指针轮流前进,并且指针不能超出对应List的长度。
public class ZigzagIterator {
int size1 = 0, size2 = 0;
int ptr1 = 0, ptr2 = 0;
List<Integer> v1, v2;
public ZigzagIterator(List<Integer> v1, List<Integer> v2) {
size1 = v1.size();
size2 = v2.size();
this.v1 = v1;
this.v2 = v2;
}
public int next() {
int result = -1;
// 'zigzagly' moving ptr1 and ptr2 forward
if (ptr1 < ptr2 && ptr1 < size1) {
result = v1.get(ptr1);
ptr1++;
return result;
}
if (ptr2 < ptr1 && ptr2 < size2) {
result = v2.get(ptr2);
ptr2++;
return result;
}
// one of the pointers reach the end, only moving other pointers
if (ptr1 < size1) {
result = v1.get(ptr1);
ptr1++;
return result;
}
if (ptr2 < size2) {
result = v2.get(ptr2);
ptr2++;
return result;
}
return result;
}
public boolean hasNext() {
// v1 unfinished, or v2 unfinished
return (ptr1 < size1 || ptr2 < size2);
}
}
282. Expression Add Operators
https://leetcode.com/problems/expression-add-operators/
使用dfs,抄录num
的每一位到exp
中,再分别用三种符号dfs尝试是否满足条件。注意处理首位为0(跳过)以及整数溢出(使用long)的情况。
class Solution {
private List<String> result;
private char[] num;
private char[] exp;
public List<String> addOperators(String num, int target) {
this.num = num.toCharArray();
this.result = new ArrayList<>();
this.exp = new char[num.length() * 2];
dfs(0, 0, 0, 0, target);
return result;
}
private void dfs(int pos, int len, long prev, long curr, int target) {
// finish whole string
if (pos == num.length) {
if (curr == target) {
// add current expression to result
result.add(new String(exp, 0, len));
}
return;
}
// record current position of num and expression
int s = pos, l = len;
if (s != 0) {
len++;
}
long n = 0;
while (pos < num.length) {
// first digit is 0, skipped
if (num[s] == '0' && pos - s > 0) {
break;
}
// calculation
n = n * 10 + (int)(num[pos] - '0');
if (n > Integer.MAX_VALUE) {
break;
}
// copy current position of num to expression
exp[len] = num[pos];
len++;
pos++;
// at the start, no current value, go to dfs straightly
if (s == 0) {
dfs(pos, len, n, n, target);
continue;
}
// try '+', '-', '*'
exp[l] = '+';
dfs(pos, len, n, curr + n, target);
exp[l] = '-';
dfs(pos, len, -n, curr - n, target);
exp[l] = '*';
dfs(pos, len, prev * n, curr - prev + prev * n, target);
}
}
}
283. Move Zeroes
https://leetcode.com/problems/move-zeroes/
遍历数组,将所有非0的数按顺序填到数组最前;之后的部分补0即可。
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[count] = nums[i];
count++;
}
}
for (int i = count; i < nums.length; i++) {
nums[i] = 0;
}
return;
}
}
284. Peeking Iterator
https://leetcode.com/problems/peeking-iterator/
构造函数里,需要先检查迭代器是否为空(含有下一个元素)。
使用isIterationFinished
变量,记录目前迭代器是否访问完毕;在已访问完的情况下调用next()
,需要抛出异常。
// Java Iterator interface reference:
// https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html
import java.util.NoSuchElementException;
class PeekingIterator implements Iterator<Integer> {
Integer next;
Iterator<Integer> iter;
boolean isIterationFinished = false; // whether finished iterating current iterator
public PeekingIterator(Iterator<Integer> iterator) {
// initialize any member here.
iter = iterator;
// pre-set 'next' pointer and 'isIterationFinished' flag
if (iter.hasNext()) {
next = iter.next();
} else {
isIterationFinished = true;
}
}
// Returns the next element in the iteration without advancing the iterator.
public Integer peek() {
return next;
}
// hasNext() and next() should behave the same as in the Iterator interface.
// Override them if needed.
@Override
public Integer next() {
if (isIterationFinished) {
throw new NoSuchElementException();
}
Integer result = next;
// need to set 'next' pointer and 'isIterationFinished' flag before return
if (iter.hasNext()) {
next = iter.next();
} else {
isIterationFinished = true;
}
return result;
}
@Override
public boolean hasNext() {
return !isIterationFinished;
}
}
285. Inorder Successor in BST
https://leetcode.com/problems/inorder-successor-in-bst/
最近的继承者要么是p
的右子树上的最小值,要么是p
的父节点。
当root
的值比p
小时,不断递归调用root
的右子,向右查找最近的successor;否则,调用root
的左子进行判断,如果存在左子,root
的左子肯定比root
的小,但又比root
的父节点大,因此返回root
的左子;如果不存在左子,说明root
本身就已经是以root
为根的树上能获取到的最小节点(再小就需要返回上一级父节点),因此返回root
本身。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
if (root == null) {
return null;
}
// start to search in left subtree
if (root.val <= p.val) {
return inorderSuccessor(root.right, p);
} else {
// may be the smallest node in left subtree, otherwise root itself
TreeNode left = inorderSuccessor(root.left, p);
return (left != null) ? left : root;
}
}
}
286. Walls and Gates
https://leetcode.com/problems/walls-and-gates/
DFS遍历数组,发现一个门时开始DFS,更新整个数组的值。DFS的过程中,迭代的深度即表示当前方格到门的距离。
class Solution {
public void wallsAndGates(int[][] rooms) {
if (rooms == null || rooms.length == 0 || rooms[0].length == 0) {
return;
}
for (int i = 0; i < rooms.length; i++) {
for (int j = 0; j < rooms[0].length; j++) {
// start from a gate to update the whole matrix
if (rooms[i][j] == 0) {
dfs(rooms, i, j, 0);
}
}
}
return;
}
private void dfs(int[][] rooms, int i, int j, int depth) {
// set current depth of route
rooms[i][j] = depth;
if (i > 0 && rooms[i - 1][j] > depth + 1) {
dfs(rooms, i - 1, j, depth + 1);
}
if (i < rooms.length - 1 && rooms[i + 1][j] > depth + 1) {
dfs(rooms, i + 1, j, depth + 1);
}
if (j > 0 && rooms[i][j - 1] > depth + 1) {
dfs(rooms, i, j - 1, depth + 1);
}
if (j < rooms[0].length - 1 && rooms[i][j + 1] > depth + 1) {
dfs(rooms, i, j + 1, depth + 1);
}
}
}
287. Find the Duplicate Number
https://leetcode.com/problems/find-the-duplicate-number/
与此前的Linked List Cycle II思路相近,仍旧使用快慢指针,慢指针每次跳转一位,快指针每次跳转两位。经过相同次数跳转后,快慢指针将指向同一数字。这时,快指针已经比慢指针多完成了一个cycle的循环。从相遇点到cycle入口到距离恰好等于慢指针此前移动的次数:
因此,将慢指针放回起点,之后让两个指针等速跳转,慢指针重走一遍来时的路,快指针再走一遍cycle,相遇点就是环的入口,即重复的数字。
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0, fast = 0;
while (fast < nums.length && nums[fast] < nums.length) {
slow = nums[slow];
fast = nums[ nums[fast] ];
if (slow == fast) {
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
return -1;
}
}
288. Unique Word Abbreviation
https://leetcode.com/problems/unique-word-abbreviation/
使用一个map,保存缩略词作为key,可能的unique word作为value。当发现一个新缩略词时,将其作为key,同时将这个单词本身作为value保存进map中;而一旦发现了第二个具有相缩略词的单词时,这个单词肯定不是unique word,因此将这个缩略词对应的value设为空,等待之后是否还有其他单词补入。判断是否为unique word时,只需要检查其对应缩略词key是否能获取得到value即可。
class ValidWordAbbr {
// only save the abbr key and potential unique word as value
Map<String, String> wordAbbr = new HashMap<>();
public ValidWordAbbr(String[] dictionary) {
for (String str : dictionary) {
String abbr = getAbbreviation(str);
if (!wordAbbr.containsKey(abbr)) {
// a new key
wordAbbr.put(abbr, str);
} else if (!wordAbbr.get(abbr).equals(str)) {
// eliminate a pair of repeating words
wordAbbr.put(abbr, "");
}
}
}
public boolean isUnique(String word) {
String abbr = getAbbreviation(word);
if (!wordAbbr.containsKey(abbr)) {
return true;
}
return wordAbbr.get(abbr).equals(word);
}
private String getAbbreviation(String word) {
int len = word.length();
if (len < 3) {
return word;
}
return word.charAt(0) + String.valueOf(len - 2) + word.charAt(len - 1);
}
}
289. Game of Life
https://leetcode.com/problems/game-of-life/
使用两位状态码表示一个节点的状态:
[2nd bit, 1st bit] = [next state, current state]
- 00 dead (next) <- dead (current)
- 01 dead (next) <- live (current)
- 10 live (next) <- dead (current)
- 11 live (next) <- live (current)
在统计周围方格的情况时,只需要判断1st bit的情况(和1取与);而决定一格是否生存,只需要修改2nd bit即可。只要2nd bit为1(对应状态2或3),右移后下一回合这一格就是生存的。
class Solution {
public void gameOfLife(int[][] board) {
if (board == null || board[0].length == 0) {
return;
}
int row = board.length, col = board[0].length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int lives = countLives(board, row, col, i, j);
if (board[i][j] == 1 && lives >= 2 && lives <= 3) {
// lives on to the next generation
board[i][j] = 3;
}
if (board[i][j] == 0 && lives == 3) {
// reproduction
board[i][j] = 2;
}
}
}
// shift to next state
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
board[i][j] >>= 1;
}
}
}
private int countLives(int[][] board, int row, int col, int i, int j) {
int lives = 0;
// count how many alive cells in 3*3 grids, and reduce self cell
for (int x = Math.max(i-1, 0); x <= Math.min(i+1, row-1); x++) {
for (int y = Math.max(j-1, 0); y <= Math.min(j+1, col-1); y++) {
lives += board[x][y] & 1;
}
}
lives -= board[i][j] & 1;
return lives;
}
}
290. Word Pattern
https://leetcode.com/problems/word-pattern/
使用HashMap建立起<字母,下标>和<单词,下标>的对应,比较上一次字母、单词各自出现时对应的下标是否相等。注意两点:
map.put()
返回插入前的value,可以直接用来比较,不需要再调用map.containsKey()
- Java的autoboxing机制,即JVM会缓存-128 ~ 127之间的整数,此时这两个整数对象可以简单用
==
比较值。如果循环中的i
声明为int类型,而恰好遇到了很长的测试用例使得i
超出了这个范围,比较就失效了。(值相等也返回false)
class Solution {
public boolean wordPattern(String pattern, String str) {
char[] characters = pattern.toCharArray();
String[] words = str.split(" ");
// str and pattern must have same length
if (characters.length != words.length) {
return false;
}
Map map = new HashMap<>();
for (Integer i = 0; i < characters.length; i++) {
// map.put() returns the value before inserting
if (map.put(characters[i], i) != map.put(words[i], i)) {
return false;
}
}
return true;
}
}
291. Word Pattern II
https://leetcode.com/problems/word-pattern-ii/
使用回溯算法,用一个map保存pattern
中每一个字母入口以及对应的字符串,再使用一个Set保存所有尝试过的字符串。逐个字母向前推进pattern
和str
,同时检查str
中剩余的所有可能字符串,只要没有出现长度不合法或是模式不匹配,就dfs调用下一层检查。当pattern
和str
的指针同时达到结尾,说明匹配成功。
class Solution {
public boolean wordPatternMatch(String pattern, String str) {
if (pattern.length() == 0 && str.length() == 0) {
return true;
}
if (pattern.length() > str.length()) {
return false;
}
// string entry, string
Map<Character, String> charToStr = new HashMap<>();
Set<String> visited = new HashSet<>();
return findPatternMatch(pattern, 0, str, 0, charToStr, visited);
}
private boolean findPatternMatch(String pattern, int pIndex, String str, int sIndex, Map<Character, String> charToStr, Set<String> visited) {
// one reaches the end, the other should also reaches
if (pIndex == pattern.length()) {
return sIndex == str.length();
}
if (sIndex == str.length()) {
return pIndex == pattern.length();
}
char c = pattern.charAt(pIndex);
if (charToStr.containsKey(c)) {
String cs = charToStr.get(c);
if (cs.length() > str.length() - sIndex || !cs.equals(str.substring(sIndex, sIndex + cs.length()))) {
return false;
}
return findPatternMatch(pattern, pIndex + 1, str, sIndex + cs.length(), charToStr, visited);
}
int sRemain = str.length() - sIndex;
int pRemain = pattern.length() - pIndex - 1;
// try different string length
for (int len = 1; len <= sRemain - pRemain; len++) {
String sub = str.substring(sIndex, sIndex + len);
// skip visited string
if (visited.contains(sub)) {
continue;
}
charToStr.put(c, sub);
visited.add(sub);
// dfs, try next character in pattern
if (findPatternMatch(pattern, pIndex + 1, str, sIndex + len, charToStr, visited)) {
return true;
}
charToStr.remove(c);
visited.remove(sub);
}
return false;
}
}
292. Nim Game
https://leetcode.com/problems/nim-game/
如果石头个数是4的倍数,先手方拿任意x
个,后方只需要拿4-x
个,先手方必败。其余情况下,只要不是4的倍数,先手方拿一些石头使得剩余石头个数为4的倍数就能保证必胜。
class Solution {
public boolean canWinNim(int n) {
return n % 4 != 0;
}
}
293. Flip Game
https://leetcode.com/problems/flip-game/
将字符串转为chararray,循环遍历遇到连续两个+
时,翻转到-
,再构造字符串添加到结果中;之后这里两个字母翻转为+
,继续循环。
class Solution {
public List<String> generatePossibleNextMoves(String currentState) {
List<String> result = new ArrayList<>();
char[] arr = currentState.toCharArray();
for (int i = 0; i < currentState.length() - 1; i++) {
if (arr[i] == '+' && arr[i+1] == '+') {
// flip
arr[i] = '-';
arr[i+1] = '-';
// add result
result.add(new String(arr));
// restore
arr[i] = '+';
arr[i+1] = '+';
}
}
return result;
}
}
294. Flip Game II
https://leetcode.com/problems/flip-game-ii/
使用-
分隔字符串,对每一个子字符串,分析其长度,如果其长度不为0且不是4的倍数+1,就说明对于这一组子字符串只要自己先手,就一定能让对方不能最终make move。因此添加这个长度到最终的set中。最终检查set中是否还存在元素,即可判断是否还有move。
class Solution {
public boolean canWin(String s) {
Set<Integer> set = new HashSet<>();
for (String str : s.split("-")) {
int curr = str.length();
if (curr != 0 && curr % 4 != 1) {
if (curr % 2 == 1) {
curr--;
}
if (set.contains(curr)) {
set.remove(curr);
} else {
set.add(curr);
}
}
}
return set.size() != 0;
}
}
295. Find Median from Data Stream
https://leetcode.com/problems/find-median-from-data-stream/
使用两个优先队列,一个存放较小的一半数,另一个存放较大的一半数。每次添加新num
时,计算已有数据的中位数,决定num
应该放到哪一半中,再调整平衡两个优先队列的个数。
class MedianFinder {
PriorityQueue<Integer> smallHalf; // descending order
PriorityQueue<Integer> largeHalf; // ascending order
int totalCount = 0; // total element's count
/** initialize your data structure here. */
public MedianFinder() {
smallHalf = new PriorityQueue<>((x, y) -> y - x);
largeHalf = new PriorityQueue<>();
}
public void addNum(int num) {
// initial median to spearate smallHalf and largeHalf num
double median = totalCount > 0 ? findMedian() : num;
if (num < median) {
smallHalf.add(num);
} else {
largeHalf.add(num);
}
// balance smallHalf and largeHalf
if (smallHalf.size() > largeHalf.size() + 1) {
largeHalf.add(smallHalf.poll());
}
if (smallHalf.size() + 1 < largeHalf.size()) {
smallHalf.add(largeHalf.poll());
}
totalCount++;
}
// largest element in smallHalf & smallest element in largeHalf
public double findMedian() {
if (smallHalf.size() > largeHalf.size()) {
return smallHalf.peek();
}
if (largeHalf.size() > smallHalf.size()) {
return largeHalf.peek();
}
return (smallHalf.peek() + largeHalf.peek()) / 2.0;
}
}
296. Best Meeting Point
https://leetcode.com/problems/best-meeting-point/
将每一行、每一列进行累加,得到两个1D的累加数组,分别记录每行与每列的和。之后对于每行或每列,用求和平衡法寻找到所有的行重心和列重心。
class Solution {
public int minTotalDistance(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[] rowSum = new int[n], colSum = new int[m];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
rowSum[j] += grid[i][j];
colSum[i] += grid[i][j];
}
return minDistance1D(rowSum) + minDistance1D(colSum);
}
public int minDistance1D(int[] vector) {
int i = -1, j = vector.length;
int distance = 0, left = 0, right = 0;
// reach the middle sum point of each row and column
while (i != j) {
if (left < right) {
distance += left;
i++;
left += vector[i];
} else {
distance += right;
j--;
right += vector[j];
}
}
return distance;
}
}
297. Serialize and Deserialize Binary Tree
https://leetcode.com/problems/serialize-and-deserialize-binary-tree/
串行化的过程比较容易,直接使用前序遍历的方法,使用一个StringBuilder不断添加节点的值即可,中间用逗号分隔。
解串行化复原的过程稍难,由于不允许使用静态变量,因此需要使用一个长度为1的动态数组用于储存当前的下标,仍然使用前序遍历。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serializeHelper(root, sb);
return sb.toString();
}
private void serializeHelper(TreeNode node, StringBuilder sb) {
if (node == null) {
sb.append("null,");
return;
}
sb.append(node.val).append(",");
serializeHelper(node.left, sb);
serializeHelper(node.right, sb);
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] nodes = data.split(",");
int[] index = new int[1];
return deserializeHelper(nodes, index);
}
private TreeNode deserializeHelper(String[] nodes, int[] index) {
if (nodes[index[0]].equals("null")) {
index[0]++;
return null;
}
TreeNode cur = new TreeNode(Integer.valueOf(nodes[index[0]]));
index[0]++;
cur.left = deserializeHelper(nodes, index);
cur.right = deserializeHelper(nodes, index);
return cur;
}
}
298. Binary Tree Longest Consecutive Sequence
https://leetcode.com/problems/binary-tree-longest-consecutive-sequence/
使用dfs,如果当前节点孩子的值增长了1,就在连续计数上增加1,反之重置结果。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int result;
public int longestConsecutive(TreeNode root) {
if (root == null) {
return 0;
}
helper(root, 0, root.val);
return result;
}
private void helper(TreeNode node, int tempCount, int value) {
if (node == null) {
return;
}
// if node's value is as expected, add 1 to count
if (node.val == value) {
tempCount++;
} else {
tempCount = 1;
}
// update result
result = Math.max(result, tempCount);
// next level's node value should increment by 1
helper(node.left, tempCount, node.val + 1);
helper(node.right, tempCount, node.val + 1);
}
}
299. Bulls and Cows
https://leetcode.com/problems/bulls-and-cows/
一次遍历解决问题:使用numbers
数组记录每个数字出现的次数。最理想的情况下,两个数字的对应位置数字相等,直接累加bull
。
遍历secret
时,numbers[i]
增加;此时如果遇到了numbers[i]
小于0的情况,说明这个数字已经在guess
中出现过,但是位置不对,因此累加cow
。同理,遍历guess
时,numbers[i]
减少;此时如果遇到了numbers[i]
大于0的情况,也说明这个数字已经在secret
中出现过且位置不对,累加cow
。
class Solution {
public String getHint(String secret, String guess) {
int bulls = 0, cows = 0;
int[] numbers = new int[10];
for (int i = 0; i < secret.length(); i++) {
if (secret.charAt(i) == guess.charAt(i)) {
bulls++;
} else {
if (numbers[secret.charAt(i) - '0'] < 0) {
cows++;
}
numbers[secret.charAt(i) - '0']++;
if (numbers[guess.charAt(i) - '0'] > 0) {
cows++;
}
numbers[guess.charAt(i) - '0']--;
}
}
return bulls + "A" + cows + "B";
}
}
300. Longest Increasing Subsequence
https://leetcode.com/problems/longest-increasing-subsequence/
使用一个tails
数组,tails[i]
记录当递增序列长度为i+1
时,最小的序列尾的值。由此,可以确定出大于这个最小序列尾的数的个数,记为size
。
例如,对于数列[4,5,6,3]
,有:
len = 1 : [4], [5], [6], [3] => tails[0] = 3
len = 2 : [4, 5], [5, 6] => tails[1] = 5
len = 3 : [4, 5, 6] => tails[2] = 6
容易得知,tails
是一个递增的数列。因此,每次加入一个数时就对tails
使用一次二分查找,将这个数插入到tails
中:
- 如果当前数比所有的tail都大,则添加在
tails
的末尾 - 如果当前数位于某个(
tails[i-1]
,tails[i]
]之间,更新tails[i]
的值为当前数
在这一过程中,如果添加的位置正好是size
,说明这个数肯定也是也在LIS中,因此size
增加1。
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] tails = new int[nums.length];
int size = 0; // length of LIS
for (int num : nums) {
int left = 0, right = size;
while (left < right) {
int mid = left + (right - left) / 2;
if (tails[mid] < num) {
left = mid + 1;
} else {
right = mid;
}
}
tails[left] = num;
// update size
if (left == size) {
size++;
}
}
return size;
}
}