class Solution { public int numSquares(int n) { // dp[i] : 组成和为 i 的最少完全平方数个数 // base case: dp[0] = 0; int[] dp = new int[n + 1]; for (int i = 1; i < n + 1; i++) { // 最大值即为i dp[i] = i; // 转移方程 for (int j = 1; i - j*j >= 0; j++) { dp[i] = Math.min(dp[i], dp[i - j*j] + 1); } } return dp[n]; } }
推荐题解:画解算法:279. 完全平方数
思路一:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就插入 i 指向的位置并一起向后移动,最后将 i 及其后面全部补0即可。
class Solution { public void moveZeroes(int[] nums) { int len = nums.length; int i = 0; // i 指向 非0 元素的下一个待插入位置 // j 一直向后遍历,每次遇到 非0 元素就插入i指向的位置并一起向后移动 for (int j = 0; j < len; j++) { if (nums[j] != 0) { nums[i++] = nums[j]; } } // i 及其后面全部补0即可 for ( ; i < len; i++) { nums[i] = 0; } } }
思路二:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就与 i 指向元素交换并一起向后移动,最后 [0, i)
之前全部为非0元素,(i, j)
之间全部为 0。
class Solution { public void moveZeroes(int[] nums) { int len = nums.length; int i = 0; // i 指向 非0 元素的下一个待插入位置 // 每次遇到 非0 元素就与 i 指向元素交换并一起向后移动 for (int j = 0; j < len; j++) { if (nums[j] != 0) { swap(nums, i, j); i++; } } } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
该题最简单的思路便是使用set
,但题目要求空间复杂度为O(1)
。另外一种简单思路是使用排序,然后检查重复数即可,但题目又要求时间复杂度为O(nlogn)
,且不能修改原数组。
思路一:快慢指针,建议先刷:141. 环形链表、142. 环形链表 II。
推荐题解:287.寻找重复数
class Solution { public int findDuplicate(int[] nums) { int slow = 0; int fast = nums[slow]; while (slow != fast) { fast = nums[nums[fast]]; slow = nums[slow]; } fast = nums[slow]; slow = 0; while (slow != fast) { fast = nums[fast]; slow = nums[slow]; } return slow; } }
思路二:将所有元素放置在其值将1的下标位置。如:1 应该放置在 0 位置,3,应该放置在 2 位置,这样,重复的元素至少有一个无法放置在对应位置。思路来自:剑指 Offer 03. 数组中重复的数字
class Solution { public int findDuplicate(int[] nums) { int len = nums.length; for (int i = 0; i < len - 1; i++) { // 如果元素的 值 和 下标 不匹配,则将其交换至对的位置 while ((nums[i] - 1) != i) { // 如果发现待交换的两个元素相同则直接返回 如:[3,1,3,4,2] if (nums[i] == nums[nums[i] - 1]) { return nums[i]; } swap(nums, i, nums[i] - 1); } } // 通常,最后一个元素为重复元素 return nums[len - 1]; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
备注:该方法修改了原来的数组。时间复杂度为O(n)
,因为每个元素至多交换一次,空间复杂度为O(1)
。
思路一:使用先序遍历进行序列化(dfs)
/** * 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) { if (root == null) { return "#"; } String leftSerial = serialize(root.left); String rightSerial = serialize(root.right); return root.val + "," + leftSerial + "," + rightSerial; } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { String[] strs = data.split(","); Deque<String> queue = new LinkedList<>(); for (String str : strs) { queue.offer(str); } return deserialize(queue); } private TreeNode deserialize(Deque<String> queue) { String rootVal = queue.poll(); if (rootVal.equals("#")) { return null; } TreeNode root = new TreeNode(Integer.parseInt(rootVal)); root.left = deserialize(queue); root.right = deserialize(queue); return root; } } // Your Codec object will be instantiated and called as such: // Codec ser = new Codec(); // Codec deser = new Codec(); // TreeNode ans = deser.deserialize(ser.serialize(root));
思路二:使用层次遍历进行序列化(bfs)
/** * 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) { Deque<TreeNode> queue = new LinkedList<>(); StringBuilder sb = new StringBuilder(); queue.offer(root); while (!queue.isEmpty()) { TreeNode temp = queue.poll(); if (temp != null) { sb.append(temp.val).append(","); queue.offer(temp.left); queue.offer(temp.right); } else { sb.append("#").append(","); } } System.out.println(sb.toString()); return sb.toString(); } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { String[] strs = data.split(","); Deque<TreeNode> queue = new LinkedList<>(); int i = 0; if (strs[i].equals("#")) return null; TreeNode root = new TreeNode(Integer.parseInt(strs[i++])); queue.offer(root); while (!queue.isEmpty()) { TreeNode temp = queue.poll(); String leftVal = strs[i++]; String rightVal = strs[i++]; if (!leftVal.equals("#")) { TreeNode leftChild = new TreeNode(Integer.parseInt(leftVal)); temp.left = leftChild; queue.offer(leftChild); } if (!rightVal.equals("#")) { TreeNode rightChild = new TreeNode(Integer.parseInt(rightVal)); temp.right = rightChild; queue.offer(rightChild); } } return root; } } // Your Codec object will be instantiated and called as such: // Codec ser = new Codec(); // Codec deser = new Codec(); // TreeNode ans = deser.deserialize(ser.serialize(root));
推荐题解:『手画图解』剖析DFS、BFS解法 | 二叉树的序列化与反序列化
思路:动态规划
状态:以当前字符结尾的字符串中最长递增子序列的长度
转移方程:dp[i] = max(dp[j] + 1, dp[i])
,其中 j < i
且 nums[j] < nums[i]
base case:dp[i] = 1
class Solution { public int lengthOfLIS(int[] nums) { int len = nums.length; // dp[i] 表示以当前字符结尾的字符串中最长递增子序列的长度 int[] dp = new int[len]; int maxLen = 0; for (int i = 0; i < len; i++) { //base case dp[i] = 1; for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLen = Math.max(maxLen, dp[i]); } return maxLen; } }
思路:回溯法
class Solution { public List<String> removeInvalidParentheses(String s) { // 左括号和右括号最少需要移除的个数 int leftRemove = 0, rightRemove = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { leftRemove++; } else if (s.charAt(i) == ')') { //存在左括号则消去一个 if (leftRemove > 0) { leftRemove--; } else if (leftRemove == 0) { rightRemove++; } } } StringBuilder track = new StringBuilder(); dfs(s, 0, track, 0, 0, leftRemove, rightRemove); return new ArrayList<>(res); } private Set<String> res = new HashSet<>(); private void dfs(String s, int index, StringBuilder track, int leftCnt, int rightCnt, int leftRemove, int rightRemove) { if (s.length() == index) { if (leftRemove == 0 && rightRemove == 0) { res.add(track.toString()); } return; } char ch = s.charAt(index); // 选择1:删除当前字符 if (ch == '(' && leftRemove > 0) { dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove - 1, rightRemove); } else if (ch == ')' && rightRemove > 0) { dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove - 1); } // 选择2:保留当前字符 track.append(ch); if (ch == '(' ) { dfs(s, index + 1, track, leftCnt + 1, rightCnt, leftRemove, rightRemove); } else if (ch == ')' && leftCnt > rightCnt) { dfs(s, index + 1, track, leftCnt, rightCnt + 1, leftRemove, rightRemove); } else if (ch != '(' && ch != ')') { dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove); } track.deleteCharAt(track.length() - 1); } }
推荐题解:删除无效的括号
思路:动态规划
class Solution { public int maxProfit(int[] prices) { int len = prices.length; int[][] dp = new int[len][2]; dp[0][0] = 0; dp[0][1] = -prices[0]; for (int i = 1; i < len; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], (i >= 2 ? dp[i - 2][0] : 0) - prices[i]); } return dp[len - 1][0]; } }
思路:动态规划。推荐题解:[这个菜谱, 自己在家也能做] 关键思路解释
class Solution { public int maxCoins(int[] nums) { int len = nums.length; // 前后两位各插入1 int[] newNums = new int[len + 2]; newNums[0] = 1; newNums[len + 1] = 1; for (int i = 0; i < len; i++) { newNums[i + 1] = nums[i]; } //dp[i][j] 代表 戳爆开区间 (i, j) 中的气球所能取得的最大值 //默认值都为0,base case: dp[i][j] = 0, j - i < 3 int[][] dp = new int[len + 2][len + 2]; for (int size = 3; size <= len + 2; size++) { //开区间起始位置 for (int start = 0; start <= len + 2 - size; start++) { int max = 0; //列举最后一个被戳爆的气球为 newNums[k]的所有情况, K属于[start + 1, start + size - 1),找到最大值 for (int k = start + 1; k < start + size - 1; k++) { max = Math.max(max, dp[start][k] + dp[k][start + size - 1] + newNums[start] * newNums[k] * newNums[start + size - 1]); } dp[start][start + size - 1] = max; } } return dp[0][len + 1]; } }
思路:动态规划。
class Solution { public int coinChange(int[] coins, int amount) { //状态:dp[i] 表示凑够i需要的最少硬币数 int[] dp = new int[amount + 1]; //求最小值,先初始为足够大。(若能凑成,最多需要amount枚硬币) Arrays.fill(dp, amount + 1); //base case dp[0] = 0; for (int i = 1; i <= amount; i++) { for (int j = 0; j < coins.length; j++) { //当前背包(总金额)若能装下物品(硬币面额) if (i >= coins[j]) { dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]); } } } return dp[amount] >= amount + 1 ? -1 : dp[amount]; } }