LeetCode 101-200
LeetCode Problems 101-200.
101. Symmetric Tree
https://leetcode.com/problems/symmetric-tree/
一棵树对称有两种情况:
- 当前节点为空
- 左右子树对称
因此我们可以构造一个辅助比较函数判断左右子树是否对称。如果一边为空节点,另一边也必须为空节点;另外对称节点的值应当相等,并且左子的儿子还要和右子的儿子对称相等(无论是否为空节点)。
/** * 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 boolean isSymmetric(TreeNode root) { return root == null || helper(root.left, root.right); }
public boolean helper(TreeNode left, TreeNode right) { // if one side is null, the other side must be null either if (left == null || right == null) { return left == right; }
// nodes at two sides should have same value if (left.val != right.val) { return false; }
// children's children should be same return helper(left.left, right.right) && helper(left.right, right.left); }}102. Binary Tree Level Order Traversal
https://leetcode.com/problems/binary-tree-level-order-traversal/
第一种方法,使用DFS,遍历至当前节点时,直接将其添加到这个节点所在的层数数组中。
/** * 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<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> result = new LinkedList<>(); helper(root, result, 0); return result; }
private void helper(TreeNode curNode, List<List<Integer>> result, int level) { if (curNode == null) { return; }
// add new level if (result.size() <= level) { result.add(new LinkedList<Integer>()); }
result.get(level).add(curNode.val); helper(curNode.left, result, level + 1); helper(curNode.right, result, level + 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 List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> result = new LinkedList<>(); if (root == null) { return result; }
Queue<TreeNode> q = new ArrayDeque<>(); q.offer(root); while (!q.isEmpty()) { int size = q.size(); List<Integer> tempList = new LinkedList<Integer>(); for (int i = 0; i < size; i++) { if (q.peek().left != null) { q.offer(q.peek().left); } if (q.peek().right != null) { q.offer(q.peek().right); } tempList.add(q.poll().val); }
result.add(tempList); } return result; }}103. Binary Tree Zigzag Level Order Traversal
https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/
在上一题的DFS基础上略加修改,根据当前层数的奇偶性,决定按顺序添加还是倒序添加即可。
/** * 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<List<Integer>> zigzagLevelOrder(TreeNode root) { List<List<Integer>> result = new LinkedList<>(); helper(root, result, 0); return result; }
public void helper(TreeNode curNode, List<List<Integer>> result, int level) { if (curNode == null) { return; }
// add new level if (result.size() <= level) { result.add(new LinkedList<Integer>()); }
// decide insert position by odd / even level if (level % 2 == 0) { result.get(level).add(curNode.val); } else { result.get(level).add(0, curNode.val); }
helper(curNode.left, result, level + 1); helper(curNode.right, result, level + 1); }}104. Maximum Depth of Binary Tree
https://leetcode.com/problems/maximum-depth-of-binary-tree/
使用DFS,分别比较左子树和右子树的深度,取最大值+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 maxDepth(TreeNode root) { return height(root); }
private int height(TreeNode node) { if (node == null) { return 0; }
// height of left subtree and right subtree int left = height(node.left), right = height(node.right); return Math.max(left, right) + 1; }}105. Construct Binary Tree from Preorder and Inorder Traversal
https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
在只有前序遍历的情况下,我们只能模糊地知道我们会先遍历到根节点,但之后遍历左右子树时,我们不知道什么时候结束遍历左子树,开始遍历右子树。所以我们需要中序遍历进行辅助,因为中序遍历会先于遍历根节点之前遍历完左子树。我们只需要拿preorder遇到的根节点到inorder中搜索,就能知道左、右子树分别是哪些元素。
使用一个变量rootIdx跟踪前序遍历数组,在inorder中到当前根节点preorder[rootIdx]。inorder左侧的点全部在根节点左子树上,右侧的点都在根节点右子树上。
/** * 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 rootIdx = 0; // index of current root node in preorder
public TreeNode buildTree(int[] preorder, int[] inorder) { if (preorder.length != inorder.length || inorder.length == 0) { return null; }
return builder(preorder, inorder, 0, inorder.length - 1); }
private TreeNode builder(int[] preorder, int[] inorder, int start, int end) { if (start > end) { return null; }
TreeNode node = new TreeNode(preorder[rootIdx]); rootIdx++;
if (start == end) { return node; }
// find current element in inorder array // this element will be the split point int index = 0; for (int i = end; i >= start; i--) { if (inorder[i] == node.val) { index = i; break; } }
node.left = builder(preorder, inorder, start, index - 1); node.right = builder(preorder, inorder, index + 1, end); return node; }}106. Construct Binary Tree from Inorder and Postorder Traversal
https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
与上一题思路类似,需要注意的是先构造右子树,再构造左子树。这是因为后序遍历先遍历右子树再遍历左子树,和前序遍历相反。另外根据对称性,从postorder中提取根节点时也应该从后往前遍历。
/** * 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 rootIdx = 0; // index of current root node in postorder
public TreeNode buildTree(int[] inorder, int[] postorder) { if (inorder.length != postorder.length || inorder.length == 0) { return null; }
// initialization, searched from the end of postorder rootIdx = postorder.length - 1;
return builder(postorder, inorder, 0, postorder.length - 1); }
private TreeNode builder(int[] postorder, int[] inorder, int start, int end) { if (start > end) { return null; }
TreeNode node = new TreeNode(postorder[rootIdx]); rootIdx--;
if (start == end) { return node; }
// find current element in inorder array // this element will be the split point int index = 0; for (int i = end; i >= start; i--) { if (inorder[i] == node.val) { index = i; break; } }
node.right = builder(postorder, inorder, index + 1, end); node.left = builder(postorder, inorder, start, index - 1); return node; }}107. Binary Tree Level Order Traversal II
https://leetcode.com/problems/binary-tree-level-order-traversal-ii/
仍然使用DFS,但补齐至当前层数时,需要倒序添加层数(第一层为最后,第二层为倒数第二,…),获取层数的结果时应以总数和当前层数之差作为索引。
/** * 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<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> result = new LinkedList<>(); helper(root, result, 0); return result; }
private void helper(TreeNode curNode, List<List<Integer>> result, int level) { if (curNode == null) { return; }
// add new level if (result.size() <= level) { result.add(0, new LinkedList<Integer>()); }
result.get(result.size() - 1 - level).add(curNode.val); helper(curNode.left, result, level + 1); helper(curNode.right, result, level + 1); }}108. Convert Sorted Array to Binary Search Tree
https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/
结合二分查找思想,总是将当前中位数作为根节点,比其小的数位于左子树,比其大的数位于右子树。当子数组区间下标重叠,返回空节点。
/** * 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 sortedArrayToBST(int[] nums) { if (nums.length == 0) { return null; } TreeNode result = helper(nums, 0, nums.length - 1); return result; }
private TreeNode helper(int[] nums, int low, int high) { if (low > high) { return null; }
// find median of the array, set as root node int mid = low + (high - low) / 2; TreeNode node = new TreeNode(nums[mid]); node.left = helper(nums, low, mid - 1); node.right = helper(nums, mid + 1, high);
return node; }}109. Convert Sorted List to Binary Search Tree
https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/
使用快慢指针获取到子链表的中间节点,其余仍然使用二分法。
/** * 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; } * } *//** * 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 sortedListToBST(ListNode head) { if (head == null) { return null; } return helper(head, null); }
private TreeNode helper(ListNode head, ListNode tail) { if (head == tail) { return null; }
// find the median node, set it as root ListNode slow = head, fast = head; while (fast != tail && fast.next != tail) { slow = slow.next; fast = fast.next.next; }
TreeNode mid = new TreeNode(slow.val); mid.left = helper(head, slow); mid.right = helper(slow.next, tail); return mid; }}110. Balanced Binary Tree
https://leetcode.com/problems/balanced-binary-tree/
此题实际是一个计算树高度的过程,如果树的高度不是-1(不平衡的情况),说明树是平衡的。讨论特殊情况如下:
- 遇到空节点,返回0
- 左子树或右子树的高度为-1,说明子树已经不平衡,那么以当前节点为根的树高度也为-1
- 左、右子树高度差值大于1,则当前节点高度为-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 { public boolean isBalanced(TreeNode root) { return helper(root) != -1; }
private int helper(TreeNode node) { if (node == null) { return 0; }
int leftHeight = helper(node.left); if (leftHeight == -1) { return -1; }
int rightHeight = helper(node.right); if (rightHeight == -1) { return -1; }
if (Math.abs(leftHeight - rightHeight) > 1) { return -1; }
return Math.max(leftHeight, rightHeight) + 1; }}111. Minimum Depth of Binary Tree
https://leetcode.com/problems/minimum-depth-of-binary-tree/
第一种方法,使用递归,与寻找最大深度的解法类似。注意当某一子树为空时,树的高度实际上就是另一个子树的高度。
/** * 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 minDepth(TreeNode root) { if (root == null) { return 0; }
int leftHeight = minDepth(root.left); int rightHeight = minDepth(root.right); return leftHeight == 0 || rightHeight == 0 ? leftHeight + rightHeight + 1 : Math.min(leftHeight, rightHeight) + 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 minDepth(TreeNode root) { if (root == null) { return 0; }
Queue<TreeNode> q = new ArrayDeque<>(); q.offer(root); int depth = 0;
while (!q.isEmpty()) { int size = q.size(); depth++;
for (int i = 0; i < size; i++) { TreeNode node = q.poll();
// no children, so one subtree will end here if (node.left == null && node.right == null) { return depth; }
if (node.left != null) { q.offer(node.left); } if (node.right != null) { q.offer(node.right); } } }
return depth; }}112. Path Sum
https://leetcode.com/problems/path-sum/
每向下一层递归时就更新targetSum的值,遇到叶子节点的值与当前sum相同则返回true。
/** * 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 boolean hasPathSum(TreeNode root, int targetSum) { if (root == null) { return false; }
if (root.left == null && root.right == null && root.val == targetSum) { return true; }
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); }}113. Path Sum II
https://leetcode.com/problems/path-sum-ii/
回溯算法,无论当前递归内是否找到一条path,都需要在完成遍历后从临时数组中弹出最后一个数,这样才能尝试其他path。
/** * 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<List<Integer>> pathSum(TreeNode root, int sum) { List<List<Integer>> result = new LinkedList<>(); helper(result, new LinkedList<>(), root, sum); return result; }
private void helper(List<List<Integer>> result, List<Integer> temp, TreeNode node, int sum) { if (node == null) { return; }
temp.add(node.val);
if (node.left == null && node.right == null && node.val == sum) { // find a path, remove the last node and return result.add(new LinkedList<>(temp)); temp.remove(temp.size() - 1); return; } else { // else continue traveling down helper(result, temp, node.left, sum - node.val); helper(result, temp, node.right, sum - node.val); }
// no path found, remove the last node temp.remove(temp.size() - 1); }}114. Flatten Binary Tree to Linked List
https://leetcode.com/problems/flatten-binary-tree-to-linked-list/
解题思路如下:
- 寻找到当前节点右子树中最右的节点,如果为空则寻找其兄弟左节点
- 更新当前节点的右子为
prev,这样能之前flatten的子树接到当前节点上;左子为空 prev更新为当前节点,因为当前节点是最后一个被flatten的根节点
/** * 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 TreeNode prev = null; // previous flattened node
public void flatten(TreeNode root) { if (root == null) { return; }
flatten(root.right); flatten(root.left);
root.right = prev; root.left = null;
prev = root; }}115. Distinct Subsequences
https://leetcode.com/problems/distinct-subsequences/
使用动态规划,dp[i][j]表示s的前j个字符中,包含t的前i个字符的次数。考虑两种情况:
s的第j个字符与t的第i个字符不同,则dp[i+1][j+1]与dp[i+1][j]相同,因为s多出的这个字母不影响次数s的第j个字符与t的第i个字符相同,则dp[i+1][j+1]不仅包括dp[i+1][j],还包括dp[i][j],因为要加上同时去掉了这对相同字母后,剩下的匹配次数。
class Solution { public int numDistinct(String s, String t) { int slen = s.length(), tlen = t.length();
int[][] dp = new int[tlen+1][slen+1];
// initialization, substring of s can always contain exactly 1 empty string for (int j = 0; j < slen; j++) { dp[0][j] = 1; }
for (int i = 0; i < tlen; i++) { for (int j = i; j < slen; j++) { dp[i+1][j+1] = (t.charAt(i) == s.charAt(j)) ? dp[i+1][j] + dp[i][j] : dp[i+1][j]; } }
return dp[tlen][slen]; }}116. Populating Next Right Pointers in Each Node
https://leetcode.com/problems/populating-next-right-pointers-in-each-node/
使用两个指针level和cur遍历整个树,其中level遍历逐层,cur遍历一层中的节点。对于每一个节点,完成两步修改:
- 将
cur的左儿子指向cur的右儿子 - 如果
cur本身和另一个兄弟相连,那么cur的右儿子和cur的兄弟的左儿子就是邻居关系,二者需要连起来
/*// Definition for a Node.class Node { public int val; public Node left; public Node right; public Node next;
public Node() {}
public Node(int _val) { val = _val; }
public Node(int _val, Node _left, Node _right, Node _next) { val = _val; left = _left; right = _right; next = _next; }};*/
class Solution { public Node connect(Node root) { if (root == null) { return null; }
Node level = root; // track each level Node cur = null; // track each node in one level
while (level.left != null) { cur = level;
while (cur != null) { // left child points to right child cur.left.next = cur.right;
// if there is a right neighbor, right child points to right neighbor's left child if (cur.next != null) { cur.right.next = cur.next.left; }
// cur = cur.next; }
// move to next level level = level.left; }
return root; }}117. Populating Next Right Pointers in Each Node II
https://leetcode.com/problems/populating-next-right-pointers-in-each-node-ii/
模拟层次遍历,cur指针指向当前层,curChild指向下一层。判断是否存在左子或右子,以此移动指针。
/*// Definition for a Node.class Node { public int val; public Node left; public Node right; public Node next;
public Node() {}
public Node(int _val) { val = _val; }
public Node(int _val, Node _left, Node _right, Node _next) { val = _val; left = _left; right = _right; next = _next; }};*/
class Solution { public Node connect(Node root) { Node cur = root;
while (cur != null) { Node head = new Node(0); // dummy head of next level Node curChild = head; // child in the next level
while (cur != null) { if (cur.left != null) { curChild.next = cur.left; curChild = curChild.next; }
if (cur.right != null) { curChild.next = cur.right; curChild = curChild.next; }
// move to cur's sibling cur = cur.next; }
// move to next level cur = head.next; }
return root; }}118. Pascal’s Triangle
https://leetcode.com/problems/pascals-triangle/
遍历每一行的开始时,都在行首添加一个1,之后从第二个元素至倒数第二个元素,与下一个相邻元素进行累加即可。
class Solution { public List<List<Integer>> generate(int numRows) { List<List<Integer>> result = new ArrayList<>(); if (numRows <= 0) { return result; } List<Integer> row = new ArrayList<>();
for (int i = 0; i < numRows; i++) { // add '1' at head of each row row.add(0, 1);
for (int j = 1; j < row.size() - 1; j++) { row.set(j, row.get(j) + row.get(j+1)); }
result.add(new ArrayList<>(row)); }
return result; }}119. Pascal’s Triangle II
https://leetcode.com/problems/pascals-triangle-ii/
在上一题代码的基础上略作修改,只将最后一行作为结果输出即可。
class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> row = new ArrayList<>(); row.add(1);
for (int i = 0; i < rowIndex; i++) { // add '1' at head of each row row.add(0, 1);
for (int j = 1; j < row.size() - 1; j++) { row.set(j, row.get(j) + row.get(j+1)); } }
return row; }}120. Triangle
https://leetcode.com/problems/triangle/
使用一维动态规划,自底向上迭代更新dp,选取相邻元素的最小值,再加上当前遍历的元素。
class Solution { public int minimumTotal(List<List<Integer>> triangle) { int[] dp = new int[triangle.size() + 1];
// bottom-up for (int i = triangle.size() - 1; i >= 0; i--) { for (int j = 0; j <= i; j++) { dp[j] = Math.min(dp[j], dp[j+1]) + triangle.get(i).get(j); } }
return dp[0]; }}121. Best Time to Buy and Sell Stock
https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
算出每天价格之间的差值,获利最大的时刻其实就是差值之和最大的时刻。
class Solution { public int maxProfit(int[] prices) { if (prices == null || prices.length <= 1) { return 0; } int maxDiff = 0, result = 0;
for (int i = 1; i < prices.length; i++) { // difference between today and yesterday's prices maxDiff += (prices[i] - prices[i-1]);
// find the max price difference maxDiff = Math.max(0, maxDiff); result = Math.max(maxDiff, result); }
return result; }}122. Best Time to Buy and Sell Stock II
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/
贪心思想,只要有一天能赚钱,都用一次买卖赚走,累积利润即可得到结果。
class Solution { public int maxProfit(int[] prices) { if (prices == null || prices.length <= 1) { return 0; } int result = 0;
for (int i = 1; i < prices.length; i++) { result += Math.max(0, prices[i] - prices[i-1]); }
return result; }}123. Best Time to Buy and Sell Stock III
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
使用两组变量cost和profit,分别表示两次买入的最小成本和两次卖出的最大利润。
需要注意,第二次买入的最小成本,建立在第一次卖出的最大利润之上(相当于拿已有的利润,再买入当前股票,两个数值合成为总成本)。
class Solution { public int maxProfit(int[] prices) { if (prices == null || prices.length <= 1) { return 0; }
int cost1 = Integer.MAX_VALUE, cost2 = Integer.MAX_VALUE; int profit1 = 0, profit2 = 0;
for (int i = 0; i < prices.length; i++) { cost1 = Math.min(cost1, prices[i]); profit1 = Math.max(profit1, prices[i] - cost1);
// cost2 will be based on profit1 cost2 = Math.min(cost2, prices[i] - profit1); profit2 = Math.max(profit2, prices[i] - cost2); }
return profit2; }}124. Binary Tree Maximum Path Sum
https://leetcode.com/problems/binary-tree-maximum-path-sum/
对每一个节点,计算其左子树和右子树上的最大和,返回较大一侧的值与自身节点值之和。注意,Path Sum可包括两个分支,因此对于最大值的更新,要包括当前节点和左右子节点。
/** * 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 = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) { helper(root); return result; }
private int helper(TreeNode node) { if (node == null) { return 0; } int leftSum = Math.max(0, helper(node.left)); int rightSum = Math.max(0, helper(node.right));
// update current path sum result = Math.max(result, leftSum + rightSum + node.val);
// max subtree sum + current node's value return Math.max(leftSum, rightSum) + node.val; }}125. Valid Palindrome
https://leetcode.com/problems/valid-palindrome/
使用Character类的API,或者正则表达式解决。
class Solution { public boolean isPalindrome(String s) { if (s.length() == 0 || s.length() == 1) { return true; }
// String modified = s.replaceAll("[^A-Za-z0-9]", "").toLowerCase();
int head = 0, tail = s.length() - 1;
while (head <= tail) { if (Character.isLetter(s.charAt(head)) || Character.isDigit(s.charAt(head))) { if (Character.isLetter(s.charAt(tail)) || Character.isDigit(s.charAt(tail))) { if (Character.toLowerCase(s.charAt(head)) != Character.toLowerCase(s.charAt(tail))) { return false; }
head++; tail--; } else { tail--; } } else { head++; } }
return true; }}126. Word Ladder II
https://leetcode.com/problems/word-ladder-ii/
先用用BFS,替换begin中每个单词的每一个字母,汇总得到每个单词能够转化得到的单词列表,存在map中。再使用DFS,依据单词列表的演化,将结果录入。
class Solution { public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { List<List<String>> result = new ArrayList<>();
Set<String> dicts = new HashSet<>(wordList); if (!dicts.contains(endWord)) { return result; }
Set<String> begin = new HashSet<>(), end = new HashSet<>(); Map<String, List<String>> map = new HashMap<>(); begin.add(beginWord); end.add(endWord);
bfs(map, begin, end, dicts, false);
List<String> tempList = new ArrayList<>(); tempList.add(beginWord); dfs(map, result, tempList, beginWord, endWord);
return result; }
private void bfs(Map<String, List<String>> map, Set<String> begin, Set<String> end, Set<String> dicts, boolean reverse) { if (begin.size() == 0) { return; } dicts.removeAll(begin);
// beginning set in next search Set<String> newBegin = new HashSet<>();
boolean finish = false;
for (String str : begin) { char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) { // backup old alphabet char old = chars[i];
// replace an alphabet with another for (char c = 'a'; c <= 'z'; c++) { if (old == c) { continue; } chars[i] = c;
String candidate = String.valueOf(chars);
// new word must be in wordList if (!dicts.contains(candidate)) { continue; }
// new word is in end list if (end.contains(candidate)) { finish = true; } else { newBegin.add(candidate); }
// str -> candidate's list String key = reverse ? candidate : str; String value = reverse ? str : candidate; if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } map.get(key).add(value); }
// restore old word chars[i] = old; } }
// optional, swap newBegin and end to speed up if (!finish) { if (newBegin.size() > end.size()) { bfs(map, end, newBegin, dicts, !reverse); } else { bfs(map, newBegin, end, dicts, reverse); } } }
private void dfs(Map<String, List<String>> map, List<List<String>> result, List<String> tempList, String beginWord, String endWord) { if (beginWord.equals(endWord)) { result.add(new ArrayList<>(tempList)); return; }
// current beginWord cannot transform to another work if (!map.containsKey(beginWord)) { return; }
for (String word : map.get(beginWord)) { tempList.add(word); dfs(map, result, tempList, word, endWord); tempList.remove(tempList.size() - 1); } }}127. Word Ladder
https://leetcode.com/problems/word-ladder/
由于这一题不需要不需要输出结果的具体值,因此没必要使用DFS跟踪变换过程,直接使用BFS获取结果长度即可。此外也不需要使用map,直接使用一个set统计已经访问过的单词即可。
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { Set<String> dict = new HashSet<>(wordList);
// wordList does not contain endWord if (!dict.contains(endWord)) { return 0; }
Set<String> begin = new HashSet<>(), end = new HashSet<>(), visited = new HashSet<>(); begin.add(beginWord); end.add(endWord); visited.add(beginWord); visited.add(endWord);
int result = 1;
while (!begin.isEmpty() && !end.isEmpty()) { // beginning set in next search Set<String> newBegin = new HashSet<>();
for (String str : begin) { char[] chars = str.toCharArray();
// replace an alphabet with another for (int i = 0; i < chars.length; i++) { // backup old alphabet char old = chars[i];
for (char c = 'a'; c <= 'z'; c++) { if (c == old) { continue; } chars[i] = c;
String candidate = String.valueOf(chars);
// new word must be in word list if (!dict.contains(candidate)) { continue; }
// current transformation reaches the target if (end.contains(candidate)) { result++; return result; }
// skip visited word if (visited.contains(candidate)) { continue; }
visited.add(candidate); newBegin.add(candidate); }
// restore old word chars[i] = old; } }
// one transformation result++;
// optional, swap newBegin and end to speed up if (newBegin.size() > end.size()) { Set<String> temp = newBegin; newBegin = end; end = temp; }
begin = newBegin; }
return 0; }}128. Longest Consecutive Sequence
https://leetcode.com/problems/longest-consecutive-sequence/
第一种做法,利用Set的迭代器,移除当前元素的相邻元素,统计能移除的最大个数。
class Solution { public int longestConsecutive(int[] nums) { if (nums.length == 0) { return 0; }
Set<Integer> set = new HashSet<>(); for (int num : nums) { set.add(num); }
int result = 0; while (!set.isEmpty()) { int base = set.iterator().next(); set.remove(base);
// start from base, remove adjacent elements int left = base, right = base; while (set.remove(--left)); while (set.remove(++right));
result = Math.max(result, right - left - 1); }
return result; }}第二种做法,利用HashMap模拟Set,提取所有key,判断有多少个连续的key在map中。
class Solution { public int longestConsecutive(int[] nums) { HashMap<Integer, Boolean> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { map.put(nums[i], true); }
int result = 0; for (Integer key: map.keySet()) { // avoid repeat counting if (!map.containsKey(key - 1)) { int length = 1;
while (map.containsKey(key + length)) { length++; }
result = Math.max(result, length); } }
return result; }}129. Sum Root to Leaf Numbers
https://leetcode.com/problems/sum-root-to-leaf-numbers/
自顶向下,每达到一个节点,sum乘10再加上当前节点的值。如果当前节点有儿子,继续向下递归计算子树中的值。
/** * 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 sumNumbers(TreeNode root) { return helper(root, 0); }
private int helper(TreeNode node, int sum) { if (node == null) { return 0; }
int updatedVal = sum * 10 + node.val;
if (node.left == null && node.right == null) { return updatedVal; }
return helper(node.left, updatedVal) + helper(node.right, updatedVal); }}130. Surrounded Regions
https://leetcode.com/problems/surrounded-regions/
使用DFS:
- 从矩阵边缘开始检查,如果某个元素为
'O',将这个元素以及邻近的'O'都改为'E'。这些格子没有完全被'X'包围,因此需要排除 - 将图上剩余所有的
'O'改为'X',再将所有的'E'改为'O'
class Solution { public void solve(char[][] board) { if (board == null || board.length == 0) { return; } int row = board.length, col = board[0].length;
// exclude 'O' om first and last columns for (int i = 0; i < row; i++) { if (board[i][0] == 'O') { dfs(board, i, 0); } if (board[i][col - 1] == 'O') { dfs(board, i, col - 1); } }
// exclude 'O' in first and last rows for (int j = 0; j < col; j++) { if (board[0][j] == 'O') { dfs(board, 0, j); } if (board[row - 1][j] == 'O') { dfs(board, row - 1, j); } }
for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (board[i][j] == 'O') { board[i][j] = 'X'; }
// restore excluded grids if (board[i][j] == 'E') { board[i][j] = 'O'; } } } }
private void dfs(char[][] board, int x, int y) { if (x < 0 || x > board.length - 1 || y < 0 || y > board[0].length - 1 || board[x][y] == 'E' || board[x][y] == 'X') { return; }
// exclude this grid since it is not surrounded by 'X' 4-directionally board[x][y] = 'E';
dfs(board, x + 1, y); dfs(board, x - 1, y); dfs(board, x, y + 1); dfs(board, x, y - 1); }}131. Palindrome Partitioning
https://leetcode.com/problems/palindrome-partitioning/
使用回溯算法,从当前index开始,截取s[index : i+1]的字符串,判断是否为回文串;之后再判断s[i+1 : ]是否存在回文串。
class Solution { public List<List<String>> partition(String s) { List<List<String>> result = new LinkedList<>(); if (s == null || s.length() == 0) { return result; } backtrack(result, new LinkedList<>(), s, 0); return result; }
private void backtrack(List<List<String>> result, List<String> temp, String s, int index) { if (index == s.length()) { result.add(new LinkedList<>(temp)); return; }
for (int i = index; i < s.length(); i++) { if (isPalindrome(s, index, i)) { temp.add(s.substring(index, i + 1)); backtrack(result, temp, s, i + 1); temp.remove(temp.size() - 1); } } }
private boolean isPalindrome(String s, int left, int right) { while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; }
return true; }}132. Palindrome Partitioning II
https://leetcode.com/problems/palindrome-partitioning-ii/
使用动态规划,其中dp[i]表示s[:i]的范围内共需要多少次分割才能满足题意。
观察发现,如果s[left:right]是回文串,那么可以考虑将s[left:right]这个字符串分割出来,截止到下标right时,dp[right]的组成就是s[:left - 1]之内的分割数量,再加上s[left:right]这一次分割。
当然,随着left和right的不断外扩,也许我们能找到更长的回文串,这样也可能使得总的分割次数越来越少。
class Solution { public int minCut(String s) { char[] arr = s.toCharArray();
int len = s.length(); int[] dp = new int[len + 1]; Arrays.fill(dp, len - 1); dp[0] = 0;
// odd or even length for (int i = 1; i < len; i++) { updatePalindromeCut(arr, i, i, dp, len); updatePalindromeCut(arr, i - 1, i, dp, len); }
return dp[len - 1]; }
void updatePalindromeCut(char[] str, int left, int right, int[] dp, int len) { while (left >= 0 && right < len && str[left] == str[right]) {
dp[right] = left > 0 ? Math.min(dp[right], dp[left - 1] + 1) : Math.min(dp[right], 0);
left--; right++; } }}133. Clone Graph
https://leetcode.com/problems/clone-graph/
使用一个map,存储新旧节点之间的映射,遇到已经复制过的节点,直接从map中获取;复制一个节点时,新节点newNode直接用原来的值node.val初始化,而newNode的邻居则用深度优先思想,循环从node.neighbors里分别调用cloneGraph方法复制。
/*// Definition for a Node.class Node { public int val; public List<Node> neighbors; public Node() { val = 0; neighbors = new ArrayList<Node>(); } public Node(int _val) { val = _val; neighbors = new ArrayList<Node>(); } public Node(int _val, ArrayList<Node> _neighbors) { val = _val; neighbors = _neighbors; }}*/
class Solution { // map from old to new nodes private Map<Node, Node> map = new HashMap<Node, Node>();
public Node cloneGraph(Node node) { if (node == null) { return node; }
// current node has been cloned if (map.containsKey(node)) { return map.get(node); }
Node newNode = new Node(node.val, new ArrayList<Node>()); map.put(node, newNode);
for (int i = 0; i < node.neighbors.size(); i++) { Node clonedNeighbor = cloneGraph(node.neighbors.get(i)); newNode.neighbors.add(clonedNeighbor); }
return newNode; }}134. Gas Station
https://leetcode.com/problems/gas-station/
使用一个变量tankGas跟踪当前油箱中的油量。如果从第i个加油站开到第i+1个加油站的路上油箱里的油量变成负数,说明油不够用,只能换一个起点。
再使用一个变量totalGas计算整趟旅程下来加油 - 耗油的差值,总的耗油不能比加油量多。
class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { // total gas filled - total gas cost int totalGasDiff = 0;
// current gas in the tank int tankGas = 0;
// starting gas station's index int start = 0;
for (int i = 0; i < cost.length; i++) { // gas consumption amount from i-th station to (i+1)-th station int cur = gas[i] - cost[i];
// update total consumed gas totalGasDiff += cur;
// update gas in the tank // if tank drops below 0, we have to start from another station tankGas += cur; if (tankGas < 0) { start = i + 1; tankGas = 0; } }
return totalGasDiff < 0 ? -1 : start; }}135. Candy
https://leetcode.com/problems/candy/
解题流程分为三步:
- 初始分配给每个孩子一个糖
- 从左向右,确保位于右侧,
rating更高的孩子比左侧孩子获得更多的糖 - 从右向左,确保位于左侧,
rating更高的孩子比右侧孩子获得更多的糖。注意有可能在此前从左到右的分配中,num[i - 1]的值很大,因此我们需要比较num[i - 1]和num[i] + 1的值,选更大的那个值,确保不违背之前的分发方式。
最后只需要将结果累加即可。
class Solution { public int candy(int[] ratings) { int len = ratings.length; if (len == 0) { return 0; }
int result = 0;
int[] num = new int[len + 1]; Arrays.fill(num, 1);
// left to right, make sure that right child with higher rating receive more candy for (int i = 0; i < len - 1; i++) { if (ratings[i] < ratings[i + 1]) { num[i+1] = num[i] + 1; } }
// right to left, make sure that left child with higher rating receive more candy // we cannot break the previous left-to-right rule as well for (int i = len - 1; i > 0; i--) { if (ratings[i - 1] > ratings[i]) { num[i - 1] = Math.max(num[i - 1], num[i] + 1); } result += num[i]; }
result += num[0]; return result; }}136. Single Number
https://leetcode.com/problems/single-number/
任意两个相同的数,异或值为0。遍历整个数组并不断取异或,只有single number自身无法通过成对异或得到0,因此最后的结果就是single number。
class Solution { public int singleNumber(int[] nums) { int result = 0; for (int num : nums) { result ^= num; } return result; }}137. Single Number II
https://leetcode.com/problems/single-number-ii/
解决本题有2个前提:
- 遇到
num两次时,不应该记录这个数 - 遇到
num三次后,将num对结果的影响清除
结合上述前提以及上一题的解决方案,只需要在第一次和第三次遇到一个数时进行记录即可;并且,如果ones ^ num == 0,表示当前遇到了num。
由此我们拆分出两个核心步骤:
-
如果没有将
num记录在ones中,更新ones;否则不记录ones = ones ^ num & ~twos -
如果没有将
num记录在twos中,更新twos;否则将twos清零twos = twos ^ num & ~ones
class Solution { public int singleNumber(int[] nums) { int ones = 0, twos = 0; for (int num : nums) { ones = ones ^ num & ~twos; twos = twos ^ num & ~ones; } return ones; }}138. Copy List with Random Pointer
https://leetcode.com/problems/copy-list-with-random-pointer/
与此前的Clone Graph问题类似,仍然是一张map,记录当前复制的进度,遇到已经复制过的节点直接从map中获取结果;并且仍然是使用递归,优先完成后续节点的复制。
/*// Definition for a Node.class Node { int val; Node next; Node random;
public Node(int val) { this.val = val; this.next = null; this.random = null; }}*/
class Solution { // map from old to new nodes private Map<Node, Node> map = new HashMap<Node, Node>();
public Node copyRandomList(Node head) { if (head == null) { return null; }
// current node has been cloned if (map.containsKey(head)) { return map.get(head); }
// copy node Node newHead = new Node(0); newHead.val = head.val; map.put(head, newHead);
newHead.next = copyRandomList(head.next); newHead.random = copyRandomList(head.random);
return newHead; }}139. Word Break
https://leetcode.com/problems/word-break/
使用动态规划,dp[i]表示截止至第i个字符时,s的子字符串s[0:i]是否符合题意。
使用两层遍历,从第i-1个字母开始向前搜索,如果遇到子字符串s[j:i-1]在wordDict中,并且起始下标j处有dp[j]为true,则将dp[i]标记为true。可以限定向前搜索的长度(wordDict中最长单词的长度)来加快运行时间。
class Solution { public boolean wordBreak(String s, List<String> wordDict) { Set<String> dict = new HashSet<>(wordDict);
// maximum word length in wordDict int maxWordLen = 0; for (String each : dict) { maxWordLen = Math.max(maxWordLen, each.length()); }
boolean[] dp = new boolean[s.length() + 1]; dp[0] = true;
for (int i = 1; i <= s.length(); i++) { for (int j = i - 1; j >= Math.max(0, i - maxWordLen); j--) { if (dp[j] && dict.contains(s.substring(j, i))) { dp[i] = true; break; } } }
return dp[s.length()]; }}140. Word Break II
https://leetcode.com/problems/word-break-ii/
使用回溯算法,尝试每一个s[index:i]能不能组成一个存在于wordDict中的单词。
class Solution { public List<String> wordBreak(String s, List<String> wordDict) { List<String> result = new ArrayList<>(); backtrack(result, s, wordDict, 0, new ArrayList<>()); return result; }
private void backtrack(List<String> result, String s, List<String> wordDict, int index, List<String> temp) { if (index == s.length()) { result.add(String.join(" ", temp)); return; }
// try each s[index:i] for (int i = index; i <= s.length(); i++) { if (wordDict.contains(s.substring(index, i))) { temp.add(s.substring(index, i));
// start index should be i now backtrack(result, s, wordDict, i, temp);
temp.remove(temp.size() - 1); } } }}
// Time: O(2^n * n)// Space: O(n)141. Linked List Cycle
https://leetcode.com/problems/linked-list-cycle/
使用快慢指针,慢指针一次移动一格,快指针一次移动两格。如果存在环,快慢指针将在某个时刻相遇。
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */public class Solution { public boolean hasCycle(ListNode head) { if (head == null) { return false; } ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next;
// fast and slow pointers will eventually meet if there is a cycle if (slow == fast) { return true; } }
return false; }}142. Linked List Cycle II
https://leetcode.com/problems/linked-list-cycle-ii/
实际上这是一个数学问题。仍然使用上一题中的快慢指针方法。假设链表头距离环起始点的距离为A,环起始点距离快慢指针相遇点的距离为B,环的长度为N,则:
A + B + N = 2A + 2B (快指针比慢指针多走了一个环的距离)可以解得:
N - B = A即快慢指针相遇点距离链表终点的距离也为A,因此慢指针只需要再移动A+1距离,便能够回到环的起点。使用另一个指针slow2从头开始,与慢指针共同位移,二者相遇的位置就是环起点。
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */public class Solution { public ListNode detectCycle(ListNode head) { if (head == null) { return null; }
ListNode slow = head, fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next;
if (slow == fast) { ListNode slow2 = head; while (slow2 != slow) { slow2 = slow2.next; slow = slow.next; } return slow; } }
return null; }}143. Reorder List
https://leetcode.com/problems/reorder-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 void reorderList(ListNode head) { if (head == null || head.next == null) { return; }
// find middle element ListNode slow = head; ListNode fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; }
// reverse the second half of the list ListNode preMiddle = slow; ListNode preCurrent = slow.next; while (preCurrent.next != null) { ListNode current = preCurrent.next; preCurrent.next = current.next; current.next = preMiddle.next; preMiddle.next = current; }
// merge two sub-lists slow = head; fast = preMiddle.next; while (slow != preMiddle) { preMiddle.next = fast.next; fast.next = slow.next; slow.next = fast; slow = fast.next; fast = preMiddle.next; } }}144. Binary Tree Preorder Traversal
https://leetcode.com/problems/binary-tree-preorder-traversal/
第一种做法,迭代使用栈解决问题。注意由于栈先进后出,因此要先加入每个节点的右分支。
/** * 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> preorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); if (root == null) { return result; }
Stack<TreeNode> st = new Stack<>(); st.push(root); while (!st.isEmpty()) { TreeNode node = st.pop(); result.add(node.val);
if (node.right != null) { st.push(node.right); } if (node.left != null) { st.push(node.left); } }
return result; }}第二种做法,递归遍历:
/** * 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> preorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); preorder(result, root); return result; }
private void preorder(List<Integer> result, TreeNode node) { if (node == null) { return; }
result.add(node.val); preorder(result, node.left); preorder(result, node.right); }}145. Binary Tree Postorder Traversal
https://leetcode.com/problems/binary-tree-postorder-traversal/
第一种做法,仍然使用栈,调整st和result添加的顺序即可:
/** * 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> postorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); if (root == null) { return result; }
Stack<TreeNode> st = new Stack<>(); st.push(root); while (!st.isEmpty()) { TreeNode node = st.pop(); result.add(0, node.val);
if (node.left != null) { st.push(node.left); }
if (node.right != null) { st.push(node.right); } }
return result; }}第二种做法,递归遍历:
/** * 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> postorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); postorder(result, root); return result; }
private void postorder(List<Integer> result, TreeNode node) { if (node == null) { return; }
postorder(result, node.left); postorder(result, node.right); result.add(node.val); }}146. LRU Cache
https://leetcode.com/problems/lru-cache/
第一种是简便做法。LRU缓存是LinkedHashMap的一种应用。对一个键执行get/put操作后,对应的键值对会移动到链表末尾,因此最末尾的元素是最近访问的。在LinkedHashMap添加元素后,会调用removeEldestEntry()方法,传递的参数为最久没有被访问的键值对时,如果方法返回true,这个最久的键值对就会被删除。因此,LRUCache类只需在LinkedHashMap类的基础上进行继承扩展即可。
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) { super(capacity, 0.75F, true); this.capacity = capacity; }
public int get(int key) { return super.getOrDefault(key, -1); }
public void put(int key, int value) { super.put(key, value); }
@Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return size() > capacity; }}第二种做法,使用一个链表和一个数组。数组记录所有出现过的key与value对应的信息;而链表则负责维护最近使用过的节点情况,如果某个节点最近被使用过,就把它先从链表中删掉,再把它加到链表的尾部。这样能确保链表始终是按照最近使用时间由远到近进行排序,如果要替换节点直接从链表头选择节点替换即可。
class Node { Node next, prev; int key, val; Node(int key, int val) { this.key = key; this.val = val; }}
class LRUCache { Node map[]; Node head, tail; int CAP, SIZE;
public LRUCache(int capacity) { CAP = capacity; SIZE = 0; map = new Node[10001]; // default max capacity is 10000 head = tail = null; }
private void add(Node n) { // first node, set it as both head and tail if (head == null) { head = tail = n; return; }
// add to tail tail.next = n; n.prev = tail; tail = n; }
private void remove(Node n) { if (head == tail) { // remove the only node head = tail = null; } else if (head == n) { // remove head node head = head.next; } else if (tail == n) { // remove tail node tail = tail.prev; } else { // find previous and next nodes, then connect them Node prev = n.prev, next = n.next; prev.next = next; next.prev = prev; } }
public int get(int key) { if (map[key] == null) { return -1; }
// recently used, update it's position in linked list remove(map[key]); add(map[key]);
return map[key].val; }
public void put(int key, int value) { // update the value of the key, if the key exists if (map[key] != null) { remove(map[key]); add(map[key]); map[key].val = value; return; }
// need to evict a node and replace with current key if (SIZE >= CAP) { Node nodeToRemove = head; remove(nodeToRemove); map[nodeToRemove.key] = null; SIZE--; }
Node newNode = new Node(key, value); add(newNode); map[key] = newNode; SIZE++; }}147. Insertion Sort List
https://leetcode.com/problems/insertion-sort-list/
使用三个指针,其中pre总是从链表头开始向后移动,寻找插入位置;cur指向当前待插入的元素;next指向下一个待插入的元素。
/** * 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 insertionSortList(ListNode head) { if (head == null) { return head; }
ListNode dummyHead = new ListNode(0);
// current node to be inserted ListNode cur = head;
// pre -> cur -> next ListNode pre = dummyHead, next = null;
while (cur != null) { next = cur.next;
// find the first element which breaks the ascending order // this is the insertion position while (pre.next != null && pre.next.val < cur.val) { pre = pre.next; }
cur.next = pre.next; pre.next = cur; cur = next;
// reset pre pre = dummyHead; }
return dummyHead.next; }}148. Sort List
https://leetcode.com/problems/sort-list/
Divide and conquer,不断将链表二分为左、右两部分,按顺序合并两个子链表,组成一个完整的有序链表。
/** * 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 sortList(ListNode head) { if (head == null || head.next == null) { return head; }
// divide the list into two half ListNode pre = null, slow = head, fast = head; while (fast != null && fast.next != null) { pre = slow; slow = slow.next; fast = fast.next.next; } pre.next = null;
ListNode l1 = sortList(head); ListNode l2 = sortList(slow);
return mergeTwoLists(l1, l2); }
private ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } if (l2 == null) { return l1; }
// merge after smaller node if (l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); return l1; } else { l2.next = mergeTwoLists(l1, l2.next); return l2; } }}149. Max Points on a Line
https://leetcode.com/problems/max-points-on-a-line/
三重循环,如果两点重合,则寻找所有的重合点;否则,计算出两点连线的斜率,并遍历其他点,寻找与当前点连线斜率相同的点。每一轮循环结束后的结果是斜率相同 + 相同点的总个数。注意,如果全部点都相同,还需要在结尾进行额外判断,决定samepoint值是否需要 + 1。
class Solution { public int maxPoints(int[][] points) { if (points == null) { return 0; } if (points.length < 3) { return points.length; }
int result = 0; int n = points.length;
for (int i = 0; i < n; i++) { int samepoint = 0;
for (int j = i+1; j < n; j++) { int count = 2; long x = points[j][0] - points[i][0]; long y = points[j][1] - points[i][1];
boolean isValidSlope = true; if (x == 0 && y == 0) { samepoint++; continue; }
if (x == 0) { isValidSlope = false; }
long interdx = x * points[i][1] - y * points[i][0];
// slope exists and equal inter multiplication, or slope not exist and same point for (int k = j+1; k < n; k++) { if (isValidSlope) { if (interdx == x*points[k][1] - y*points[k][0]) { count++; } } else if (points[k][0] == points[i][0]) { count++; } }
result = Math.max(count + samepoint, result); }
result = Math.max(samepoint + 1, result); // [[1,1],[1,1],[1,1]] }
return result; }}150. Evaluate Reverse Polish Notation
https://leetcode.com/problems/evaluate-reverse-polish-notation/
逆波兰式实际对应的就是树的后序遍历,根据后续遍历的顺序,我们会先遇到操作数a,再遇到操作数b,之后才是操作符。因此当我们遇到操作数的时候,直接推入栈中;遇到操作符时,从栈顶推出的操作数是先b再a,这个顺序在减法和除法中很重要。
class Solution { public int evalRPN(String[] tokens) { int a, b; Stack<Integer> st = new Stack<Integer>();
for (String s : tokens) { if (s.equals("+")) { st.add(st.pop() + st.pop()); } else if (s.equals("-")) { b = st.pop(); a = st.pop(); st.add(a - b); } else if (s.equals("*")) { st.add(st.pop() * st.pop()); } else if (s.equals("/")) { b = st.pop(); a = st.pop(); st.add(a / b); } else { st.add(Integer.parseInt(s)); } }
return st.pop(); }}151. Reverse Words in a String
https://leetcode.com/problems/reverse-words-in-a-string/
使用trim()函数除去起始的空白,再使用split()函数分割每个单词,汇集成一个单词数组。之后倒序输出所有单词,并添加空格。
class Solution { public String reverseWords(String s) { StringBuilder sb = new StringBuilder();
// trim preceding & leading whitespaces, then split by " " String[] words = s.trim().split(" ");
for (int i = words.length - 1; i >= 0; i--) { if (words[i].length() > 0) { sb.append(words[i]); if (i > 0) { sb.append(" "); } } }
return sb.toString(); }}152. Maximum Product Subarray
https://leetcode.com/problems/maximum-product-subarray/
从左向右遍历一遍,不断累乘,如果当前乘积为0需要重新修改为1。
考虑到存在负数的情况(例如[3, -1, 4]),可能会导致数组被负数分割成若干部分,因此从左到右不一定能找到最大子区间。因此我们还需要从右向左再遍历一次,确保筛选出最大子序列。
class Solution { public int maxProduct(int[] nums) { if (nums.length == 0) { return 0; }
int result = Integer.MIN_VALUE, product = 1; for (int i = 0; i < nums.length; i++) { product *= nums[i]; result = Math.max(result, product); if (product == 0) { product = 1; } }
product = 1; for (int i = nums.length - 1; i >= 0; i--) { product *= nums[i];
result = Math.max(result, product); if (product == 0) { product = 1; } }
return result; }}153. Find Minimum in Rotated Sorted Array
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
这题借鉴了此前Search in Rotated Sorted Array的数组对半划分思想,通过比较nums[left]、nums[mid]和nums[right]确定下一轮的搜索区间。
如果nums[mid]不小于边界两个值,说明nums[left, mid]是一段递增序列,最小值在nums[mid + 1, right]中;反之,最小值在nums[left, mid]中。因为我们希望找到数组最小值,根据左边界查找思想,在确定最小值所在范围后我们仍然希望尽量向左扩展范围,试图能在范围中找到更小的值符合要求。
注意这题还可以用辅助加速的手段快速检查nums[mid]值是否为最小值,提升运行效率。
class Solution { public int findMin(int[] nums) { if (nums == null || nums.length == 0) { return 0; }
if (nums.length == 1) { return nums[0]; }
int left = 0, right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2;
// optional speed-up, quickly determine if nums[mid] is minimum if (mid > 0 && nums[mid] < nums[mid - 1]) { return nums[mid]; }
if (nums[left] <= nums[mid] && nums[right] < nums[mid]) { left = mid + 1; } else { right = mid; } }
return nums[left]; }}154. Find Minimum in Rotated Sorted Array II
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/
最小值初始情况下在肯定在nums[0, nums.length - 1]之间。分类讨论:
- 如果
nums[mid] > nums[right],最小值在nums[mid + 1, right]之间,因此有left = mid + 1 - 如果
nums[mid] < nums[left],最小值在nums[left + 1, mid]之间,因此有right = mid;同时我们也知道nums[left]不可能是最小值,因此让left++稍微加快一些运行效率 - 其他情况下,对应
nums[left] <= nums[mid] <= nums[right],我们只知道最小值为nums[left],不知道nums[mid]和两端的关系,因此只能让right--
完成二分查找循环后,left与right相等,返回nums[left]即可。
class Solution { public int findMin(int[] nums) { if (nums == null || nums.length == 0) { return 0; }
if (nums.length == 1) { return nums[0]; }
int left = 0, right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2;
if (nums[right] < nums[mid]) { left = mid + 1; } else if (nums[mid] < nums[left]) { right = mid; left++; } else { right--; } }
return nums[left]; }}155. Min Stack
https://leetcode.com/problems/min-stack/
主体可以构造一个链表存储。对于最小值的跟踪,可以在push时顺便完成,直接和当前头元素中存储的已有最小值比较,节省搜索的时间。
class MinStack {
private class Node { int val; int min; Node next;
private Node(int val, int min) { this(val, min, null); }
private Node(int val, int min, Node next) { this.val = val; this.min = min; this.next = next; } }
private Node head;
/** initialize your data structure here. */ public MinStack() {
}
public void push(int x) { if (head == null) { head = new Node(x, x); } else { // store global min value into each incoming node head = new Node(x, Math.min(x, head.min), head); } }
public void pop() { head = head.next; }
public int top() { return head.val; }
public int getMin() { return head.min; }}156. Binary Tree Upside Down
https://leetcode.com/problems/binary-tree-upside-down/
可以将最左支(1-2-4)视为树的骨干,其余节点视为枝叶。参考XYZ例图,旋转后骨干变成最右支,枝叶则位于左侧。
/** * 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 upsideDownBinaryTree(TreeNode root) { if (root == null || root.left == null) { return root; }
TreeNode newRoot = upsideDownBinaryTree(root.left);
// Y -> Z root.left.left = root.right;
// Y -> X root.left.right = root;
// root has become a leaf now root.left = root.right = null;
return newRoot; }}157. Read N Characters Given Read4
https://leetcode.com/problems/read-n-characters-given-read4/
问题限制只能使用read4()函数来实现read()函数。read4()函数限制了一次性读入的字符最多为4,因此read()函数必须不断调用read4(),将字符从read4()专用的缓存read4TempBuff中拷贝到自己的缓存buf内。
/** * The read4 API is defined in the parent class Reader4. * int read4(char[] buf4); */
public class Solution extends Reader4 {
// the buffer for read4 API private char[] read4TempBuff = new char[4];
/** * @param buf Destination buffer * @param n Number of characters to read * @return The number of actual characters read */ public int read(char[] buf, int n) {
// how many characters have been read int result = 0;
// read 4 characters to buff int bytes = read4(read4TempBuff);
while (bytes > 0) { for (int i = 0; i < bytes; i++) { if (n == result) { break; }
// copy 1 char buf[result] = read4TempBuff[i]; result++; } bytes = read4(read4TempBuff); }
return result; }}158. Read N Characters Given read4 II - Call Multiple Times
https://leetcode.com/problems/read-n-characters-given-read4-ii-call-multiple-times/
由于read()可能被调用多次,因此需要及时更新缓存指针,每次read4()读字符进来,就将字符逐个从read4TempBuff拷贝到buf,复制完一个就移动一次缓存指针。4个都复制完了,重置缓存指针到0,只有为0的时候才可以接受下一批读入的字符。
/** * The read4 API is defined in the parent class Reader4. * int read4(char[] buf4); */
public class Solution extends Reader4 {
private int buffPtr = 0; private int bytes = 0; // characters length read to buffer
private char[] read4TempBuff = new char[4];
/** * @param buf Destination buffer * @param n Number of characters to read * @return The number of actual characters read */ public int read(char[] buf, int n) {
// how many characters have been read int result = 0;
while (result < n) { // write buff when buffer is ready if (buffPtr == 0) { bytes = read4(read4TempBuff); }
// copy chars from read4TempBuff to buf while (result < n && buffPtr < bytes) { buf[result] = read4TempBuff[buffPtr]; result++; buffPtr++; }
// finish copying current 4 chars, reset to 0, ready to receive new chars if (buffPtr == bytes) { buffPtr = 0; }
// read4() return a number less than 4, EOF if (bytes < 4) { break; } }
return result; }}159. Longest Substring with At Most Two Distinct Characters
https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/
滑动窗口思想,使用一个map存储每个字符出现的次数;使用双指针i, j记录当前遍历输入字符串的前、后字符,确保map中不超过2个字符。一旦超过,就需要从前指针开始剔除一个老字符。
class Solution { public int lengthOfLongestSubstringTwoDistinct(String s) { if (s == null || s.length() == 0) { return 0; }
char[] arr = s.toCharArray();
// <Character, Integer> int[] map = new int[256];
// how many distinct characters in current substring int count = 0;
// longest substring length int result = 0;
for (int i = 0, j = 0; j < arr.length; j++) { // a new character if (map[ arr[j] ] == 0) { count++; }
map[ arr[j] ]++;
// more than 2 characters in current substring, delete one character while (count > 2) { map[ arr[i] ]--;
// delete one if (map[ arr[i] ] == 0) { count--; }
i++; }
result = Math.max(result, j - i + 1); }
return result; }}160. Intersection of Two Linked Lists
https://leetcode.com/problems/intersection-of-two-linked-lists/
无需计算A、B链表的长度,直接分别从头到尾进行遍历,如果遇到结尾,跳转至另一链表的头部继续遍历。这样一来,如果两个链表有交集,遍历指针一定会在交集点相遇;否则,同时移动至链表结尾,返回null。
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { return null; }
ListNode curA = headA, curB = headB; while (curA != curB) { curA = (curA == null) ? headB : curA.next; curB = (curB == null) ? headA : curB.next; }
return curA; }}161. One Edit Distance
https://leetcode.com/problems/one-edit-distance/
要想在一次编辑内实现s, t两个字符串的相等,必须只有一个字符在下标i处不同,并且
- 如果
s,t等长,下标i之后s,t仍旧要相等 s比t短,则t删掉t[i]之后剩余字符串和s从s[i]起的字符串相等t比s短,与上一种情况对应
最后还有一种特殊情况,即最后一个字符才出现不同,需要判断两个字符串的长度差值是否为1。
class Solution { public boolean isOneEditDistance(String s, String t) { for (int i = 0; i < Math.min(s.length(), t.length()); i++) { if (s.charAt(i) != t.charAt(i)) { if (s.length() == t.length()) { return s.substring(i + 1).equals(t.substring(i + 1)); } else if (s.length() < t.length()) { return s.substring(i).equals(t.substring(i + 1)); } else { return s.substring(i + 1).equals(t.substring(i)); } } }
return Math.abs(s.length() - t.length()) == 1; }}162. Find Peak Element
https://leetcode.com/problems/find-peak-element/
使用二分查找,找到任意一个极大值便返回。
class Solution { public int findPeakElement(int[] nums) { int left = 0, right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2;
// find a local maximum if (nums[mid] < nums[mid + 1]) { left = mid + 1; } else { right = mid; } }
return left; }}163. Missing Ranges
https://leetcode.com/problems/missing-ranges/
三步检查:
- 判断
lower和nums的第一个数之间的关系 - 判断每两个数之间是否产生了大于1的间隔(生成缺失区间)
- 判断
upper和nums的最后一个数之间的关系
class Solution { public List<String> findMissingRanges(int[] nums, int lower, int upper) { List<String> result = new ArrayList<>();
if (nums == null || nums.length == 0) { result.add(formRange(lower, upper)); // generate the whole range return result; }
// initial gap if (nums[0] > lower) { result.add(formRange(lower,nums[0] - 1)); }
// find a gap greeater than 1 for (int i = 0; i < nums.length - 1; i++) { if (nums[i+1] != nums[i] && nums[i+1] > nums[i] + 1) { result.add(formRange(nums[i] + 1, nums[i+1] - 1)); } }
// last gap if (nums[nums.length-1] < upper) { result.add(formRange(nums[nums.length-1] + 1, upper)); }
return result; }
public String formRange(int low, int high) { return low == high ? String.valueOf(low) : (low + "->" + high); }}164. Maximum Gap
https://leetcode.com/problems/maximum-gap/
先遍历一次数组,得出数组的最小值min和最大值max;将数组划分为若干个桶,每个桶覆盖一段区间[min + (k-1)gap, min + k*gap)。对于每个桶,重点记录桶内的最小值和最大值,最后比较不同桶之间最值之间的间距。
class Solution { public int maximumGap(int[] nums) { if (nums == null || nums.length < 2) { return 0; }
// find minimum and maximum value of nums int min = nums[0], max = nums[0]; for (int n: nums) { min = Math.min(min, n); max = Math.max(max, n); }
if (min == max) { return 0; }
int n = nums.length;
// each bucket is an interval,[min + (k-1)gap, min + k*gap) int gap = (int)Math.ceil((double)(max - min) / (n - 1)); // max gap must be greater than this int bucketMin[] = new int[n]; int bucketMax[] = new int[n]; Arrays.fill(bucketMin, Integer.MAX_VALUE); Arrays.fill(bucketMax, Integer.MIN_VALUE);
// put current number in the bucket and update this bucket's min/max value for (int num: nums) { int i = (num - min) / gap; bucketMin[i] = Math.min(bucketMin[i], num); bucketMax[i] = Math.max(bucketMax[i], num); }
// for the first bucket, compare bucket's min value and num's min value // for other buckets, compare this bucket's min value and previous bucket's max value for (int i = 0; i < n; i++) { // only compare when the bucket is not empty if (bucketMin[i] != Integer.MAX_VALUE) { gap = Math.max(gap, bucketMin[i] - min); min = bucketMax[i]; } }
return gap; }}165. Compare Version Numbers
https://leetcode.com/problems/compare-version-numbers/
使用split()函数,根据'.'提取出每个子版本号进行比较。可以调用parseInt()函数转化格式。比较过程中,如果较短的版本号已经遍历完毕,之后的子版本号可以看作都是0。
class Solution { public int compareVersion(String version1, String version2) { String[] s1 = version1.split("\\."); String[] s2 = version2.split("\\.");
for (int i = 0; i < Math.max(s1.length, s2.length); i++) { // if version number too long, treat as 0 Integer v1 = i < s1.length ? Integer.parseInt(s1[i]) : 0; Integer v2 = i < s2.length ? Integer.parseInt(s2[i]) : 0;
if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } }
return 0; }}166. Fraction to Recurring Decimal
https://leetcode.com/problems/fraction-to-recurring-decimal/
解题步骤可划分为:
- 确定正负号
- 将int型输入变量转化为long型,避免溢出
- 小数部分,不断倍增
numerator,每循环做一次除法,便用map存储当前的余数以及长度。如果当前余数已经在map中,则终止循环
class Solution { public String fractionToDecimal(int numerator, int denominator) { if (numerator == 0) { return "0"; }
StringBuffer sb = new StringBuffer();
// determine sign if ((numerator > 0) ^ (denominator > 0)) { sb.append('-'); }
long num = Math.abs((long) numerator); long den = Math.abs((long) denominator);
sb.append(num / den); num %= den; if (num == 0) { return sb.toString(); } sb.append('.');
// <recurring decimal, recurring string length> HashMap<Long, Integer> map = new HashMap<>(); while (num != 0) { num *= 10;
// detect recurring part if (map.containsKey(num)) { sb.insert((int) map.get(num), '('); sb.append(')'); return sb.toString(); }
map.put(num, sb.length()); sb.append(num / den); num %= den; }
return sb.toString(); }}167. Two Sum II - Input Array Is Sorted
https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
左右双指针法,遍历解决。
class Solution { public int[] twoSum(int[] numbers, int target) { int len = numbers.length;
int[] result = new int[2]; if (numbers == null || numbers.length < 2) { return result; }
int index1 = 0, index2 = len-1; while (index1 < index2) { if (numbers[index1] + numbers[index2] == target) { result[0] = index1 + 1; result[1] = index2 + 1; return result; } else if (numbers[index1] + numbers[index2] < target) { index1++; } else { index2--; } }
return result; }}168. Excel Sheet Column Title
https://leetcode.com/problems/excel-sheet-column-title/
相当于10进制转特殊的26进制,按位倒序添加每一位数。
class Solution { public String convertToTitle(int columnNumber) { StringBuilder result = new StringBuilder();
while (columnNumber > 0) { columnNumber--; result.insert(0, (char)('A' + columnNumber % 26)); columnNumber /= 26; }
return result.toString(); }}169. Majority Element
https://leetcode.com/problems/majority-element/
运用Boyer-Moore投票算法,遍历整个数组,判断当前数nums[i]是否和当前的candidate相等,进而加减投票。由于输入数据中一定存在一个出现次数超过一半的元素,所以遍历完数组后数组中一定会存在至少一个元素,即majority element。
class Solution { public int majorityElement(int[] nums) { // current candicate and its occurrence int count = 0, candidate = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) { // set as current new candidate if (count == 0) { candidate = nums[i]; }
// "vote" for current candidate, or "vote" for others if (nums[i] == candidate) { count++; } else { count--; } }
return candidate; }}170. Two Sum III - Data structure design
https://leetcode.com/problems/two-sum-iii-data-structure-design/
用ArrayList存储每个添加的数字;使用HashMap存储每个数字出现的次数。每次添加一个数字后,就立即更新当前list内的最小值和最大值,这样能在find()时提前判定不合理范围,节省时间。
class TwoSum {
// all numbers private List<Integer> list = new ArrayList<>();
// every number's occurrence private Map<Integer, Integer> map = new HashMap<>();
// record max and minimum value int max, min;
/** Initialize your data structure here. */ public TwoSum() {}
/** Add the number to an internal data structure.. */ public void add(int number) { list.add(number);
min = Math.min(number, min); max = Math.max(number, max);
map.put(number, map.getOrDefault(number, 0) + 1); }
/** Find if there exists any pair of numbers which sum is equal to the value. */ public boolean find(int value) { if (value < 2 * min || value > 2 * max) { return false; }
for (int num : list) { int num2 = value - num;
if ((num == num2 && map.getOrDefault(num, 0) > 1) || (num != num2 && map.containsKey(num2))) { return true; } }
return false; }}171. Excel Sheet Column Number
https://leetcode.com/problems/excel-sheet-column-number/
遍历每个字符,进行26进制 -> 10进制的转换。
class Solution { public int titleToNumber(String s) { if (s.length() < 1) { return 0; }
int result = 0; for (int i = 0; i < s.length(); i++) { result = result * 26 + (s.charAt(i) - 'A' + 1); }
return result; }}172. Factorial Trailing Zeroes
https://leetcode.com/problems/factorial-trailing-zeroes/
阶乘中的10,本质上来源于5这个因子(5*2)。考虑到每个偶数都能提供一个2作为因子,因此2因子的数量一定比5因子多,尾数0的个数只取决于5的个数。
class Solution { public int trailingZeroes(int n) { int result = 0;
while (n > 0) { result += n / 5; n /= 5; }
return result; }}173. Binary Search Tree Iterator
https://leetcode.com/problems/binary-search-tree-iterator/
根据BST的特点,观察如果按照中序遍历从左到右遍历整颗BST,将得到一个有序的list。每次调用next()函数获取当前树中最小值时,其实就是获取list当前的头元素。不断使用poll()函数清空list,最终list为空时,hasNext()便返回false。
/** * 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 BSTIterator {
LinkedList<Integer> tree = new LinkedList<>();
public BSTIterator(TreeNode root) { visit(root); }
void visit(TreeNode root) { if (root == null) { return; }
// in-order visit(root.left); tree.offer(root.val); visit(root.right); }
/** @return the next smallest number */ public int next() { return tree.poll(); }
/** @return whether we have a next smallest number */ public boolean hasNext() { return !tree.isEmpty(); }}174. Dungeon Game
https://leetcode.com/problems/dungeon-game/
动态规划,从终点向起点递推。如果当前房间可以加血,并且回复的血量比相邻房间所消耗的最低血量还多,说明只需要1滴血就能进入当前的回血房间,并且继续进入下一个房间;否则,所需的最小血量就要加上当前房间所扣除的血量。
class Solution { public int calculateMinimumHP(int[][] dungeon) { int m = dungeon.length; int n = dungeon[0].length; int[][] dp = new int[m+1][n+1];
// unreachable bound for (int i = 0; i <= m; i++) { dp[i][n] = Integer.MAX_VALUE; } for (int i = 0; i <= n; i++) { dp[m][i] = Integer.MAX_VALUE; }
// before finally enter the bottom-right room, the knight should have at least 1 health dp[m][n-1] = 1; dp[m-1][n] = 1;
for (int i = m - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { int minToNext = Math.min(dp[i][j+1], dp[i+1][j]); dp[i][j] = (dungeon[i][j] >= minToNext) ? 1 : minToNext - dungeon[i][j]; } }
return dp[0][0]; }}175. Combine Two Tables
https://leetcode.com/problems/combine-two-tables/
两个表的PersonId字段可以充当桥梁。另外注意到即使Address表缺乏记录,也要返回Person表中的对应人物数据,因此需要用到LEFT JOIN。
SELECT FirstName, LastName, City, StateFROM Person LEFT JOIN AddressON Person.PersonId = Address.PersonId;176. Second Highest Salary
https://leetcode.com/problems/second-highest-salary/
从高到低排序后,获取第二大的结果。
SELECT (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1 /* LIMIT: number of rows returned */ OFFSET 1 /* OFFSET:starting row index */ )AS SecondHighestSalary177. Nth Highest Salary
https://leetcode.com/problems/nth-highest-salary/
与上一题类似,仍然是从高到低排序,只不过OFFSET变成了N - 1。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INTBEGIN DECLARE M INT; SET M = N - 1; RETURN ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1 OFFSET M );END178. Rank Scores
https://leetcode.com/problems/rank-scores/
第一种方法,对于当前分数,不断计算出大于等于当前分数的数量,这个数量对应排名。
SELECT Score, (SELECT COUNT(*) FROM (SELECT DISTINCT Score s FROM Scores) AS tmp /* greater or equal to current score */ WHERE s >= Score) AS 'Rank'FROM ScoresORDER BY Score DESC第二种方法,可以直接使用DENSE_RANK()函数。
SELECT Score, DENSE_RANK() OVER (ORDER BY Score DESC) AS 'Rank'FROM Scores179. Largest Number
https://leetcode.com/problems/largest-number/
重定义Arrays.sort()的Comparator。如果Comparator调用的compare(obj param1, obj param2)函数返回结果大于0,说明排序时param1位于param2之后,达到升序的效果;等于0和小于0的情况以此类推。
为了排除字典序首字符的干扰,需要将两个待比较字符串参数s1,s2合并,即比较s1+s2和s2+s1。如果s2+s1“大于”s1+s2,排序的结果中s2将前于s1。
class Solution { public String largestNumber(int[] nums) { if (nums == null || nums.length == 0) { return ""; }
String[] strs = new String[nums.length]; for (int i = 0; i < nums.length; i++) { strs[i] = String.valueOf(nums[i]); }
// sort from the largest num to the smallest Arrays.sort(strs, (String s1, String s2) -> { return (s2 + s1).compareTo(s1 + s2); });
// starting digit is 0, then it must be 0 if (strs[0].charAt(0) == '0') { return "0"; }
StringBuilder sb = new StringBuilder(); for (String str : strs) { sb.append(str); }
return sb.toString(); }}180. Consecutive Numbers
https://leetcode.com/problems/consecutive-numbers/
自连接三遍,比较三张表是否有连续的三个ID对应的数字相等。
SELECT DISTINCT l1.Num AS ConsecutiveNumsFROM Logs l1, Logs l2, Logs l3WHERE l1.Id = l2.Id - 1 AND l2.Id = l3.Id - 1 AND l1.Num = l2.Num AND l2.Num = l3.Num181. Employees Earning More Than Their Managers
https://leetcode.com/problems/employees-earning-more-than-their-managers/
进行一次左连接,左表是Employee表自身,右表选出Id和Salary即可。左表的managerId需要和右表的id相等,并且左表的工资比右表高。
SELECT E1.Name AS EmployeeFROM Employee E1 LEFT JOIN ( SELECT Id, Salary FROM Employee GROUP BY Id) E2ON E1.ManagerId = E2.IdWHERE E1.Salary > E2.Salary;182. Duplicate Emails
https://leetcode.com/problems/duplicate-emails/
使用COUNT()函数,找出出现次数不止1次的邮箱。注意需要先GROUP BY Email,才能统计次数。
SELECT EmailFROM PersonGROUP BY EmailHAVING COUNT(*) > 1183. Customers Who Never Order
https://leetcode.com/problems/customers-who-never-order/
从Customers表中选出符合条件的顾客,这些顾客的ID没有出现在Orders表中。
SELECT Name AS CustomersFROM CustomersWHERE Id NOT IN ( SELECT DISTINCT(CustomerId) FROM Orders)184. Department Highest Salary
https://leetcode.com/problems/department-highest-salary/
用一个子查询,按照每个部门选出最高的工资,得到若干(DepartmentId, Salary)组合;再遍历Employee表,找到匹配的记录。
SELECT D.Name AS Department, E.Name AS Employee, E.SalaryFROM Employee E, Department DWHERE E.DepartmentId = D.Id AND (E.DepartmentId, E.Salary) IN ( SELECT DepartmentId, max(Salary) AS max FROM Employee GROUP BY DepartmentId)185. Department Top Three Salaries
https://leetcode.com/problems/department-top-three-salaries/
与上一题思路类似,仍然使用子查询。
SELECT D.name Department, E.name Employee, E.salary SalaryFROM Employee E, Department DWHERE E.DepartmentId = d.Id AND ( SELECT COUNT(*) FROM (SELECT DISTINCT Salary, DepartmentId FROM Employee) T WHERE T.DepartmentId = E.DepartmentId AND T.Salary >= E.Salary ) <= 3ORDER BY D.name, E.salary desc186. Reverse Words in a String II
https://leetcode.com/problems/reverse-words-in-a-string-ii/
先颠倒整个字符串,之后利用空格或是字符串终点,判定当前单词的范围,再颠倒每一个单词。
class Solution { public void reverseWords(char[] s) { if (s == null || s.length <= 1) { return; }
reverse(s, 0, s.length - 1);
for (int i = 0, j = 0; i <= s.length; i++) { if (i == s.length || s[i] == ' ') { // reverse current word reverse(s, j, i - 1);
// set j to next word's start j = i + 1; } } }
// reverse characters in [begin, end] private void reverse(char[] s, int begin, int end) { while (begin < end) { char c = s[begin]; s[begin] = s[end]; s[end] = c;
begin++; end--; } }}187. Repeated DNA Sequences
https://leetcode.com/problems/repeated-dna-sequences/
使用两个集合seen和repeated。每10个字符作为一个字符串,加入到seen中,如果seen加不进去,说明已经重复出现过了,加到repeated去。最后将repeated转为list即可。
class Solution { public List<String> findRepeatedDnaSequences(String s) { Set seen = new HashSet(), repeated = new HashSet();
for (int i = 0; i < s.length() - 9; i++) { String cur = s.substring(i, i + 10);
if (!seen.add(cur)) { repeated.add(cur); } }
return new ArrayList(repeated); }}188. Best Time to Buy and Sell Stock IV
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/
当输入的k很大,超过prices的长度时,无法用完这么多次交易的次数,因此转化为最简单的情况:只要有钱赚,当天买第二天就卖。
k不够大时,设立两个数组buy和sell,其中buy[i]表示第i次买入后用户剩余多少钱,sell[i]表示第i次卖出后用户剩余多少钱。显然有:
buy[i]= 上一次卖出后的钱 - 当前股票价格sell[i]= 当前买入之前的钱 + 当前股票价格
视情况决定是否有必要进行交易。结果关注sell[k]即可。
class Solution { public int maxProfit(int k, int[] prices) { if (k == 0 || prices.length <= 1) { return 0; } int len = prices.length;
// k is very large, let's become greedy if (k >= len - 1) { int result = 0; for (int i = 1; i < len; i++) { if (prices[i] > prices[i - 1]) { result += prices[i] - prices[i - 1]; } }
return result; }
int[] buy = new int[k + 1]; int[] sell = new int[k + 1]; Arrays.fill(buy, Integer.MIN_VALUE); Arrays.fill(sell, 0);
for (Integer cur : prices) { for (int i = 1; i <= k; i++) { buy[i] = Math.max(sell[i - 1] - cur, buy[i]); sell[i] = Math.max(buy[i] + cur, sell[i]); } }
return sell[k]; }}189. Rotate Array
https://leetcode.com/problems/rotate-array/
进行三次数组倒序:
- 整个数组倒序
- 前
k个数倒序 - 剩余数倒序
class Solution { public void rotate(int[] nums, int k) { if (nums == null || nums.length <= 1 || k < 1) { return; } int len = nums.length; if (k > len) { k %= len; }
reverse(nums, 0, len - 1); reverse(nums, 0, k - 1); reverse(nums, k, len - 1); }
private void reverse(int[] nums, int start, int end) { while (start < end) { int temp = nums[start]; nums[start] = nums[end]; nums[end] = temp;
start++; end--; } }}190. Reverse Bits
https://leetcode.com/problems/reverse-bits/
解题步骤分为3步:
result左移1位,空出最右的一位- 判断当前
n的最后一位是否为1,如果是1,result也要加1。因为此前result的最后一位肯定是0,所以这一步能保证目前result的最后一位和n的最后一位是同步的 n右移一位,表示完成了一位的处理。
循环32次,便可完成逐位转化。
public class Solution { // you need treat n as an unsigned value public int reverseBits(int n) { int result = 0;
for (int i = 0; i < 32; i++) { result <<= 1;
// add 1 to result if the last bif of n is 1 result += (n & 1);
// logic right shift, always complement 0 n >>>= 1; }
return result; }}191. Number of 1 Bits
https://leetcode.com/problems/number-of-1-bits/
利用n & 1提取最后一位的值,再使用右移推进至前一位。
public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int result = 0;
while (n != 0) { result += (n & 1); n >>>= 1; }
return result; }}192. Word Frequency
https://leetcode.com/problems/word-frequency/
bash部分命令:
cat: 查看某文件的内容tr: 删除文件中控制字符或进行字符转换。-s参数表示删除所有重复出现字符序列,只保留第一个sort: 将文本文件内容加以排序。-r参数表示以相反顺序排序uniq: 检查及删除文本文件中重复出现的行列。-c参数表示在每列旁边显示该行重复出现的次数。awk: 文本格式化命令。awk '{print $2}'表示打印第二个字段。
cat words.txt | tr -s ' ' '\n' | sort | uniq -c | sort -r | awk '{ print $2, $1 }'193. Valid Phone Numbers
https://leetcode.com/problems/valid-phone-numbers/
grep / egrep是Linux中的文本搜索工具,运用正则表达式搜索符合指定格式的字符串。
egrep "^((\([0-9]{3}\) )|([0-9]{3}-))[0-9]{3}-[0-9]{4}$" file.txt194. Transpose File
https://leetcode.com/problems/transpose-file/
awk命令中,
NF记录域个数,此题中可以理解为文件的行数NR表示已经读取的记录数,此题可以理解为已经读取的文件行数
同时在bash中,字符串的追加非常容易,例如s = s a表示将字符串a追加于s之后。完成awk后,还有一个END部分用于处理输出。其余语法与C语言近似。
awk '{ for (i = 1; i <= NF; i++) { if (NR == 1) result[i] = $i; else result[i] = result[i] " " $i }}END { for (i = 1; result[i] != ""; i++) print result[i];}' file.txt195. Tenth Line
https://leetcode.com/problems/tenth-line/
一种方法是使用cat和awk的组合命令:
cat file.txt | awk 'NR == 10'另一种方法是使用sed命令。sed可依照脚本的指令来处理、编辑文本文件,单位是行。
sed -n 10p file.txt其中,-n表示仅显示处理后的结果,p表示打印某个选择的数据。
196. Delete Duplicate Emails
https://leetcode.com/problems/delete-duplicate-emails/
将相同的邮件地址记录group by合并,只选出每一条合并记录的最小id,其余的id舍弃。
DELETE FROM PersonWHERE Id NOT IN ( SELECT A.Id FROM ( SELECT Min(Id) AS Id FROM Person GROUP BY Email ) AS A)197. Rising Temperature
https://leetcode.com/problems/rising-temperature/
使用TO_DAYS()函数转化DATE形式的变量,这样一来可以进行日期的比较。
SELECT W2.IdFROM Weather W1, Weather W2WHERE TO_DAYS(W2.RecordDate) - TO_DAYS(W1.RecordDate) = 1 AND W2.Temperature > W1.Temperature198. House Robber
https://leetcode.com/problems/house-robber/
使用两个变量,prev2表示不考虑当前数的前一个数时,获取的最大收益;prev则综合比较prev2与当前数之和,与不考虑当前数时的收益,哪一个更大选哪个。
class Solution { public int rob(int[] nums) { if (nums == null || nums.length == 0) { return 0; }
// 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 num : nums) { int temp = prev; prev = Math.max(prev, prev2 + num); prev2 = temp; }
return prev; }}199. Binary Tree Right Side View
https://leetcode.com/problems/binary-tree-right-side-view/
正常情况下,不断向下搜索当前节点的右节点;但如果右节点为空,则返回搜索左节点。搜索过程中,层数和结果list的长度应当始终对应。
/** * 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> rightSideView(TreeNode root) { List<Integer> result = new LinkedList<>(); helper(result, root, 1); return result; }
private void helper(List<Integer> result, TreeNode node, int depth) { if (node == null) { return; } if (result.size() < depth) { result.add(node.val); }
helper(result, node.right, depth + 1); helper(result, node.left, depth + 1); }}200. Number of Islands
https://leetcode.com/problems/number-of-islands/
使用DFS,已经走过的格子需要置为0,每遇到一个’1’岛屿数就加一。这是因为如果陆地是连续的,在计数之前,在此前的DFS递归调用已经将当前岛屿的邻近’1’全部变为0了。
class Solution { private int row = 0, col = 0;
public int numIslands(char[][] grid) { if (grid.length == 0 || grid[0].length == 0) { return 0; }
row = grid.length; col = grid[0].length;
int result = 0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == '1') { dfs(grid, i, j); result++; } } }
return result; }
private void dfs(char[][] grid, int x, int y) { if (x < 0 || y < 0 || x >= row || y >= col || grid[x][y] != '1') { return; }
grid[x][y] = '0';
dfs(grid, x + 1, y); dfs(grid, x - 1, y); dfs(grid, x, y + 1); dfs(grid, x, y - 1); }}