323 零钱兑换(Medium)

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11

输出:3

解释:11 = 5 + 5 + 1

今天的每日一题在十天前做过,过一遍就不重新做了

动态规划

dp数组存当前下标的钱可以用的最少兑换次数,如果用现在的零钱兑现不了,就为-1

dp[k] = min{dp[i]+dp[k-1-i]} 当且仅当dp[i]和dp[k-1-i]都不为-1
但是如果这个本身就可以用零钱找开,就为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
public int coinChange(int[] coins, int amount) {
if (amount == 0){
return 0;
}
int[] dp = new int[amount+1];
for (int i = 0; i < coins.length; i++) {
if (coins[i] <= amount){
dp[coins[i]] = 1;
}
}
//System.out.println(Arrays.toString(dp));
for (int i = 1; i <= amount; i++) {
if (i != 1){
int head = 1;
int rear = i-1;
int min = Integer.MAX_VALUE;
//dp[k] = min{dp[i]+dp[k-1-i]} 当且仅当dp[i]和dp[k-1-i]都不为-1
while (head <= rear){
if (dp[head] != -1 && dp[rear]!=-1 && dp[head] + dp[rear] < min){
min = dp[head]+dp[rear];
}
//System.out.println(i+":"+dp[head] +" "+ dp[rear]);
head++;
rear--;
}
//最小值没有改变,说明不能找的开
if (dp[i] == 0 && min == Integer.MAX_VALUE){
dp[i] = -1;
}
//改变了就最小值
if (dp[i] == 0 && min != Integer.MAX_VALUE){
dp[i] = min;
}
//如果这个本身就是1的话就不考虑,还是1
}
else {
if (dp[i] == 0){
dp[i] = -1;
}
}
}
//System.out.println(Arrays.toString(dp));

return dp[amount];
}
}

238 除自身以外数组的乘积(Medium)

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

不要使用除法,且在 O(n) 时间复杂度内完成此题。

偷懒方法(就是用了除法)

最简单方法,所有乘积起来,当前元素只要除掉这个就可以,遍历一次就可以。0的时候要特殊判断一下,这个时候还是傻方法,遍历其他的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public int[] productExceptSelf(int[] nums) {
int[] ans = new int[nums.length];
if (nums.length == 1){
return nums;
}
else if (nums.length > 1){
int sum = nums[0];
//乘起来
for (int i = 1; i < nums.length; i++) {
sum *=nums[i];
}

for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0){
ans[i] = sum /nums[i];
}
//如果是0特判
else {
int front = i-1;
int next = i+1;
int anss = 1;
while (front >=0){
anss*=nums[front];
front--;
}
while (next <=nums.length-1){
anss*=nums[next];
next++;
}
ans[i] = anss;
}
}
}
return ans;
}
}

官方解:左右乘积列表

维护两个数组,L[i]是当前元素i左边的乘积,R[i]是i右边的乘积,返回的数组就是L[i]*R[i],每次都是o(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int[] productExceptSelf(int[] nums) {
int[] L = new int[nums.length];
int[] R = new int[nums.length];
int[] ans = new int[nums.length];
L[0] = 1;
R[nums.length-1] = 1;
for (int i = 1; i < nums.length; i++) {
L[i] = L[i-1]*nums[i-1];
}
for (int j = nums.length-2; j >=0 ; j--) {
R[j] = R[j+1]*nums[j+1];
}
for (int i = 0; i < nums.length; i++) {
ans[i] = L[i] * R[i];
}
return ans;
}
}

11 盛最多水的容器(Medium)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

暴力超时

遍历找最小

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int maxArea(int[] height) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < height.length-1; i++) {
for (int j = i+1; j < height.length; j++) {
max = Math.max(max,(j-i)*Math.min(height[j],height[i]));
}
}
return max;
}
}

官方解:双指针

感觉证明有待考察,初始左右指针一个在左边一个在右边,每次移动比较小的就可以找到。


简单理解为什么移动小的,因为本来就是取较小的,移动大的话铁定没之前大,因为距离缩短了1,移动小的还可能碰到大的使原来的变大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int maxArea(int[] height) {
int head = 0;
int rear = height.length-1;
int max = Integer.MIN_VALUE;
while (head < rear){
int length = rear-head;
max = Math.max(max,length*Math.min(height[head],height[rear]));
if (height[head]<height[rear]){
head++;
}
else if (height[head]>height[rear]){
rear--;
}
else {
head++;
}
}
return max;
}
}