312. 戳气球
把nums数组扩充一下,首尾加一个数字1。
dp[i][j]代表开区间(i,j)所能获得硬币的最大数量。
边界:
(i,j)区间长度小于等于2时,能获得的硬币数为0。
转移方程:
遍历开区间内的所有值,记为k,k代表这个开区间(i,j)最后一个戳破的气球。
那么开区间(i,j)的收益就为dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]。
dp[i][j]等于所有的k取收益最大的一个。
区间长度从3开始遍历,直到区间长度达到数组长度。这样才能保证dp数组更新正确。
最后答案就为dp[0][nums.length-1]
class Solution {
public int maxCoins(int[] nums) {
int[] newNums = new int[nums.length + 2];
newNums[0] = newNums[nums.length + 1] = 1;
System.arraycopy(nums, 0, newNums, 1, nums.length);
nums = newNums;
int[][] dp = new int[nums.length][nums.length];
for (int L = 3; L <= nums.length; L++) {
for (int i = 0; i <= nums.length - L; i++) {
int res = 0;
int j = i + L - 1;
for (int k = i + 1; k < j; k++) {
int left = dp[i][k];
int right = dp[k][j];
res = Math.max(res, left + nums[i] * nums[k] * nums[j] + right);
}
dp[i][j] = res;
}
}
return dp[0][nums.length - 1];
}
}
313. 超级丑数
多指针,思想和第264. 丑数 II一样。
pos[j]代表应该用丑数数组uglies中的第几个数去乘以质数数组primes中的第j个数。 初始pos[j]都等于0,每一轮遍历时,都先选出下一个最小的丑数,为了避免重复,需要把所有的uglies[pos[j]] * primes[j] 等于这个丑数的pos[j]进行加1。
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int k = primes.length;
int[] uglies = new int[n], pos = new int[k];
uglies[0] = 1;
for (int i = 1; i < n; i++) {
int min = Integer.MAX_VALUE, index = 0;
for (int j = 0; j < k; j++) {
if (uglies[pos[j]] * primes[j] < min) {
min = uglies[pos[j]] * primes[j];
index = j;
}
}
for (int j = 0; j < k; j++) {
if (min == uglies[pos[j]] * primes[j]) {
pos[j]++;
}
}
uglies[i] = min;
}
return uglies[n - 1];
}
}
315. 计算右侧小于当前元素的个数
暴力法,超时:
class Solution {
public List countSmaller(int[] nums) {
List res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int count = 0;
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[i]) {
count++;
}
}
res.add(count);
}
return res;
}
}
树状数组:
316. 去除重复字母
先使用一个Map记录每一个字符最后出现的位置,使用一个Set记录某个字符是否在栈中。
遍历字符串,如果栈中没有这个字符,将它进栈,但是进栈之前还需要判断一下:
如果栈顶元素比它大,且栈顶元素之后还会出现,那么将栈顶元素出栈,Set中也要移除栈顶元素。
最后把所有元素出栈就是结果的倒序。
class Solution {
public String removeDuplicateLetters(String s) {
Deque stack = new ArrayDeque<>();
Set isVisit = new HashSet<>();
Map lastPos = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
lastPos.put(s.charAt(i), i);
}
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!isVisit.contains(c)) {
while (!stack.isEmpty() && c < stack.peek() && lastPos.get(stack.peek()) > i) {
isVisit.remove(stack.pop());
}
isVisit.add(c);
stack.push(c);
}
}
StringBuilder res = new StringBuilder();
while (!stack.isEmpty()) {
res.append(stack.pop());
}
res.reverse();
return res.toString();
}
}
318. 最大单词长度乘积
class Solution {
public int maxProduct(String[] words) {
Arrays.sort(words, (o1, o2) -> o2.length() - o1.length());
int res = 0;
for (int i = 0; i < words.length; i++) {
Set set = new HashSet<>();
for (char c : words[i].toCharArray()) {
set.add(c);
}
for (int j = i + 1; j < words.length; j++) {
boolean flag = true;
for (char c : words[j].toCharArray()) {
if (set.contains(c)) {
flag = false;
break;
}
}
if (flag == true) {
res = Math.max(res, words[i].length() * words[j].length());
}
}
}
return res;
}
}
319. 灯泡开关
数学题,找规律,只有是完全平方的数最后是亮着的,1到n中完全平方的个数就等于 sqrt(n)向下取整。
class Solution {
public int bulbSwitch(int n) {
return (int)Math.sqrt(n * 1.0);
}
}
322. 零钱兑换
回溯,超时:
class Solution {
int res = Integer.MAX_VALUE;
List temp = new ArrayList<>();
private void dfs(int begin, int[] coins, int amount) {
if (begin > coins.length || amount < 0) {
return;
}
if (amount == 0) {
res = Math.min(res, temp.size());
return;
}
for (int i = begin; i < coins.length; i++) {
temp.add(coins[i]);
dfs(i, coins, amount - coins[i]);
temp.remove(temp.size() - 1);
}
}
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
int i = 0, j = coins.length - 1;
while (i < j) {
int temp = coins[i];
coins[i] = coins[j];
coins[j] = temp;
i++;
j--;
}
dfs(0, coins, amount);
return res == Integer.MAX_VALUE ? -1 : res;
}
}
动态规划:
dp[i]代表凑出金额为i所需最少的硬币数。 初始都赋为amount+1 ,如果最终dp[i]的值是amount+1,说明无法凑出。
边界:
dp[0] = 0
转移方程:
从所有硬币中选择一个硬币j,只要这个硬币j的金额小于等于i,dp[i]就等于dp[i-coins[j]] + 1。 遍历所有的硬币,dp[i]等于其中最小的那个。
最终答案就是dp[amount]。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
324. 摆动排序 II
先排序,然后左半部分和右半部分交叉填入结果中,为了保证答案正确,左边每次要先选最大的,右边每次也要先选最大的。
class Solution {
public void wiggleSort(int[] nums) {
Arrays.sort(nums);
int i = (int) Math.ceil(nums.length / 2.0) - 1, j = nums.length - 1;
int[] res = new int[nums.length];
int pos = 0;
while (pos != nums.length) {
res[pos++] = nums[i--];
if (pos == nums.length) {
break;
}
res[pos++] = nums[j--];
}
for (int k = 0; k < nums.length; k++) {
nums[k] = res[k];
}
}
}
326. 3的幂
class Solution {
public boolean isPowerOfThree(int n) {
if (n <= 0) {
return false;
}
while (n % 3 == 0) {
n /= 3;
}
return n == 1;
}
}
327. 区间和的个数
前缀和暴力法:
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long[] pre = new long[nums.length];
int res = 0;
for (int i = 0; i < nums.length; i++) {
pre[i] = (i - 1 >= 0 ? pre[i - 1] : 0) + nums[i];
}
for (int i = 0; i < nums.length; i++) {
for (int j = i; j < nums.length; j++) {
if (pre[j] - (i - 1 >= 0 ? pre[i - 1] : 0) >= lower &&
pre[j] - (i - 1 >= 0 ? pre[i - 1] : 0) <= upper) {
res++;
}
}
}
return res;
}
}
树状数组:
328. 奇偶链表
class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null) {
return null;
}
ListNode evenHead = head.next;
ListNode curOdd = head, curEven = evenHead;
while (curEven != null && curEven.next != null) {
curOdd.next = curEven.next;
curEven.next = curEven.next.next;
curOdd = curOdd.next;
curEven = curEven.next;
}
curOdd.next = evenHead;
return head;
}
}
329. 矩阵中的最长递增路径
dfs 超时:
class Solution {
int res = 1;
List temp = new ArrayList<>();
boolean[][] isVisit;
int[] X = {1, -1, 0, 0}, Y = {0, 0, 1, -1};
private void dfs(int i, int j, int[][] matrix) {
if (i < 0 || i >= matrix.length || j < 0 || j >= matrix[0].length || isVisit[i][j] == true) {
return;
}
if (temp.size() > 0 && matrix[i][j] <= temp.get(temp.size() - 1)) {
res = Math.max(res, temp.size());
return;
}
temp.add(matrix[i][j]);
res = Math.max(res, temp.size());
isVisit[i][j] = true;
for (int k = 0; k < 4; k++) {
int newI = i + X[k], newJ = j + Y[k];
dfs(newI, newJ, matrix);
}
temp.remove(temp.size() - 1);
isVisit[i][j] = false;
}
public int longestIncreasingPath(int[][] matrix) {
if (matrix.length == 0) {
return 0;
}
isVisit = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
dfs(i, j, matrix);
}
}
return res;
}
}
记忆化dfs:
class Solution {
int[][] dp;
int[] X = {0, 0, 1, -1}, Y = {1, -1, 0, 0};
private int dfs(int i, int j, int[][] matrix) {
if (dp[i][j] != 0) {
return dp[i][j];
}
dp[i][j]++;
for (int k = 0; k < 4; k++) {
int newI = i + X[k], newJ = j + Y[k];
if (newI >= 0 && newI < matrix.length && newJ >= 0 && newJ < matrix[0].length && matrix[newI][newJ] > matrix[i][j]) {
dp[i][j] = Math.max(dp[i][j], dfs(newI, newJ, matrix) + 1);
}
}
return dp[i][j];
}
public int longestIncreasingPath(int[][] matrix) {
if (matrix.length == 0) {
return 0;
}
int row = matrix.length, col = matrix[0].length;
dp = new int[row][col];
int res = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res = Math.max(res, dfs(i, j, matrix));
}
}
return res;
}
}
330. 按要求补齐数组
public class Solution {
public int minPatches(int[] nums, int n) {
int res = 0, i = 0;
long miss = 1;
while (miss <= n) {
if (i < nums.length && nums[i] <= miss) {
miss += nums[i++];
} else {
miss += miss;
res++;
}
}
return res;
}
}
331. 验证二叉树的前序序列化
class Solution {
public boolean isValidSerialization(String preorder) {
int slots = 1;
String[] split = preorder.split(",");
for (String s : split) {
slots--;
if (slots < 0) {
return false;
}
if (!"#".equals(s)) {
slots += 2;
}
}
return slots == 0;
}
}
332. 重新安排行程
通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。
通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。
具有欧拉回路的无向图称为欧拉图。
具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。
本题是欧拉图或半欧拉图。使用优先队列贪心的每次往字典序更小的结点进行dfs,直到没有新的到达为止,就把这个结点加入到结果中。最后的答案是将res反转得到。
class Solution {
Map> map = new HashMap>();
List res = new LinkedList();
public List findItinerary(List> tickets) {
for (List ticket : tickets) {
String from = ticket.get(0), to = ticket.get(1);
if (!map.containsKey(from)) {
map.put(from, new PriorityQueue());
}
map.get(from).offer(to);
}
dfs("JFK");
Collections.reverse(res);
return res;
}
public void dfs(String curr) {
while (map.containsKey(curr) && map.get(curr).size() > 0) {
String tmp = map.get(curr).poll();
dfs(tmp);
}
res.add(curr);
}
}
334. 递增的三元子序列
a 始终记录最小元素,b 为某个子序列里第二大的数。
接下来不断更新 a,同时保持 b 尽可能的小。
如果下一个元素比 b 大,说明找到了三元组。
class Solution {
public boolean increasingTriplet(int[] nums) {
int a = Integer.MAX_VALUE, b = Integer.MAX_VALUE;
for (int i : nums) {
if (i <= a) {
a = i;
} else if (i <= b) {
b = i;
} else {
return true;
}
}
return false;
}
}
336. 回文对
暴力法,超时:
class Solution {
private boolean isPar(String str) {
if ("".equals(str)) {
return true;
}
int i = 0, j = str.length() - 1;
while (i < j) {
if (str.charAt(i++) != str.charAt(j--)) {
return false;
}
}
return true;
}
public List> palindromePairs(String[] words) {
List> res = new ArrayList<>();
for (int i = 0; i < words.length; i++) {
for (int j = i + 1; j < words.length; j++) {
if (isPar(words[i] + words[j])) {
res.add(Arrays.asList(i, j));
}
if (isPar(words[j] + words[i])) {
res.add(Arrays.asList(j, i));
}
}
}
return res;
}
}
337. 打家劫舍 III
树形dp:
dp[0]代表偷当前结点的最大金额,dp[1]代表不偷当前结点的最大金额。
采取后序遍历,这样当到达根结点时,就已经知道左右子结点的信息了。
如果不偷当前结点,dp[0]等于左子树的最大金额加上右子树的最大金额。
如果偷当前结点,dp[1]等于当前结点金额加上左子树不偷的金额加上右子树不偷的金额。
class Solution {
private int[] dfs(TreeNode root) {
if (root == null) {
return new int[]{0, 0};
}
int[] left = dfs(root.left);
int[] right = dfs(root.right);
int[] dp = new int[2];
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = left[0] + right[0] + root.val;
return dp;
}
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
}
338. 比特位计数
n & (n-1)能把n中最低位的1变成0,其它位不变。
这样就很快计算出n中1的个数。
class Solution {
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = count(i);
}
return res;
}
private int count(int n) {
int sum = 0;
while (n != 0) {
sum++;
n &= n - 1;
}
return sum;
}
}