今天主要做了这两道题
560. 和为 K 的子数组、
兑换零钱(一)__牛客网 (nowcoder.com)
其中兑换零钱【力扣】版的测试用例太变态了,逼不得已去牛客上刷。
1.和为K的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subarray-sum-equals-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
说实话,这道题我第一眼看上去我就想用滑动窗口。 大概意思就是
做一个不断扩大的窗口(j++),一旦发现和大于目标值(i++,j=i+1),一旦发现等于目标值,即Result++,并重置窗口(i++,j=i+1)。 我相信这个思路是正确的,但我觉得时间复杂度过高了O(n²)。
class Solution {
public int subarraySum(int[] nums, int k) {
/*初始化计算值为0初始化返回值为0*/
int sum=0,res=0;
/*新建HashMap,格式为k=结果v=个数,如和为0的子集个数为1*/
Map<Integer,Integer> map=new HashMap<>();
map.put(0,1);
//循环中++i比i++效率更高
for(int i=0;i<nums.length;++i){
//将值累加
sum += nums[i];
//一旦发现sum-k=0或者sum-k=某个加载过的值,res累加
if(map.containsKey(sum-k)){
res+=map.get(sum-k);
}
//把计算出的全部结果都放入map中,初始为1
map.put(sum,map.getOrDefault(sum,0)+1);
}
return res;
}
}
思路如下
就是经典的空间换时间,走过的路使用HashMap记录下来。
2.兑换零钱
链接:https://www.nowcoder.com/questionTerminal/3911a20b3f8743058214ceaa099eeb45
来源:牛客网
给定数组arr,arr中所有的值都为正整数且不重复。
每个值代表一种面值的货币,每种面值的货币可以使用任意张,
再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.
数据范围:数组大小满足 0 \le n \le 100000≤n≤10000 ,
数组中每个数字都满足 0 < val \le 100000<val≤10000,
0 \le aim \le 50000≤aim≤5000
要求:时间复杂度 O(n \times aim)O(n×aim) ,空间复杂度 O(aim)O(aim)。
示例1
输入
[5,2,3],20
输出
4
示例2
输入
[5,2,3],0
输出
0
示例3
输入
[3,5],2
输出
-1
实话实说,我不是一个多聪明的人,承认自己的平凡并不丢人。所以这题我没有选择动态规划,因为我不确定下次变一道题,我还能否写出关系式。
好在我的回溯学得不错,于是这道题我打算用回溯+贪心+剪枝来做。
一开始我的思路:
先把数组排序,然后倒过来,每次都取最大硬币,如果运气好,总金额%最大硬币=0,那我直接返回除数就好。如果不能等于0,那我先到我最多能用多少最大的硬币,然后再用小硬币补。这时候只需要做个递归取用小硬币就好。 那么当我算出来的第一组硬币,就应该是最少的。
嗯,想得很美。
coins:[1, 4, 5]
amount:28
我的算法结果 28 = 5 + 5 + 5 + 5 + 5 + 1 + 1 + 1,8枚
实际的最小数 28 = 5 + 5 + 5 + 5 + 4 + 4,6枚
coins:[1, 7, 10]
amount:14
我的算法结果 14 = 10 + 1 + 1 + 1 + 1,5枚
实际的最小数 14 = 7+7,2枚
于是没有办法,不能暴力减枝,必须遍历全部取最小值,然后时间肯定超了。 经过一些大神的指点,代码写出来了。
import java.util.*;
public class Solution {
int res=Integer.MAX_VALUE;
public int minMoney (int[] arr, int aim) {
if(arr.length==0) return -1;
Arrays.sort(arr);
getRes(arr,aim,0,arr.length-1);
return res==Integer.MAX_VALUE?-1:res;
}
public void getRes(int[] arr, int aim,int count,int index){
if(index<0) return;
for(int i=(aim/arr[index]);i>=0;--i){
int result=aim-i*arr[index];
int newCount = count + i;
if(result==0){
res=Math.min(res,newCount);
break;
}
if (newCount + 1 >= res) break;
getRes(arr,result,newCount,index-1);
}
}
}
也是运用了贪心的思想,我一开始假设取所有都是最大的,一共需要多少枚(n)。一旦等于0,直接返回。不等于0,去用小硬币凑。凑不出来,就看需要(n-1)枚最大硬币,再用小的凑,直到能凑出来为止。这里的剪枝思路就是一旦硬币数已经超过之前的最小的硬币了,就不用再算了,算出来的结果也是多的,因为是从大硬币往小硬币用,只会越来越多。